]> go.fuhry.dev Git - fsnotify.git/commitdiff
Move some files around
authorMartin Tournoij <martin@arp242.net>
Sat, 6 Aug 2022 18:24:59 +0000 (20:24 +0200)
committerMartin Tournoij <martin@arp242.net>
Sat, 6 Aug 2022 18:24:59 +0000 (20:24 +0200)
This will be useful once we start splitting out "systems" and "backends"
a bit more, e.g. when polling support is added.

backend_fen.go [moved from fen.go with 100% similarity]
backend_inotify.go [moved from inotify.go with 100% similarity]
backend_inotify_test.go [moved from inotify_test.go with 100% similarity]
backend_kqueue.go [moved from kqueue.go with 100% similarity]
backend_other.go [moved from fsnotify_unsupported.go with 100% similarity]
backend_windows.go [moved from windows.go with 100% similarity]
fsnotify_test.go
integration_test.go [deleted file]
system_bsd.go [moved from open_mode_bsd.go with 100% similarity]
system_darwin.go [moved from open_mode_darwin.go with 100% similarity]
system_darwin_test.go [moved from integration_darwin_test.go with 98% similarity]

similarity index 100%
rename from fen.go
rename to backend_fen.go
similarity index 100%
rename from inotify.go
rename to backend_inotify.go
similarity index 100%
rename from inotify_test.go
rename to backend_inotify_test.go
similarity index 100%
rename from kqueue.go
rename to backend_kqueue.go
similarity index 100%
rename from fsnotify_unsupported.go
rename to backend_other.go
similarity index 100%
rename from windows.go
rename to backend_windows.go
index 5f6c8827fd0de93189bf69bfeeeb4d484352b589..6e369d62d6acd3c2be2e92b28d7acaa40933ce5e 100644 (file)
-//go:build !plan9
-// +build !plan9
+//go:build !plan9 && !solaris
+// +build !plan9,!solaris
 
 package fsnotify
 
 import (
+       "errors"
+       "fmt"
+       "path/filepath"
+       "runtime"
+       "strings"
+       "sync/atomic"
        "testing"
+       "time"
+
+       "github.com/fsnotify/fsnotify/internal"
 )
 
+func TestWatch(t *testing.T) {
+       tests := []testCase{
+               {"multiple creates", func(t *testing.T, w *Watcher, tmp string) {
+                       file := filepath.Join(tmp, "file")
+                       addWatch(t, w, tmp)
+
+                       cat(t, "data", file)
+                       rm(t, file)
+
+                       touch(t, file)       // Recreate the file
+                       cat(t, "data", file) // Modify
+                       cat(t, "data", file) // Modify
+               }, `
+                       create  /file
+                       write   /file
+                       remove  /file
+                       create  /file
+                       write   /file
+                       write   /file
+               `},
+
+               {"dir only", func(t *testing.T, w *Watcher, tmp string) {
+                       beforeWatch := filepath.Join(tmp, "beforewatch")
+                       file := filepath.Join(tmp, "file")
+
+                       touch(t, beforeWatch)
+                       addWatch(t, w, tmp)
+
+                       cat(t, "data", file)
+                       rm(t, file)
+                       rm(t, beforeWatch)
+               }, `
+                       create /file
+                       write  /file
+                       remove /file
+                       remove /beforewatch
+               `},
+
+               {"subdir", func(t *testing.T, w *Watcher, tmp string) {
+                       addWatch(t, w, tmp)
+
+                       file := filepath.Join(tmp, "file")
+                       dir := filepath.Join(tmp, "sub")
+                       dirfile := filepath.Join(tmp, "sub/file2")
+
+                       mkdir(t, dir)     // Create sub-directory
+                       touch(t, file)    // Create a file
+                       touch(t, dirfile) // Create a file (Should not see this! we are not watching subdir)
+                       time.Sleep(200 * time.Millisecond)
+                       rmAll(t, dir) // Make sure receive deletes for both file and sub-directory
+                       rm(t, file)
+               }, `
+                       create /sub
+                       create /file
+                       remove /sub
+                       remove /file
+
+                       # Windows includes a write for the /sub dir too, two of them even(?)
+                       windows:
+                               create /sub
+                               create /file
+                               write  /sub
+                               write  /sub
+                               remove /sub
+                               remove /file
+               `},
+
+               {"file in directory is not readable", func(t *testing.T, w *Watcher, tmp string) {
+                       if runtime.GOOS == "windows" {
+                               t.Skip("attributes don't work on Windows")
+                       }
+
+                       touch(t, tmp, "file-unreadable")
+                       chmod(t, 0, tmp, "file-unreadable")
+                       touch(t, tmp, "file")
+                       addWatch(t, w, tmp)
+
+                       cat(t, "hello", tmp, "file")
+                       rm(t, tmp, "file")
+                       rm(t, tmp, "file-unreadable")
+               }, `
+                       WRITE     "/file"
+                       REMOVE    "/file"
+                       REMOVE    "/file-unreadable"
+
+                       # We never set up a watcher on the unreadable file, so we don't get
+                       # the REMOVE.
+                       kqueue:
+                WRITE    "/file"
+                REMOVE   "/file"
+               `},
+       }
+
+       for _, tt := range tests {
+               tt := tt
+               tt.run(t)
+       }
+}
+
+func TestWatchRename(t *testing.T) {
+       tests := []testCase{
+               {"rename file", func(t *testing.T, w *Watcher, tmp string) {
+                       file := filepath.Join(tmp, "file")
+
+                       addWatch(t, w, tmp)
+                       cat(t, "asd", file)
+                       mv(t, file, tmp, "renamed")
+               }, `
+                       create /file
+                       write  /file
+                       rename /file
+                       create /renamed
+               `},
+
+               {"rename from unwatched directory", func(t *testing.T, w *Watcher, tmp string) {
+                       unwatched := t.TempDir()
+
+                       addWatch(t, w, tmp)
+                       touch(t, unwatched, "file")
+                       mv(t, filepath.Join(unwatched, "file"), tmp, "file")
+               }, `
+                       create /file
+               `},
+
+               {"rename to unwatched directory", func(t *testing.T, w *Watcher, tmp string) {
+                       if runtime.GOOS == "netbsd" && isCI() {
+                               t.Skip("fails in CI; see #488")
+                       }
+
+                       unwatched := t.TempDir()
+                       file := filepath.Join(tmp, "file")
+                       renamed := filepath.Join(unwatched, "renamed")
+
+                       addWatch(t, w, tmp)
+
+                       cat(t, "data", file)
+                       mv(t, file, renamed)
+                       cat(t, "data", renamed) // Modify the file outside of the watched dir
+                       touch(t, file)          // Recreate the file that was moved
+               }, `
+                       create /file # cat data >file
+                       write  /file # ^
+                       rename /file # mv file ../renamed
+                       create /file # touch file
+
+                       # Windows has REMOVE /file, rather than CREATE /file
+                       windows:
+                               create   /file
+                               write    /file
+                               remove   /file
+                               create   /file
+               `},
+
+               {"rename overwriting existing file", func(t *testing.T, w *Watcher, tmp string) {
+                       touch(t, tmp, "renamed")
+                       addWatch(t, w, tmp)
+
+                       unwatched := t.TempDir()
+                       file := filepath.Join(unwatched, "file")
+                       touch(t, file)
+                       mv(t, file, tmp, "renamed")
+               }, `
+                       remove /renamed
+                       create /renamed
+
+                       # No remove event for inotify; inotify just sends MOVE_SELF.
+                       linux:
+                               create /renamed
+               `},
+
+               {"rename watched directory", func(t *testing.T, w *Watcher, tmp string) {
+                       addWatch(t, w, tmp)
+
+                       dir := filepath.Join(tmp, "dir")
+                       mkdir(t, dir)
+                       addWatch(t, w, dir)
+
+                       mv(t, dir, tmp, "dir-renamed")
+                       touch(t, tmp, "dir-renamed/file")
+               }, `
+                       CREATE   "/dir"           # mkdir
+                       RENAME   "/dir"           # mv
+                       CREATE   "/dir-renamed"
+                       RENAME   "/dir"
+                       CREATE   "/dir/file"      # touch
+
+                       windows:
+                               CREATE       "/dir"                 # mkdir
+                               RENAME       "/dir"                 # mv
+                               CREATE       "/dir-renamed"
+                               CREATE       "/dir-renamed/file"    # touch
+
+                       # TODO: no results for the touch; this is probably a bug; windows
+                       # was fixed in #370.
+                       kqueue:
+                               CREATE               "/dir"           # mkdir
+                               CREATE               "/dir-renamed"   # mv
+                               REMOVE|RENAME        "/dir"
+               `},
+       }
+
+       for _, tt := range tests {
+               tt := tt
+               tt.run(t)
+       }
+}
+
+func TestWatchSymlink(t *testing.T) {
+       tests := []testCase{
+               {"create unresolvable symlink", func(t *testing.T, w *Watcher, tmp string) {
+                       addWatch(t, w, tmp)
+
+                       symlink(t, filepath.Join(tmp, "target"), tmp, "link")
+               }, `
+                       create /link
+
+                       windows:
+                create    /link
+                write     /link
+               `},
+
+               {"cyclic symlink", func(t *testing.T, w *Watcher, tmp string) {
+                       if runtime.GOOS == "darwin" {
+                               // This test is borked on macOS; it reports events outside the
+                               // watched directory:
+                               //
+                               //   create "/private/.../testwatchsymlinkcyclic_symlink3681444267/001/link"
+                               //   create "/link"
+                               //   write  "/link"
+                               //   write  "/private/.../testwatchsymlinkcyclic_symlink3681444267/001/link"
+                               //
+                               // kqueue.go does a lot of weird things with symlinks that I
+                               // don't think are necessarily correct, but need to test a bit
+                               // more.
+                               t.Skip()
+                       }
+
+                       symlink(t, ".", tmp, "link")
+                       addWatch(t, w, tmp)
+                       rm(t, tmp, "link")
+                       cat(t, "foo", tmp, "link")
+
+               }, `
+                       write  /link
+                       create /link
+
+                       linux, windows:
+                               remove    /link
+                               create    /link
+                               write     /link
+               `},
+       }
+
+       for _, tt := range tests {
+               tt := tt
+               tt.run(t)
+       }
+}
+
+func TestWatchAttrib(t *testing.T) {
+       if runtime.GOOS == "windows" {
+               t.Skip("attributes don't work on Windows")
+       }
+
+       tests := []testCase{
+               {"chmod", func(t *testing.T, w *Watcher, tmp string) {
+                       file := filepath.Join(tmp, "file")
+
+                       cat(t, "data", file)
+                       addWatch(t, w, file)
+                       chmod(t, 0o700, file)
+               }, `
+                       CHMOD   "/file"
+               `},
+
+               {"write does not trigger CHMOD", func(t *testing.T, w *Watcher, tmp string) {
+                       file := filepath.Join(tmp, "file")
+
+                       cat(t, "data", file)
+                       addWatch(t, w, file)
+                       chmod(t, 0o700, file)
+
+                       cat(t, "more data", file)
+               }, `
+                       CHMOD   "/file"
+                       WRITE   "/file"
+               `},
+
+               {"chmod after write", func(t *testing.T, w *Watcher, tmp string) {
+                       file := filepath.Join(tmp, "file")
+
+                       cat(t, "data", file)
+                       addWatch(t, w, file)
+                       chmod(t, 0o700, file)
+                       cat(t, "more data", file)
+                       chmod(t, 0o600, file)
+               }, `
+                       CHMOD   "/file"
+                       WRITE   "/file"
+                       CHMOD   "/file"
+               `},
+       }
+
+       for _, tt := range tests {
+               tt := tt
+               tt.run(t)
+       }
+}
+
+func TestWatchRm(t *testing.T) {
+       tests := []testCase{
+               {"remove watched directory", func(t *testing.T, w *Watcher, tmp string) {
+                       if runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd" {
+                               t.Skip("behaviour is inconsistent on OpenBSD and NetBSD, and this test is flaky")
+                       }
+
+                       file := filepath.Join(tmp, "file")
+
+                       touch(t, file)
+                       addWatch(t, w, tmp)
+                       rmAll(t, tmp)
+               }, `
+                       # OpenBSD, NetBSD
+                       remove             /file
+                       remove|write       /
+
+                       freebsd:
+                               remove|write   "/"
+                               remove         ""
+                               create         "."
+
+                       darwin:
+                               remove         /file
+                               remove|write   /
+                       linux:
+                               remove         /file
+                               remove         /
+                       windows:
+                               remove         /file
+                               remove         /
+               `},
+       }
+
+       for _, tt := range tests {
+               tt := tt
+               tt.run(t)
+       }
+}
+
+func TestClose(t *testing.T) {
+       t.Run("close", func(t *testing.T) {
+               t.Parallel()
+
+               w := newWatcher(t)
+               if err := w.Close(); err != nil {
+                       t.Fatal(err)
+               }
+
+               var done int32
+               go func() {
+                       w.Close()
+                       atomic.StoreInt32(&done, 1)
+               }()
+
+               eventSeparator()
+               if atomic.LoadInt32(&done) == 0 {
+                       t.Fatal("double Close() test failed: second Close() call didn't return")
+               }
+
+               if err := w.Add(t.TempDir()); err == nil {
+                       t.Fatal("expected error on Watch() after Close(), got nil")
+               }
+       })
+
+       // Make sure that Close() works even when the Events channel isn't being
+       // read.
+       t.Run("events not read", func(t *testing.T) {
+               t.Parallel()
+
+               tmp := t.TempDir()
+               w := newWatcher(t, tmp)
+
+               touch(t, tmp, "file")
+               rm(t, tmp, "file")
+               if err := w.Close(); err != nil {
+                       t.Fatal(err)
+               }
+       })
+
+       // Make sure that calling Close() while REMOVE events are emitted doesn't race.
+       t.Run("close while removing files", func(t *testing.T) {
+               t.Parallel()
+               tmp := t.TempDir()
+
+               files := make([]string, 0, 200)
+               for i := 0; i < 200; i++ {
+                       f := filepath.Join(tmp, fmt.Sprintf("file-%03d", i))
+                       touch(t, f, noWait)
+                       files = append(files, f)
+               }
+
+               w := newWatcher(t, tmp)
+
+               startC, stopC, errC := make(chan struct{}), make(chan struct{}), make(chan error)
+               go func() {
+                       for {
+                               select {
+                               case <-w.Errors:
+                               case <-w.Events:
+                               case <-stopC:
+                                       return
+                               }
+                       }
+               }()
+               rmDone := make(chan struct{})
+               go func() {
+                       <-startC
+                       for _, f := range files {
+                               rm(t, f, noWait)
+                       }
+                       rmDone <- struct{}{}
+               }()
+               go func() {
+                       <-startC
+                       errC <- w.Close()
+               }()
+               close(startC)
+               defer close(stopC)
+               if err := <-errC; err != nil {
+                       t.Fatal(err)
+               }
+
+               <-rmDone
+       })
+
+       // Make sure Close() doesn't race when called more than once; hard to write
+       // a good reproducible test for this, but running it 150 times seems to
+       // reproduce it in ~75% of cases and isn't too slow (~0.06s on my system).
+       t.Run("double close", func(t *testing.T) {
+               t.Parallel()
+
+               for i := 0; i < 150; i++ {
+                       w, err := NewWatcher()
+                       if err != nil {
+                               if strings.Contains(err.Error(), "too many") { // syscall.EMFILE
+                                       time.Sleep(100 * time.Millisecond)
+                                       continue
+                               }
+                               t.Fatal(err)
+                       }
+                       go w.Close()
+                       go w.Close()
+                       go w.Close()
+               }
+       })
+}
+
+func TestAdd(t *testing.T) {
+       t.Run("permission denied", func(t *testing.T) {
+               if runtime.GOOS == "windows" {
+                       t.Skip("attributes don't work on Windows")
+               }
+
+               t.Parallel()
+
+               tmp := t.TempDir()
+               dir := filepath.Join(tmp, "dir-unreadable")
+               mkdir(t, dir)
+               touch(t, dir, "/file")
+               chmod(t, 0, dir)
+
+               w := newWatcher(t)
+               defer func() {
+                       w.Close()
+                       chmod(t, 0o755, dir) // Make TempDir() cleanup work
+               }()
+               err := w.Add(dir)
+               if err == nil {
+                       t.Fatal("error is nil")
+               }
+               if !errors.Is(err, internal.UnixEACCES) {
+                       t.Errorf("not unix.EACCESS: %T %#[1]v", err)
+               }
+               if !errors.Is(err, internal.SyscallEACCES) {
+                       t.Errorf("not syscall.EACCESS: %T %#[1]v", err)
+               }
+       })
+}
+
+// TODO: should also check internal state is correct/cleaned up; e.g. no
+//       left-over file descriptors or whatnot.
+func TestRemove(t *testing.T) {
+       t.Run("works", func(t *testing.T) {
+               t.Parallel()
+
+               tmp := t.TempDir()
+               touch(t, tmp, "file")
+
+               w := newCollector(t)
+               w.collect(t)
+               addWatch(t, w.w, tmp)
+               if err := w.w.Remove(tmp); err != nil {
+                       t.Fatal(err)
+               }
+
+               time.Sleep(200 * time.Millisecond)
+               cat(t, "data", tmp, "file")
+               chmod(t, 0o700, tmp, "file")
+
+               have := w.stop(t)
+               if len(have) > 0 {
+                       t.Errorf("received events; expected none:\n%s", have)
+               }
+       })
+
+       t.Run("remove same dir twice", func(t *testing.T) {
+               tmp := t.TempDir()
+
+               touch(t, tmp, "file")
+
+               w := newWatcher(t)
+               defer w.Close()
+
+               addWatch(t, w, tmp)
+
+               if err := w.Remove(tmp); err != nil {
+                       t.Fatal(err)
+               }
+               if err := w.Remove(tmp); err == nil {
+                       t.Fatal("no error")
+               }
+       })
+
+       // Make sure that concurrent calls to Remove() don't race.
+       t.Run("no race", func(t *testing.T) {
+               t.Parallel()
+
+               tmp := t.TempDir()
+               touch(t, tmp, "file")
+
+               for i := 0; i < 10; i++ {
+                       w := newWatcher(t)
+                       defer w.Close()
+                       addWatch(t, w, tmp)
+
+                       done := make(chan struct{})
+                       go func() {
+                               defer func() { done <- struct{}{} }()
+                               w.Remove(tmp)
+                       }()
+                       go func() {
+                               defer func() { done <- struct{}{} }()
+                               w.Remove(tmp)
+                       }()
+                       <-done
+                       <-done
+                       w.Close()
+               }
+       })
+}
+
 func TestEventString(t *testing.T) {
        tests := []struct {
                in   Event
diff --git a/integration_test.go b/integration_test.go
deleted file mode 100644 (file)
index da9bb20..0000000
+++ /dev/null
@@ -1,578 +0,0 @@
-//go:build !plan9 && !solaris
-// +build !plan9,!solaris
-
-package fsnotify
-
-import (
-       "errors"
-       "fmt"
-       "path/filepath"
-       "runtime"
-       "strings"
-       "sync/atomic"
-       "testing"
-       "time"
-
-       "github.com/fsnotify/fsnotify/internal"
-)
-
-func TestWatch(t *testing.T) {
-       tests := []testCase{
-               {"multiple creates", func(t *testing.T, w *Watcher, tmp string) {
-                       file := filepath.Join(tmp, "file")
-                       addWatch(t, w, tmp)
-
-                       cat(t, "data", file)
-                       rm(t, file)
-
-                       touch(t, file)       // Recreate the file
-                       cat(t, "data", file) // Modify
-                       cat(t, "data", file) // Modify
-               }, `
-                       create  /file
-                       write   /file
-                       remove  /file
-                       create  /file
-                       write   /file
-                       write   /file
-               `},
-
-               {"dir only", func(t *testing.T, w *Watcher, tmp string) {
-                       beforeWatch := filepath.Join(tmp, "beforewatch")
-                       file := filepath.Join(tmp, "file")
-
-                       touch(t, beforeWatch)
-                       addWatch(t, w, tmp)
-
-                       cat(t, "data", file)
-                       rm(t, file)
-                       rm(t, beforeWatch)
-               }, `
-                       create /file
-                       write  /file
-                       remove /file
-                       remove /beforewatch
-               `},
-
-               {"subdir", func(t *testing.T, w *Watcher, tmp string) {
-                       addWatch(t, w, tmp)
-
-                       file := filepath.Join(tmp, "file")
-                       dir := filepath.Join(tmp, "sub")
-                       dirfile := filepath.Join(tmp, "sub/file2")
-
-                       mkdir(t, dir)     // Create sub-directory
-                       touch(t, file)    // Create a file
-                       touch(t, dirfile) // Create a file (Should not see this! we are not watching subdir)
-                       time.Sleep(200 * time.Millisecond)
-                       rmAll(t, dir) // Make sure receive deletes for both file and sub-directory
-                       rm(t, file)
-               }, `
-                       create /sub
-                       create /file
-                       remove /sub
-                       remove /file
-
-                       # Windows includes a write for the /sub dir too, two of them even(?)
-                       windows:
-                               create /sub
-                               create /file
-                               write  /sub
-                               write  /sub
-                               remove /sub
-                               remove /file
-               `},
-
-               {"file in directory is not readable", func(t *testing.T, w *Watcher, tmp string) {
-                       if runtime.GOOS == "windows" {
-                               t.Skip("attributes don't work on Windows")
-                       }
-
-                       touch(t, tmp, "file-unreadable")
-                       chmod(t, 0, tmp, "file-unreadable")
-                       touch(t, tmp, "file")
-                       addWatch(t, w, tmp)
-
-                       cat(t, "hello", tmp, "file")
-                       rm(t, tmp, "file")
-                       rm(t, tmp, "file-unreadable")
-               }, `
-                       WRITE     "/file"
-                       REMOVE    "/file"
-                       REMOVE    "/file-unreadable"
-
-                       # We never set up a watcher on the unreadable file, so we don't get
-                       # the REMOVE.
-                       kqueue:
-                WRITE    "/file"
-                REMOVE   "/file"
-               `},
-       }
-
-       for _, tt := range tests {
-               tt := tt
-               tt.run(t)
-       }
-}
-
-func TestWatchRename(t *testing.T) {
-       tests := []testCase{
-               {"rename file", func(t *testing.T, w *Watcher, tmp string) {
-                       file := filepath.Join(tmp, "file")
-
-                       addWatch(t, w, tmp)
-                       cat(t, "asd", file)
-                       mv(t, file, tmp, "renamed")
-               }, `
-                       create /file
-                       write  /file
-                       rename /file
-                       create /renamed
-               `},
-
-               {"rename from unwatched directory", func(t *testing.T, w *Watcher, tmp string) {
-                       unwatched := t.TempDir()
-
-                       addWatch(t, w, tmp)
-                       touch(t, unwatched, "file")
-                       mv(t, filepath.Join(unwatched, "file"), tmp, "file")
-               }, `
-                       create /file
-               `},
-
-               {"rename to unwatched directory", func(t *testing.T, w *Watcher, tmp string) {
-                       if runtime.GOOS == "netbsd" && isCI() {
-                               t.Skip("fails in CI; see #488")
-                       }
-
-                       unwatched := t.TempDir()
-                       file := filepath.Join(tmp, "file")
-                       renamed := filepath.Join(unwatched, "renamed")
-
-                       addWatch(t, w, tmp)
-
-                       cat(t, "data", file)
-                       mv(t, file, renamed)
-                       cat(t, "data", renamed) // Modify the file outside of the watched dir
-                       touch(t, file)          // Recreate the file that was moved
-               }, `
-                       create /file # cat data >file
-                       write  /file # ^
-                       rename /file # mv file ../renamed
-                       create /file # touch file
-
-                       # Windows has REMOVE /file, rather than CREATE /file
-                       windows:
-                               create   /file
-                               write    /file
-                               remove   /file
-                               create   /file
-               `},
-
-               {"rename overwriting existing file", func(t *testing.T, w *Watcher, tmp string) {
-                       touch(t, tmp, "renamed")
-                       addWatch(t, w, tmp)
-
-                       unwatched := t.TempDir()
-                       file := filepath.Join(unwatched, "file")
-                       touch(t, file)
-                       mv(t, file, tmp, "renamed")
-               }, `
-                       remove /renamed
-                       create /renamed
-
-                       # No remove event for inotify; inotify just sends MOVE_SELF.
-                       linux:
-                               create /renamed
-               `},
-
-               {"rename watched directory", func(t *testing.T, w *Watcher, tmp string) {
-                       addWatch(t, w, tmp)
-
-                       dir := filepath.Join(tmp, "dir")
-                       mkdir(t, dir)
-                       addWatch(t, w, dir)
-
-                       mv(t, dir, tmp, "dir-renamed")
-                       touch(t, tmp, "dir-renamed/file")
-               }, `
-                       CREATE   "/dir"           # mkdir
-                       RENAME   "/dir"           # mv
-                       CREATE   "/dir-renamed"
-                       RENAME   "/dir"
-                       CREATE   "/dir/file"      # touch
-
-                       windows:
-                               CREATE       "/dir"                 # mkdir
-                               RENAME       "/dir"                 # mv
-                               CREATE       "/dir-renamed"
-                               CREATE       "/dir-renamed/file"    # touch
-
-                       # TODO: no results for the touch; this is probably a bug; windows
-                       # was fixed in #370.
-                       kqueue:
-                               CREATE               "/dir"           # mkdir
-                               CREATE               "/dir-renamed"   # mv
-                               REMOVE|RENAME        "/dir"
-               `},
-       }
-
-       for _, tt := range tests {
-               tt := tt
-               tt.run(t)
-       }
-}
-
-func TestWatchSymlink(t *testing.T) {
-       tests := []testCase{
-               {"create unresolvable symlink", func(t *testing.T, w *Watcher, tmp string) {
-                       addWatch(t, w, tmp)
-
-                       symlink(t, filepath.Join(tmp, "target"), tmp, "link")
-               }, `
-                       create /link
-
-                       windows:
-                create    /link
-                write     /link
-               `},
-
-               {"cyclic symlink", func(t *testing.T, w *Watcher, tmp string) {
-                       if runtime.GOOS == "darwin" {
-                               // This test is borked on macOS; it reports events outside the
-                               // watched directory:
-                               //
-                               //   create "/private/.../testwatchsymlinkcyclic_symlink3681444267/001/link"
-                               //   create "/link"
-                               //   write  "/link"
-                               //   write  "/private/.../testwatchsymlinkcyclic_symlink3681444267/001/link"
-                               //
-                               // kqueue.go does a lot of weird things with symlinks that I
-                               // don't think are necessarily correct, but need to test a bit
-                               // more.
-                               t.Skip()
-                       }
-
-                       symlink(t, ".", tmp, "link")
-                       addWatch(t, w, tmp)
-                       rm(t, tmp, "link")
-                       cat(t, "foo", tmp, "link")
-
-               }, `
-                       write  /link
-                       create /link
-
-                       linux, windows:
-                               remove    /link
-                               create    /link
-                               write     /link
-               `},
-       }
-
-       for _, tt := range tests {
-               tt := tt
-               tt.run(t)
-       }
-}
-
-func TestWatchAttrib(t *testing.T) {
-       if runtime.GOOS == "windows" {
-               t.Skip("attributes don't work on Windows")
-       }
-
-       tests := []testCase{
-               {"chmod", func(t *testing.T, w *Watcher, tmp string) {
-                       file := filepath.Join(tmp, "file")
-
-                       cat(t, "data", file)
-                       addWatch(t, w, file)
-                       chmod(t, 0o700, file)
-               }, `
-                       CHMOD   "/file"
-               `},
-
-               {"write does not trigger CHMOD", func(t *testing.T, w *Watcher, tmp string) {
-                       file := filepath.Join(tmp, "file")
-
-                       cat(t, "data", file)
-                       addWatch(t, w, file)
-                       chmod(t, 0o700, file)
-
-                       cat(t, "more data", file)
-               }, `
-                       CHMOD   "/file"
-                       WRITE   "/file"
-               `},
-
-               {"chmod after write", func(t *testing.T, w *Watcher, tmp string) {
-                       file := filepath.Join(tmp, "file")
-
-                       cat(t, "data", file)
-                       addWatch(t, w, file)
-                       chmod(t, 0o700, file)
-                       cat(t, "more data", file)
-                       chmod(t, 0o600, file)
-               }, `
-                       CHMOD   "/file"
-                       WRITE   "/file"
-                       CHMOD   "/file"
-               `},
-       }
-
-       for _, tt := range tests {
-               tt := tt
-               tt.run(t)
-       }
-}
-
-func TestWatchRm(t *testing.T) {
-       tests := []testCase{
-               {"remove watched directory", func(t *testing.T, w *Watcher, tmp string) {
-                       if runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd" {
-                               t.Skip("behaviour is inconsistent on OpenBSD and NetBSD, and this test is flaky")
-                       }
-
-                       file := filepath.Join(tmp, "file")
-
-                       touch(t, file)
-                       addWatch(t, w, tmp)
-                       rmAll(t, tmp)
-               }, `
-                       # OpenBSD, NetBSD
-                       remove             /file
-                       remove|write       /
-
-                       freebsd:
-                               remove|write   "/"
-                               remove         ""
-                               create         "."
-
-                       darwin:
-                               remove         /file
-                               remove|write   /
-                       linux:
-                               remove         /file
-                               remove         /
-                       windows:
-                               remove         /file
-                               remove         /
-               `},
-       }
-
-       for _, tt := range tests {
-               tt := tt
-               tt.run(t)
-       }
-}
-
-func TestClose(t *testing.T) {
-       t.Run("close", func(t *testing.T) {
-               t.Parallel()
-
-               w := newWatcher(t)
-               if err := w.Close(); err != nil {
-                       t.Fatal(err)
-               }
-
-               var done int32
-               go func() {
-                       w.Close()
-                       atomic.StoreInt32(&done, 1)
-               }()
-
-               eventSeparator()
-               if atomic.LoadInt32(&done) == 0 {
-                       t.Fatal("double Close() test failed: second Close() call didn't return")
-               }
-
-               if err := w.Add(t.TempDir()); err == nil {
-                       t.Fatal("expected error on Watch() after Close(), got nil")
-               }
-       })
-
-       // Make sure that Close() works even when the Events channel isn't being
-       // read.
-       t.Run("events not read", func(t *testing.T) {
-               t.Parallel()
-
-               tmp := t.TempDir()
-               w := newWatcher(t, tmp)
-
-               touch(t, tmp, "file")
-               rm(t, tmp, "file")
-               if err := w.Close(); err != nil {
-                       t.Fatal(err)
-               }
-       })
-
-       // Make sure that calling Close() while REMOVE events are emitted doesn't race.
-       t.Run("close while removing files", func(t *testing.T) {
-               t.Parallel()
-               tmp := t.TempDir()
-
-               files := make([]string, 0, 200)
-               for i := 0; i < 200; i++ {
-                       f := filepath.Join(tmp, fmt.Sprintf("file-%03d", i))
-                       touch(t, f, noWait)
-                       files = append(files, f)
-               }
-
-               w := newWatcher(t, tmp)
-
-               startC, stopC, errC := make(chan struct{}), make(chan struct{}), make(chan error)
-               go func() {
-                       for {
-                               select {
-                               case <-w.Errors:
-                               case <-w.Events:
-                               case <-stopC:
-                                       return
-                               }
-                       }
-               }()
-               rmDone := make(chan struct{})
-               go func() {
-                       <-startC
-                       for _, f := range files {
-                               rm(t, f, noWait)
-                       }
-                       rmDone <- struct{}{}
-               }()
-               go func() {
-                       <-startC
-                       errC <- w.Close()
-               }()
-               close(startC)
-               defer close(stopC)
-               if err := <-errC; err != nil {
-                       t.Fatal(err)
-               }
-
-               <-rmDone
-       })
-
-       // Make sure Close() doesn't race when called more than once; hard to write
-       // a good reproducible test for this, but running it 150 times seems to
-       // reproduce it in ~75% of cases and isn't too slow (~0.06s on my system).
-       t.Run("double close", func(t *testing.T) {
-               t.Parallel()
-
-               for i := 0; i < 150; i++ {
-                       w, err := NewWatcher()
-                       if err != nil {
-                               if strings.Contains(err.Error(), "too many") { // syscall.EMFILE
-                                       time.Sleep(100 * time.Millisecond)
-                                       continue
-                               }
-                               t.Fatal(err)
-                       }
-                       go w.Close()
-                       go w.Close()
-                       go w.Close()
-               }
-       })
-}
-
-func TestAdd(t *testing.T) {
-       t.Run("permission denied", func(t *testing.T) {
-               if runtime.GOOS == "windows" {
-                       t.Skip("attributes don't work on Windows")
-               }
-
-               t.Parallel()
-
-               tmp := t.TempDir()
-               dir := filepath.Join(tmp, "dir-unreadable")
-               mkdir(t, dir)
-               touch(t, dir, "/file")
-               chmod(t, 0, dir)
-
-               w := newWatcher(t)
-               defer func() {
-                       w.Close()
-                       chmod(t, 0o755, dir) // Make TempDir() cleanup work
-               }()
-               err := w.Add(dir)
-               if err == nil {
-                       t.Fatal("error is nil")
-               }
-               if !errors.Is(err, internal.UnixEACCES) {
-                       t.Errorf("not unix.EACCESS: %T %#[1]v", err)
-               }
-               if !errors.Is(err, internal.SyscallEACCES) {
-                       t.Errorf("not syscall.EACCESS: %T %#[1]v", err)
-               }
-       })
-}
-
-// TODO: should also check internal state is correct/cleaned up; e.g. no
-//       left-over file descriptors or whatnot.
-func TestRemove(t *testing.T) {
-       t.Run("works", func(t *testing.T) {
-               t.Parallel()
-
-               tmp := t.TempDir()
-               touch(t, tmp, "file")
-
-               w := newCollector(t)
-               w.collect(t)
-               addWatch(t, w.w, tmp)
-               if err := w.w.Remove(tmp); err != nil {
-                       t.Fatal(err)
-               }
-
-               time.Sleep(200 * time.Millisecond)
-               cat(t, "data", tmp, "file")
-               chmod(t, 0o700, tmp, "file")
-
-               have := w.stop(t)
-               if len(have) > 0 {
-                       t.Errorf("received events; expected none:\n%s", have)
-               }
-       })
-
-       t.Run("remove same dir twice", func(t *testing.T) {
-               tmp := t.TempDir()
-
-               touch(t, tmp, "file")
-
-               w := newWatcher(t)
-               defer w.Close()
-
-               addWatch(t, w, tmp)
-
-               if err := w.Remove(tmp); err != nil {
-                       t.Fatal(err)
-               }
-               if err := w.Remove(tmp); err == nil {
-                       t.Fatal("no error")
-               }
-       })
-
-       // Make sure that concurrent calls to Remove() don't race.
-       t.Run("no race", func(t *testing.T) {
-               t.Parallel()
-
-               tmp := t.TempDir()
-               touch(t, tmp, "file")
-
-               for i := 0; i < 10; i++ {
-                       w := newWatcher(t)
-                       defer w.Close()
-                       addWatch(t, w, tmp)
-
-                       done := make(chan struct{})
-                       go func() {
-                               defer func() { done <- struct{}{} }()
-                               w.Remove(tmp)
-                       }()
-                       go func() {
-                               defer func() { done <- struct{}{} }()
-                               w.Remove(tmp)
-                       }()
-                       <-done
-                       <-done
-                       w.Close()
-               }
-       })
-}
similarity index 100%
rename from open_mode_bsd.go
rename to system_bsd.go
similarity index 100%
rename from open_mode_darwin.go
rename to system_darwin.go
similarity index 98%
rename from integration_darwin_test.go
rename to system_darwin_test.go
index a5dfb08c32bfbddc97ad890d2a45a98e5b90004f..48cabe915e5be427e0a84ed153418e9aca88e66d 100644 (file)
@@ -1,3 +1,6 @@
+//go:build darwin
+// +build darwin
+
 package fsnotify
 
 import (