Skip to content

Commit

Permalink
Document and test removing watched directory on Windows
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
arp242 committed Oct 19, 2023
1 parent 2f2332a commit c86f21c
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 60 deletions.
5 changes: 5 additions & 0 deletions backend_fen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions backend_inotify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions backend_kqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions backend_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions backend_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
120 changes: 60 additions & 60 deletions fsnotify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions mkdoc.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ watcher=$(<<EOF
// 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
Expand Down

0 comments on commit c86f21c

Please sign in to comment.