From c86f21c72aa1c788735bce93fced3754a30e658b Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 19 Oct 2023 02:41:00 +0100 Subject: [PATCH] Document and test removing watched directory on Windows On Windows deleting the watched directive gives undefined results and delivering events for the files in the directory is not guaranteed. I can reproduce this even with a very simple example in Python, and it's not an fsnotify bug as far as I can see. There's nothing we can do about this, so update the documentation and tweak the test so we don't get intermittent test failures. On other platforms it does report all files. --- backend_fen.go | 5 ++ backend_inotify.go | 5 ++ backend_kqueue.go | 5 ++ backend_other.go | 5 ++ backend_windows.go | 5 ++ fsnotify_test.go | 120 ++++++++++++++++++++++----------------------- mkdoc.zsh | 5 ++ 7 files changed, 90 insertions(+), 60 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index 3e9bab9a..cd07888a 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -72,6 +72,11 @@ import ( // Paths can be added as "C:\path\to\dir", but forward slashes // ("C:/path/to/dir") will also work. // +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// // The default buffer size is 64K, which is the largest value that is guaranteed // to work with SMB filesystems. If you have many events in quick succession // this may not be enough, and you will have to use [WithBufferSize] to increase diff --git a/backend_inotify.go b/backend_inotify.go index 09108c27..c7c5be7d 100644 --- a/backend_inotify.go +++ b/backend_inotify.go @@ -75,6 +75,11 @@ import ( // Paths can be added as "C:\path\to\dir", but forward slashes // ("C:/path/to/dir") will also work. // +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// // The default buffer size is 64K, which is the largest value that is guaranteed // to work with SMB filesystems. If you have many events in quick succession // this may not be enough, and you will have to use [WithBufferSize] to increase diff --git a/backend_kqueue.go b/backend_kqueue.go index 90b2d99d..3354d2d8 100644 --- a/backend_kqueue.go +++ b/backend_kqueue.go @@ -72,6 +72,11 @@ import ( // Paths can be added as "C:\path\to\dir", but forward slashes // ("C:/path/to/dir") will also work. // +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// // The default buffer size is 64K, which is the largest value that is guaranteed // to work with SMB filesystems. If you have many events in quick succession // this may not be enough, and you will have to use [WithBufferSize] to increase diff --git a/backend_other.go b/backend_other.go index cebe002b..52056e1b 100644 --- a/backend_other.go +++ b/backend_other.go @@ -64,6 +64,11 @@ import "errors" // Paths can be added as "C:\path\to\dir", but forward slashes // ("C:/path/to/dir") will also work. // +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// // The default buffer size is 64K, which is the largest value that is guaranteed // to work with SMB filesystems. If you have many events in quick succession // this may not be enough, and you will have to use [WithBufferSize] to increase diff --git a/backend_windows.go b/backend_windows.go index 87c840a1..0d09ee77 100644 --- a/backend_windows.go +++ b/backend_windows.go @@ -80,6 +80,11 @@ import ( // Paths can be added as "C:\path\to\dir", but forward slashes // ("C:/path/to/dir") will also work. // +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// // The default buffer size is 64K, which is the largest value that is guaranteed // to work with SMB filesystems. If you have many events in quick succession // this may not be enough, and you will have to use [WithBufferSize] to increase diff --git a/fsnotify_test.go b/fsnotify_test.go index 13456c46..2c8c66f7 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -680,66 +680,6 @@ func TestWatchRemove(t *testing.T) { CHMOD "/file" `}, - {"remove watched directory", func(t *testing.T, w *Watcher, tmp string) { - touch(t, tmp, "a") - touch(t, tmp, "b") - touch(t, tmp, "c") - touch(t, tmp, "d") - touch(t, tmp, "e") - touch(t, tmp, "f") - touch(t, tmp, "g") - mkdir(t, tmp, "h") - mkdir(t, tmp, "h", "a") - mkdir(t, tmp, "i") - mkdir(t, tmp, "i", "a") - mkdir(t, tmp, "j") - mkdir(t, tmp, "j", "a") - addWatch(t, w, tmp) - rmAll(t, tmp) - }, ` - remove / - remove /a - remove /b - remove /c - remove /d - remove /e - remove /f - remove /g - remove /h - remove /i - remove /j - - # TODO: this is broken; I've also seen (/i and /j missing): - # REMOVE "/" - # REMOVE "/a" - # REMOVE "/b" - # REMOVE "/c" - # REMOVE "/d" - # REMOVE "/e" - # REMOVE "/f" - # REMOVE "/g" - # WRITE "/h" - # WRITE "/h" - windows: - REMOVE "/" - REMOVE "/a" - REMOVE "/b" - REMOVE "/c" - REMOVE "/d" - REMOVE "/e" - REMOVE "/f" - REMOVE "/g" - REMOVE "/h" - REMOVE "/i" - REMOVE "/j" - WRITE "/h" - WRITE "/h" - WRITE "/i" - WRITE "/i" - WRITE "/j" - WRITE "/j" - `}, - {"remove recursive", func(t *testing.T, w *Watcher, tmp string) { recurseOnly(t) @@ -778,6 +718,66 @@ func TestWatchRemove(t *testing.T) { tt := tt tt.run(t) } + + t.Run("remove watched directory", func(t *testing.T) { + t.Parallel() + tmp := t.TempDir() + + w := newCollector(t) + w.collect(t) + + touch(t, tmp, "a") + touch(t, tmp, "b") + touch(t, tmp, "c") + touch(t, tmp, "d") + touch(t, tmp, "e") + touch(t, tmp, "f") + touch(t, tmp, "g") + mkdir(t, tmp, "h") + mkdir(t, tmp, "h", "a") + mkdir(t, tmp, "i") + mkdir(t, tmp, "i", "a") + mkdir(t, tmp, "j") + mkdir(t, tmp, "j", "a") + addWatch(t, w.w, tmp) + rmAll(t, tmp) + + if runtime.GOOS != "windows" { + cmpEvents(t, tmp, w.stop(t), newEvents(t, ` + remove / + remove /a + remove /b + remove /c + remove /d + remove /e + remove /f + remove /g + remove /h + remove /i + remove /j`)) + return + } + + // ReadDirectoryChangesW gives undefined results: not all files are + // always present. So test only that 1) we got the directory itself, and + // 2) we don't get events for unspected files. + var ( + events = w.stop(t) + found bool + ) + for _, e := range events { + if e.Name == tmp && e.Has(Remove) { + found = true + continue + } + if filepath.Dir(e.Name) != tmp { + t.Errorf("unexpected event: %s", e) + } + } + if !found { + t.Fatalf("didn't see directory in:\n%s", events) + } + }) } func TestWatchRecursive(t *testing.T) { diff --git a/mkdoc.zsh b/mkdoc.zsh index ae80ff2a..5e3e501d 100755 --- a/mkdoc.zsh +++ b/mkdoc.zsh @@ -62,6 +62,11 @@ watcher=$(<