summaryrefslogtreecommitdiffstats
path: root/watcher/filenotify/poller_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'watcher/filenotify/poller_test.go')
-rw-r--r--watcher/filenotify/poller_test.go304
1 files changed, 304 insertions, 0 deletions
diff --git a/watcher/filenotify/poller_test.go b/watcher/filenotify/poller_test.go
new file mode 100644
index 000000000..b4723c758
--- /dev/null
+++ b/watcher/filenotify/poller_test.go
@@ -0,0 +1,304 @@
+// Package filenotify is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
+// Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
+package filenotify
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+ "time"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/fsnotify/fsnotify"
+ "github.com/gohugoio/hugo/htesting"
+)
+
+const (
+ subdir1 = "subdir1"
+ subdir2 = "subdir2"
+ watchWaitTime = 200 * time.Millisecond
+)
+
+var (
+ isMacOs = runtime.GOOS == "darwin"
+ isWindows = runtime.GOOS == "windows"
+ isCI = htesting.IsCI()
+)
+
+func TestPollerAddRemove(t *testing.T) {
+ c := qt.New(t)
+ w := NewPollingWatcher(watchWaitTime)
+
+ c.Assert(w.Add("foo"), qt.Not(qt.IsNil))
+ c.Assert(w.Remove("foo"), qt.Not(qt.IsNil))
+
+ f, err := ioutil.TempFile("", "asdf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(f.Name())
+ c.Assert(w.Add(f.Name()), qt.IsNil)
+ c.Assert(w.Remove(f.Name()), qt.IsNil)
+
+}
+
+func TestPollerEvent(t *testing.T) {
+ c := qt.New(t)
+
+ for _, poll := range []bool{true, false} {
+ if !(poll || isMacOs) || isCI {
+ // Only run the fsnotify tests on MacOS locally.
+ continue
+ }
+ method := "fsnotify"
+ if poll {
+ method = "poll"
+ }
+
+ c.Run(fmt.Sprintf("%s, Watch dir", method), func(c *qt.C) {
+ dir, w := preparePollTest(c, poll)
+ subdir := filepath.Join(dir, subdir1)
+ c.Assert(w.Add(subdir), qt.IsNil)
+
+ filename := filepath.Join(subdir, "file1")
+
+ // Write to one file.
+ c.Assert(ioutil.WriteFile(filename, []byte("changed"), 0600), qt.IsNil)
+
+ var expected []fsnotify.Event
+
+ if poll {
+ expected = append(expected, fsnotify.Event{Name: filename, Op: fsnotify.Write})
+ assertEvents(c, w, expected...)
+ } else {
+ // fsnotify sometimes emits Chmod before Write,
+ // which is hard to test, so skip it here.
+ drainEvents(c, w)
+ }
+
+ // Remove one file.
+ filename = filepath.Join(subdir, "file2")
+ c.Assert(os.Remove(filename), qt.IsNil)
+ assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Remove})
+
+ // Add one file.
+ filename = filepath.Join(subdir, "file3")
+ c.Assert(ioutil.WriteFile(filename, []byte("new"), 0600), qt.IsNil)
+ assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Create})
+
+ // Remove entire directory.
+ subdir = filepath.Join(dir, subdir2)
+ c.Assert(w.Add(subdir), qt.IsNil)
+
+ c.Assert(os.RemoveAll(subdir), qt.IsNil)
+
+ expected = expected[:0]
+
+ // This looks like a bug in fsnotify on MacOS. There are
+ // 3 files in this directory, yet we get Remove events
+ // for one of them + the directory.
+ if !poll {
+ expected = append(expected, fsnotify.Event{Name: filepath.Join(subdir, "file2"), Op: fsnotify.Remove})
+ }
+ expected = append(expected, fsnotify.Event{Name: subdir, Op: fsnotify.Remove})
+ assertEvents(c, w, expected...)
+
+ })
+
+ c.Run(fmt.Sprintf("%s, Add should not trigger event", method), func(c *qt.C) {
+ dir, w := preparePollTest(c, poll)
+ subdir := filepath.Join(dir, subdir1)
+ w.Add(subdir)
+ assertEvents(c, w)
+ // Create a new sub directory and add it to the watcher.
+ subdir = filepath.Join(dir, subdir1, subdir2)
+ c.Assert(os.Mkdir(subdir, 0777), qt.IsNil)
+ w.Add(subdir)
+ // This should create only one event.
+ assertEvents(c, w, fsnotify.Event{Name: subdir, Op: fsnotify.Create})
+ })
+
+ }
+}
+
+func TestPollerClose(t *testing.T) {
+ c := qt.New(t)
+ w := NewPollingWatcher(watchWaitTime)
+ f1, err := ioutil.TempFile("", "f1")
+ c.Assert(err, qt.IsNil)
+ f2, err := ioutil.TempFile("", "f2")
+ c.Assert(err, qt.IsNil)
+ filename1 := f1.Name()
+ filename2 := f2.Name()
+ f1.Close()
+ f2.Close()
+
+ c.Assert(w.Add(filename1), qt.IsNil)
+ c.Assert(w.Add(filename2), qt.IsNil)
+ c.Assert(w.Close(), qt.IsNil)
+ c.Assert(w.Close(), qt.IsNil)
+ c.Assert(ioutil.WriteFile(filename1, []byte("new"), 0600), qt.IsNil)
+ c.Assert(ioutil.WriteFile(filename2, []byte("new"), 0600), qt.IsNil)
+ // No more event as the watchers are closed.
+ assertEvents(c, w)
+
+ f2, err = ioutil.TempFile("", "f2")
+ c.Assert(err, qt.IsNil)
+
+ defer os.Remove(f2.Name())
+
+ c.Assert(w.Add(f2.Name()), qt.Not(qt.IsNil))
+
+}
+
+func TestCheckChange(t *testing.T) {
+ c := qt.New(t)
+
+ dir := prepareTestDirWithSomeFiles(c, "check-change")
+
+ stat := func(s ...string) os.FileInfo {
+ fi, err := os.Stat(filepath.Join(append([]string{dir}, s...)...))
+ c.Assert(err, qt.IsNil)
+ return fi
+ }
+
+ f0, f1, f2 := stat(subdir2, "file0"), stat(subdir2, "file1"), stat(subdir2, "file2")
+ d1 := stat(subdir1)
+
+ // Note that on Windows, only the 0200 bit (owner writable) of mode is used.
+ c.Assert(os.Chmod(filepath.Join(filepath.Join(dir, subdir2, "file1")), 0400), qt.IsNil)
+ f1_2 := stat(subdir2, "file1")
+
+ c.Assert(ioutil.WriteFile(filepath.Join(filepath.Join(dir, subdir2, "file2")), []byte("changed"), 0600), qt.IsNil)
+ f2_2 := stat(subdir2, "file2")
+
+ c.Assert(checkChange(f0, nil), qt.Equals, fsnotify.Remove)
+ c.Assert(checkChange(nil, f0), qt.Equals, fsnotify.Create)
+ c.Assert(checkChange(f1, f1_2), qt.Equals, fsnotify.Chmod)
+ c.Assert(checkChange(f2, f2_2), qt.Equals, fsnotify.Write)
+ c.Assert(checkChange(nil, nil), qt.Equals, fsnotify.Op(0))
+ c.Assert(checkChange(d1, f1), qt.Equals, fsnotify.Op(0))
+ c.Assert(checkChange(f1, d1), qt.Equals, fsnotify.Op(0))
+}
+
+func BenchmarkPoller(b *testing.B) {
+ runBench := func(b *testing.B, item *itemToWatch) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ evs, err := item.checkForChanges()
+ if err != nil {
+ b.Fatal(err)
+ }
+ if len(evs) != 0 {
+ b.Fatal("got events")
+ }
+
+ }
+
+ }
+
+ b.Run("Check for changes in dir", func(b *testing.B) {
+ c := qt.New(b)
+ dir := prepareTestDirWithSomeFiles(c, "bench-check")
+ item, err := newItemToWatch(dir)
+ c.Assert(err, qt.IsNil)
+ runBench(b, item)
+
+ })
+
+ b.Run("Check for changes in file", func(b *testing.B) {
+ c := qt.New(b)
+ dir := prepareTestDirWithSomeFiles(c, "bench-check-file")
+ filename := filepath.Join(dir, subdir1, "file1")
+ item, err := newItemToWatch(filename)
+ c.Assert(err, qt.IsNil)
+ runBench(b, item)
+ })
+
+}
+
+func prepareTestDirWithSomeFiles(c *qt.C, id string) string {
+ dir, err := ioutil.TempDir("", fmt.Sprintf("test-poller-dir-%s", id))
+ c.Assert(err, qt.IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(dir, subdir1), 0777), qt.IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(dir, subdir2), 0777), qt.IsNil)
+
+ for i := 0; i < 3; i++ {
+ c.Assert(ioutil.WriteFile(filepath.Join(dir, subdir1, fmt.Sprintf("file%d", i)), []byte("hello1"), 0600), qt.IsNil)
+ }
+
+ for i := 0; i < 3; i++ {
+ c.Assert(ioutil.WriteFile(filepath.Join(dir, subdir2, fmt.Sprintf("file%d", i)), []byte("hello2"), 0600), qt.IsNil)
+ }
+
+ c.Cleanup(func() {
+ os.RemoveAll(dir)
+ })
+
+ return dir
+}
+
+func preparePollTest(c *qt.C, poll bool) (string, FileWatcher) {
+ var w FileWatcher
+ if poll {
+ w = NewPollingWatcher(watchWaitTime)
+ } else {
+ var err error
+ w, err = NewEventWatcher()
+ c.Assert(err, qt.IsNil)
+ }
+
+ dir := prepareTestDirWithSomeFiles(c, fmt.Sprint(poll))
+
+ c.Cleanup(func() {
+ w.Close()
+ })
+ return dir, w
+}
+
+func assertEvents(c *qt.C, w FileWatcher, evs ...fsnotify.Event) {
+ c.Helper()
+ i := 0
+ check := func() error {
+ for {
+ select {
+ case got := <-w.Events():
+ if i > len(evs)-1 {
+ return fmt.Errorf("got too many event(s): %q", got)
+ }
+ expected := evs[i]
+ i++
+ if expected.Name != got.Name {
+ return fmt.Errorf("got wrong filename, expected %q: %v", expected.Name, got.Name)
+ } else if got.Op&expected.Op != expected.Op {
+ return fmt.Errorf("got wrong event type, expected %q: %v", expected.Op, got.Op)
+ }
+ case e := <-w.Errors():
+ return fmt.Errorf("got unexpected error waiting for events %v", e)
+ case <-time.After(watchWaitTime + (watchWaitTime / 2)):
+ return nil
+ }
+ }
+ }
+ c.Assert(check(), qt.IsNil)
+ c.Assert(i, qt.Equals, len(evs))
+}
+
+func drainEvents(c *qt.C, w FileWatcher) {
+ c.Helper()
+ check := func() error {
+ for {
+ select {
+ case <-w.Events():
+ case e := <-w.Errors():
+ return fmt.Errorf("got unexpected error waiting for events %v", e)
+ case <-time.After(watchWaitTime * 2):
+ return nil
+ }
+ }
+ }
+ c.Assert(check(), qt.IsNil)
+}