2018-08-16 20:53:47 +02:00
|
|
|
package watch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Each implementation of the notify interface should have the same basic
|
|
|
|
// behavior.
|
|
|
|
|
|
|
|
func TestNoEvents(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
f.assertEvents()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEventOrdering(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
count := 8
|
|
|
|
dirs := make([]string, count)
|
|
|
|
for i, _ := range dirs {
|
|
|
|
dir, err := f.root.NewDir("watched")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
dirs[i] = dir.Path()
|
|
|
|
err = f.notify.Add(dir.Path())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f.fsync()
|
|
|
|
f.events = nil
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
var expected []string
|
2018-08-16 20:53:47 +02:00
|
|
|
for i, dir := range dirs {
|
|
|
|
base := fmt.Sprintf("%d.txt", i)
|
|
|
|
p := filepath.Join(dir, base)
|
|
|
|
err := ioutil.WriteFile(p, []byte(base), os.FileMode(0777))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
expected = append(expected, filepath.Join(dir, base))
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
f.assertEvents(expected...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWatchesAreRecursive(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a sub directory
|
|
|
|
subPath := filepath.Join(root.Path(), "sub")
|
2018-08-20 15:47:40 +02:00
|
|
|
err = os.MkdirAll(subPath, os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-16 20:53:47 +02:00
|
|
|
|
|
|
|
// watch parent
|
|
|
|
err = f.notify.Add(root.Path())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f.fsync()
|
|
|
|
f.events = nil
|
|
|
|
// change sub directory
|
|
|
|
changeFilePath := filepath.Join(subPath, "change")
|
|
|
|
_, err = os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(changeFilePath)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// watch parent
|
|
|
|
err = f.notify.Add(root.Path())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
f.fsync()
|
|
|
|
f.events = nil
|
|
|
|
// add a sub directory
|
|
|
|
subPath := filepath.Join(root.Path(), "sub")
|
2018-08-20 15:47:40 +02:00
|
|
|
err = os.MkdirAll(subPath, os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
f.t.Fatal(err)
|
|
|
|
}
|
2018-08-16 20:53:47 +02:00
|
|
|
// change something inside sub directory
|
|
|
|
changeFilePath := filepath.Join(subPath, "change")
|
|
|
|
_, err = os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(subPath, changeFilePath)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestWatchNonExistentPath(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(root.Path(), "change")
|
|
|
|
|
|
|
|
err = f.notify.Add(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-23 19:24:47 +02:00
|
|
|
|
|
|
|
f.fsync()
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
d1 := []byte("hello\ngo\n")
|
|
|
|
err = ioutil.WriteFile(path, d1, 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(path)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemove(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(root.Path(), "change")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
d1 := []byte("hello\ngo\n")
|
|
|
|
err = ioutil.WriteFile(path, d1, 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = f.notify.Add(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
f.fsync()
|
|
|
|
f.events = nil
|
|
|
|
err = os.Remove(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(path)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemoveAndAddBack(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
2018-08-22 21:48:33 +02:00
|
|
|
path := filepath.Join(f.watched.Path(), "change")
|
2018-08-16 20:53:47 +02:00
|
|
|
|
|
|
|
d1 := []byte("hello\ngo\n")
|
2018-08-22 21:48:33 +02:00
|
|
|
err := ioutil.WriteFile(path, d1, 0644)
|
2018-08-16 20:53:47 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = f.notify.Add(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:48:33 +02:00
|
|
|
f.assertEvents(path)
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
err = os.Remove(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(path)
|
2018-08-16 20:53:47 +02:00
|
|
|
f.events = nil
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(path, d1, 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(path)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestSingleFile(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(root.Path(), "change")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
d1 := []byte("hello\ngo\n")
|
|
|
|
err = ioutil.WriteFile(path, d1, 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.notify.Add(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-23 17:56:00 +02:00
|
|
|
f.fsync()
|
2018-08-16 20:53:47 +02:00
|
|
|
|
|
|
|
d2 := []byte("hello\nworld\n")
|
|
|
|
err = ioutil.WriteFile(path, d2, 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
f.assertEvents(path)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-08-22 21:59:46 +02:00
|
|
|
func TestWriteBrokenLink(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
link := filepath.Join(f.watched.Path(), "brokenLink")
|
|
|
|
missingFile := filepath.Join(f.watched.Path(), "missingFile")
|
|
|
|
err := os.Symlink(missingFile, link)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f.assertEvents(link)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWriteGoodLink(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
goodFile := filepath.Join(f.watched.Path(), "goodFile")
|
|
|
|
err := ioutil.WriteFile(goodFile, []byte("hello"), 0644)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
link := filepath.Join(f.watched.Path(), "goodFileSymlink")
|
|
|
|
err = os.Symlink(goodFile, link)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f.assertEvents(goodFile, link)
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:13:36 +02:00
|
|
|
func TestWatchBrokenLink(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
newRoot, err := NewDir(t.Name())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer newRoot.TearDown()
|
|
|
|
|
|
|
|
link := filepath.Join(newRoot.Path(), "brokenLink")
|
|
|
|
missingFile := filepath.Join(newRoot.Path(), "missingFile")
|
|
|
|
err = os.Symlink(missingFile, link)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.notify.Add(newRoot.Path())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Remove(link)
|
|
|
|
f.assertEvents(link)
|
|
|
|
}
|
|
|
|
|
2018-10-18 16:44:07 +02:00
|
|
|
func TestMoveAndReplace(t *testing.T) {
|
|
|
|
f := newNotifyFixture(t)
|
|
|
|
defer f.tearDown()
|
|
|
|
|
|
|
|
root, err := f.root.NewDir("root")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
file := filepath.Join(root.Path(), "myfile")
|
|
|
|
err = ioutil.WriteFile(file, []byte("hello"), 0777)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.notify.Add(file)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpFile := filepath.Join(root.Path(), ".myfile.swp")
|
|
|
|
err = ioutil.WriteFile(tmpFile, []byte("world"), 0777)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.Rename(tmpFile, file)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f.assertEvents(file)
|
|
|
|
}
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
type notifyFixture struct {
|
|
|
|
t *testing.T
|
|
|
|
root *TempDir
|
|
|
|
watched *TempDir
|
|
|
|
notify Notify
|
2018-08-22 21:33:06 +02:00
|
|
|
events []FileEvent
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func newNotifyFixture(t *testing.T) *notifyFixture {
|
|
|
|
SetLimitChecksEnabled(false)
|
|
|
|
notify, err := NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
root, err := NewDir(t.Name())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
watched, err := root.NewDir("watched")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = notify.Add(watched.Path())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return ¬ifyFixture{
|
|
|
|
t: t,
|
|
|
|
root: root,
|
|
|
|
watched: watched,
|
|
|
|
notify: notify,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
func (f *notifyFixture) assertEvents(expected ...string) {
|
|
|
|
f.fsync()
|
2018-08-16 20:53:47 +02:00
|
|
|
|
|
|
|
if len(f.events) != len(expected) {
|
|
|
|
f.t.Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, actual := range f.events {
|
2018-08-22 21:33:06 +02:00
|
|
|
e := FileEvent{expected[i]}
|
|
|
|
if actual != e {
|
|
|
|
f.t.Fatalf("Got event %v (expected %v)", actual, e)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *notifyFixture) fsync() {
|
|
|
|
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
|
|
|
|
syncPath := filepath.Join(f.watched.Path(), syncPathBase)
|
|
|
|
anySyncPath := filepath.Join(f.watched.Path(), "sync-")
|
|
|
|
timeout := time.After(time.Second)
|
|
|
|
|
|
|
|
err := ioutil.WriteFile(syncPath, []byte(fmt.Sprintf("%s", time.Now())), os.FileMode(0777))
|
|
|
|
if err != nil {
|
|
|
|
f.t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
F:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case err := <-f.notify.Errors():
|
|
|
|
f.t.Fatal(err)
|
|
|
|
|
|
|
|
case event := <-f.notify.Events():
|
2018-08-22 21:33:06 +02:00
|
|
|
if strings.Contains(event.Path, syncPath) {
|
2018-08-16 20:53:47 +02:00
|
|
|
break F
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
if strings.Contains(event.Path, anySyncPath) {
|
2018-08-16 20:53:47 +02:00
|
|
|
continue
|
|
|
|
}
|
2018-08-22 21:33:06 +02:00
|
|
|
|
|
|
|
// Don't bother tracking duplicate changes to the same path
|
|
|
|
// for testing.
|
|
|
|
if len(f.events) > 0 && f.events[len(f.events)-1].Path == event.Path {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
f.events = append(f.events, event)
|
|
|
|
|
|
|
|
case <-timeout:
|
|
|
|
f.t.Fatalf("fsync: timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
f.t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *notifyFixture) tearDown() {
|
|
|
|
SetLimitChecksEnabled(true)
|
|
|
|
err := f.root.TearDown()
|
|
|
|
if err != nil {
|
|
|
|
f.t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = f.notify.Close()
|
|
|
|
if err != nil {
|
|
|
|
f.t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|