Skip to content

Commit 6defe7c

Browse files
nicksndeloof
authored andcommitted
watch: tfw you have a test that asserts broken file-watch behavior 😢 (docker#1354)
1 parent 0482f92 commit 6defe7c

File tree

2 files changed

+52
-32
lines changed

2 files changed

+52
-32
lines changed

pkg/watch/notify_test.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"io/ioutil"
66
"os"
77
"path/filepath"
8-
"runtime"
98
"strings"
109
"testing"
1110
"time"
@@ -128,6 +127,26 @@ func TestWatchNonExistentPath(t *testing.T) {
128127
f.assertEvents(path)
129128
}
130129

130+
func TestWatchNonExistentPathDoesNotFireSiblingEvent(t *testing.T) {
131+
f := newNotifyFixture(t)
132+
defer f.tearDown()
133+
134+
root := f.TempDir("root")
135+
watchedFile := filepath.Join(root, "a.txt")
136+
unwatchedSibling := filepath.Join(root, "b.txt")
137+
138+
err := f.notify.Add(watchedFile)
139+
if err != nil {
140+
t.Fatal(err)
141+
}
142+
143+
f.fsync()
144+
145+
d1 := "hello\ngo\n"
146+
f.WriteFile(unwatchedSibling, d1)
147+
f.assertEvents()
148+
}
149+
131150
func TestRemove(t *testing.T) {
132151
f := newNotifyFixture(t)
133152
defer f.tearDown()
@@ -319,18 +338,13 @@ func TestWatchNonexistentDirectory(t *testing.T) {
319338
if err != nil {
320339
t.Fatal(err)
321340
}
322-
parent := f.JoinPath("root", "parent")
323341
file := f.JoinPath("root", "parent", "a")
324342

325343
f.watch(file)
326344
f.fsync()
327345
f.events = nil
328346
f.WriteFile(file, "hello")
329-
if runtime.GOOS == "darwin" {
330-
f.assertEvents(file)
331-
} else {
332-
f.assertEvents(parent, file)
333-
}
347+
f.assertEvents(file)
334348
}
335349

336350
type notifyFixture struct {

pkg/watch/watcher_naive.go

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ type naiveNotify struct {
2121
events chan fsnotify.Event
2222
wrappedEvents chan FileEvent
2323
errors chan error
24-
watchList map[string]bool
24+
25+
// Paths that we're watching that should be passed up to the caller.
26+
// Note that we may have to watch ancestors of these paths
27+
// in order to fulfill the API promise.
28+
notifyList map[string]bool
2529
}
2630

2731
func (d *naiveNotify) Add(name string) error {
@@ -32,24 +36,22 @@ func (d *naiveNotify) Add(name string) error {
3236

3337
// if it's a file that doesn't exist, watch its parent
3438
if os.IsNotExist(err) {
35-
err, fileWatched := d.watchUpRecursively(name)
39+
err = d.watchAncestorOfMissingPath(name)
3640
if err != nil {
37-
return errors.Wrapf(err, "watchUpRecursively(%q)", name)
41+
return errors.Wrapf(err, "watchAncestorOfMissingPath(%q)", name)
3842
}
39-
d.watchList[fileWatched] = true
4043
} else if fi.IsDir() {
4144
err = d.watchRecursively(name)
4245
if err != nil {
4346
return errors.Wrapf(err, "notify.Add(%q)", name)
4447
}
45-
d.watchList[name] = true
4648
} else {
4749
err = d.watcher.Add(name)
4850
if err != nil {
4951
return errors.Wrapf(err, "notify.Add(%q)", name)
5052
}
51-
d.watchList[name] = true
5253
}
54+
d.notifyList[name] = true
5355

5456
return nil
5557
}
@@ -71,22 +73,22 @@ func (d *naiveNotify) watchRecursively(dir string) error {
7173
})
7274
}
7375

74-
func (d *naiveNotify) watchUpRecursively(path string) (error, string) {
76+
func (d *naiveNotify) watchAncestorOfMissingPath(path string) error {
7577
if path == string(filepath.Separator) {
76-
return fmt.Errorf("cannot watch root directory"), ""
78+
return fmt.Errorf("cannot watch root directory")
7779
}
7880

7981
_, err := os.Stat(path)
8082
if err != nil && !os.IsNotExist(err) {
81-
return errors.Wrapf(err, "os.Stat(%q)", path), ""
83+
return errors.Wrapf(err, "os.Stat(%q)", path)
8284
}
8385

8486
if os.IsNotExist(err) {
8587
parent := filepath.Dir(path)
86-
return d.watchUpRecursively(parent)
88+
return d.watchAncestorOfMissingPath(parent)
8789
}
8890

89-
return d.watcher.Add(path), path
91+
return d.watcher.Add(path)
9092
}
9193

9294
func (d *naiveNotify) Close() error {
@@ -122,35 +124,39 @@ func (d *naiveNotify) loop() {
122124
Op: fsnotify.Create,
123125
Name: path,
124126
}
125-
d.sendEventIfWatched(newE)
126-
// TODO(dmiller): symlinks 😭
127-
err = d.Add(path)
128-
if err != nil {
129-
log.Printf("Error watching path %s: %s", e.Name, err)
127+
128+
if d.shouldNotify(newE) {
129+
d.wrappedEvents <- FileEvent{newE.Name}
130+
131+
// TODO(dmiller): symlinks 😭
132+
err = d.Add(path)
133+
if err != nil {
134+
log.Printf("Error watching path %s: %s", e.Name, err)
135+
}
130136
}
131137
return nil
132138
})
133139
if err != nil {
134140
log.Printf("Error walking directory %s: %s", e.Name, err)
135141
}
136-
} else {
137-
d.sendEventIfWatched(e)
142+
} else if d.shouldNotify(e) {
143+
d.wrappedEvents <- FileEvent{e.Name}
138144
}
139145
}
140146
}
141147

142-
func (d *naiveNotify) sendEventIfWatched(e fsnotify.Event) {
143-
if _, ok := d.watchList[e.Name]; ok {
144-
d.wrappedEvents <- FileEvent{e.Name}
148+
func (d *naiveNotify) shouldNotify(e fsnotify.Event) bool {
149+
if _, ok := d.notifyList[e.Name]; ok {
150+
return true
145151
} else {
146152
// TODO(dmiller): maybe use a prefix tree here?
147-
for path := range d.watchList {
153+
for path := range d.notifyList {
148154
if pathIsChildOf(e.Name, path) {
149-
d.wrappedEvents <- FileEvent{e.Name}
150-
break
155+
return true
151156
}
152157
}
153158
}
159+
return false
154160
}
155161

156162
func NewWatcher() (*naiveNotify, error) {
@@ -166,7 +172,7 @@ func NewWatcher() (*naiveNotify, error) {
166172
events: fsw.Events,
167173
wrappedEvents: wrappedEvents,
168174
errors: fsw.Errors,
169-
watchList: map[string]bool{},
175+
notifyList: map[string]bool{},
170176
}
171177

172178
go wmw.loop()

0 commit comments

Comments
 (0)