Skip to content

Commit c86f21c

Browse files
committed
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.
1 parent 2f2332a commit c86f21c

7 files changed

+90
-60
lines changed

backend_fen.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ import (
7272
// Paths can be added as "C:\path\to\dir", but forward slashes
7373
// ("C:/path/to/dir") will also work.
7474
//
75+
// When a watched directory is removed it will always send an event for the
76+
// directory itself, but may not send events for all files in that directory.
77+
// Sometimes it will send events for all times, sometimes it will send no
78+
// events, and often only for some files.
79+
//
7580
// The default buffer size is 64K, which is the largest value that is guaranteed
7681
// to work with SMB filesystems. If you have many events in quick succession
7782
// this may not be enough, and you will have to use [WithBufferSize] to increase

backend_inotify.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ import (
7575
// Paths can be added as "C:\path\to\dir", but forward slashes
7676
// ("C:/path/to/dir") will also work.
7777
//
78+
// When a watched directory is removed it will always send an event for the
79+
// directory itself, but may not send events for all files in that directory.
80+
// Sometimes it will send events for all times, sometimes it will send no
81+
// events, and often only for some files.
82+
//
7883
// The default buffer size is 64K, which is the largest value that is guaranteed
7984
// to work with SMB filesystems. If you have many events in quick succession
8085
// this may not be enough, and you will have to use [WithBufferSize] to increase

backend_kqueue.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ import (
7272
// Paths can be added as "C:\path\to\dir", but forward slashes
7373
// ("C:/path/to/dir") will also work.
7474
//
75+
// When a watched directory is removed it will always send an event for the
76+
// directory itself, but may not send events for all files in that directory.
77+
// Sometimes it will send events for all times, sometimes it will send no
78+
// events, and often only for some files.
79+
//
7580
// The default buffer size is 64K, which is the largest value that is guaranteed
7681
// to work with SMB filesystems. If you have many events in quick succession
7782
// this may not be enough, and you will have to use [WithBufferSize] to increase

backend_other.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ import "errors"
6464
// Paths can be added as "C:\path\to\dir", but forward slashes
6565
// ("C:/path/to/dir") will also work.
6666
//
67+
// When a watched directory is removed it will always send an event for the
68+
// directory itself, but may not send events for all files in that directory.
69+
// Sometimes it will send events for all times, sometimes it will send no
70+
// events, and often only for some files.
71+
//
6772
// The default buffer size is 64K, which is the largest value that is guaranteed
6873
// to work with SMB filesystems. If you have many events in quick succession
6974
// this may not be enough, and you will have to use [WithBufferSize] to increase

backend_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ import (
8080
// Paths can be added as "C:\path\to\dir", but forward slashes
8181
// ("C:/path/to/dir") will also work.
8282
//
83+
// When a watched directory is removed it will always send an event for the
84+
// directory itself, but may not send events for all files in that directory.
85+
// Sometimes it will send events for all times, sometimes it will send no
86+
// events, and often only for some files.
87+
//
8388
// The default buffer size is 64K, which is the largest value that is guaranteed
8489
// to work with SMB filesystems. If you have many events in quick succession
8590
// this may not be enough, and you will have to use [WithBufferSize] to increase

fsnotify_test.go

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -680,66 +680,6 @@ func TestWatchRemove(t *testing.T) {
680680
CHMOD "/file"
681681
`},
682682

683-
{"remove watched directory", func(t *testing.T, w *Watcher, tmp string) {
684-
touch(t, tmp, "a")
685-
touch(t, tmp, "b")
686-
touch(t, tmp, "c")
687-
touch(t, tmp, "d")
688-
touch(t, tmp, "e")
689-
touch(t, tmp, "f")
690-
touch(t, tmp, "g")
691-
mkdir(t, tmp, "h")
692-
mkdir(t, tmp, "h", "a")
693-
mkdir(t, tmp, "i")
694-
mkdir(t, tmp, "i", "a")
695-
mkdir(t, tmp, "j")
696-
mkdir(t, tmp, "j", "a")
697-
addWatch(t, w, tmp)
698-
rmAll(t, tmp)
699-
}, `
700-
remove /
701-
remove /a
702-
remove /b
703-
remove /c
704-
remove /d
705-
remove /e
706-
remove /f
707-
remove /g
708-
remove /h
709-
remove /i
710-
remove /j
711-
712-
# TODO: this is broken; I've also seen (/i and /j missing):
713-
# REMOVE "/"
714-
# REMOVE "/a"
715-
# REMOVE "/b"
716-
# REMOVE "/c"
717-
# REMOVE "/d"
718-
# REMOVE "/e"
719-
# REMOVE "/f"
720-
# REMOVE "/g"
721-
# WRITE "/h"
722-
# WRITE "/h"
723-
windows:
724-
REMOVE "/"
725-
REMOVE "/a"
726-
REMOVE "/b"
727-
REMOVE "/c"
728-
REMOVE "/d"
729-
REMOVE "/e"
730-
REMOVE "/f"
731-
REMOVE "/g"
732-
REMOVE "/h"
733-
REMOVE "/i"
734-
REMOVE "/j"
735-
WRITE "/h"
736-
WRITE "/h"
737-
WRITE "/i"
738-
WRITE "/i"
739-
WRITE "/j"
740-
WRITE "/j"
741-
`},
742-
743683
{"remove recursive", func(t *testing.T, w *Watcher, tmp string) {
744684
recurseOnly(t)
745685

@@ -778,6 +718,66 @@ func TestWatchRemove(t *testing.T) {
778718
tt := tt
779719
tt.run(t)
780720
}
721+
722+
t.Run("remove watched directory", func(t *testing.T) {
723+
t.Parallel()
724+
tmp := t.TempDir()
725+
726+
w := newCollector(t)
727+
w.collect(t)
728+
729+
touch(t, tmp, "a")
730+
touch(t, tmp, "b")
731+
touch(t, tmp, "c")
732+
touch(t, tmp, "d")
733+
touch(t, tmp, "e")
734+
touch(t, tmp, "f")
735+
touch(t, tmp, "g")
736+
mkdir(t, tmp, "h")
737+
mkdir(t, tmp, "h", "a")
738+
mkdir(t, tmp, "i")
739+
mkdir(t, tmp, "i", "a")
740+
mkdir(t, tmp, "j")
741+
mkdir(t, tmp, "j", "a")
742+
addWatch(t, w.w, tmp)
743+
rmAll(t, tmp)
744+
745+
if runtime.GOOS != "windows" {
746+
cmpEvents(t, tmp, w.stop(t), newEvents(t, `
747+
remove /
748+
remove /a
749+
remove /b
750+
remove /c
751+
remove /d
752+
remove /e
753+
remove /f
754+
remove /g
755+
remove /h
756+
remove /i
757+
remove /j`))
758+
return
759+
}
760+
761+
// ReadDirectoryChangesW gives undefined results: not all files are
762+
// always present. So test only that 1) we got the directory itself, and
763+
// 2) we don't get events for unspected files.
764+
var (
765+
events = w.stop(t)
766+
found bool
767+
)
768+
for _, e := range events {
769+
if e.Name == tmp && e.Has(Remove) {
770+
found = true
771+
continue
772+
}
773+
if filepath.Dir(e.Name) != tmp {
774+
t.Errorf("unexpected event: %s", e)
775+
}
776+
}
777+
if !found {
778+
t.Fatalf("didn't see directory in:\n%s", events)
779+
}
780+
})
781781
}
782782

783783
func TestWatchRecursive(t *testing.T) {

mkdoc.zsh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ watcher=$(<<EOF
6262
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
6363
// ("C:/path/to/dir") will also work.
6464
//
65+
// When a watched directory is removed it will always send an event for the
66+
// directory itself, but may not send events for all files in that directory.
67+
// Sometimes it will send events for all times, sometimes it will send no
68+
// events, and often only for some files.
69+
//
6570
// The default buffer size is 64K, which is the largest value that is guaranteed
6671
// to work with SMB filesystems. If you have many events in quick succession
6772
// this may not be enough, and you will have to use [WithBufferSize] to increase

0 commit comments

Comments
 (0)