watch: simplify the fileEvent interface to only contain paths (#144)

This commit is contained in:
Nick Santos 2018-08-22 15:33:06 -04:00 committed by Nicolas De loof
parent a3b012d89f
commit d4f074b32f
4 changed files with 42 additions and 133 deletions

View File

@ -1,10 +1,12 @@
package watch package watch
import "github.com/windmilleng/fsnotify" type FileEvent struct {
Path string
}
type Notify interface { type Notify interface {
Close() error Close() error
Add(name string) error Add(name string) error
Events() chan fsnotify.Event Events() chan FileEvent
Errors() chan error Errors() chan error
} }

View File

@ -9,8 +9,6 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/windmilleng/fsnotify"
) )
// Each implementation of the notify interface should have the same basic // Each implementation of the notify interface should have the same basic
@ -19,7 +17,6 @@ import (
func TestNoEvents(t *testing.T) { func TestNoEvents(t *testing.T) {
f := newNotifyFixture(t) f := newNotifyFixture(t)
defer f.tearDown() defer f.tearDown()
f.fsync()
f.assertEvents() f.assertEvents()
} }
@ -44,7 +41,7 @@ func TestEventOrdering(t *testing.T) {
f.fsync() f.fsync()
f.events = nil f.events = nil
var expected []fsnotify.Event var expected []string
for i, dir := range dirs { for i, dir := range dirs {
base := fmt.Sprintf("%d.txt", i) base := fmt.Sprintf("%d.txt", i)
p := filepath.Join(dir, base) p := filepath.Join(dir, base)
@ -52,33 +49,10 @@ func TestEventOrdering(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected = append(expected, create(filepath.Join(dir, base))) expected = append(expected, filepath.Join(dir, base))
} }
f.fsync()
f.filterJustCreateEvents()
f.assertEvents(expected...) f.assertEvents(expected...)
// Check to make sure that the files appeared in the right order.
createEvents := make([]fsnotify.Event, 0, count)
for _, e := range f.events {
if e.Op == fsnotify.Create {
createEvents = append(createEvents, e)
}
}
if len(createEvents) != count {
t.Fatalf("Expected %d create events. Actual: %+v", count, createEvents)
}
for i, event := range createEvents {
base := fmt.Sprintf("%d.txt", i)
p := filepath.Join(dirs[i], base)
if event.Name != p {
t.Fatalf("Expected event %q at %d. Actual: %+v", base, i, createEvents)
}
}
} }
func TestWatchesAreRecursive(t *testing.T) { func TestWatchesAreRecursive(t *testing.T) {
@ -112,10 +86,7 @@ func TestWatchesAreRecursive(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// we should get notified f.assertEvents(changeFilePath)
f.fsync()
f.assertEvents(create(changeFilePath))
} }
func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) { func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
@ -146,10 +117,7 @@ func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// we should get notified f.assertEvents(subPath, changeFilePath)
f.fsync()
// assert events
f.assertEvents(create(subPath), create(changeFilePath))
} }
func TestWatchNonExistentPath(t *testing.T) { func TestWatchNonExistentPath(t *testing.T) {
@ -172,12 +140,7 @@ func TestWatchNonExistentPath(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f.fsync() f.assertEvents(path)
if runtime.GOOS == "darwin" {
f.assertEvents(create(path))
} else {
f.assertEvents(create(path), write(path))
}
} }
func TestRemove(t *testing.T) { func TestRemove(t *testing.T) {
@ -209,9 +172,7 @@ func TestRemove(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f.fsync() f.assertEvents(path)
f.assertEvents(remove(path))
} }
func TestRemoveAndAddBack(t *testing.T) { func TestRemoveAndAddBack(t *testing.T) {
@ -242,9 +203,8 @@ func TestRemoveAndAddBack(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f.fsync()
f.assertEvents(remove(path)) f.assertEvents(path)
f.events = nil f.events = nil
err = ioutil.WriteFile(path, d1, 0644) err = ioutil.WriteFile(path, d1, 0644)
@ -252,7 +212,7 @@ func TestRemoveAndAddBack(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
f.assertEvents(create(path)) f.assertEvents(path)
} }
func TestSingleFile(t *testing.T) { func TestSingleFile(t *testing.T) {
@ -288,9 +248,7 @@ func TestSingleFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f.fsync() f.assertEvents(path)
f.assertEvents(create(path))
} }
type notifyFixture struct { type notifyFixture struct {
@ -298,7 +256,7 @@ type notifyFixture struct {
root *TempDir root *TempDir
watched *TempDir watched *TempDir
notify Notify notify Notify
events []fsnotify.Event events []FileEvent
} }
func newNotifyFixture(t *testing.T) *notifyFixture { func newNotifyFixture(t *testing.T) *notifyFixture {
@ -330,52 +288,21 @@ func newNotifyFixture(t *testing.T) *notifyFixture {
} }
} }
func (f *notifyFixture) filterJustCreateEvents() { func (f *notifyFixture) assertEvents(expected ...string) {
var r []fsnotify.Event f.fsync()
for _, ev := range f.events {
if ev.Op != fsnotify.Create {
continue
}
r = append(r, ev)
}
f.events = r
}
func (f *notifyFixture) assertEvents(expected ...fsnotify.Event) {
if len(f.events) != len(expected) { if len(f.events) != len(expected) {
f.t.Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected) f.t.Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected)
} }
for i, actual := range f.events { for i, actual := range f.events {
if actual != expected[i] { e := FileEvent{expected[i]}
f.t.Fatalf("Got event %v (expected %v)", actual, expected[i]) if actual != e {
f.t.Fatalf("Got event %v (expected %v)", actual, e)
} }
} }
} }
func create(f string) fsnotify.Event {
return fsnotify.Event{
Name: f,
Op: fsnotify.Create,
}
}
func write(f string) fsnotify.Event {
return fsnotify.Event{
Name: f,
Op: fsnotify.Write,
}
}
func remove(f string) fsnotify.Event {
return fsnotify.Event{
Name: f,
Op: fsnotify.Remove,
}
}
func (f *notifyFixture) fsync() { func (f *notifyFixture) fsync() {
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano()) syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
syncPath := filepath.Join(f.watched.Path(), syncPathBase) syncPath := filepath.Join(f.watched.Path(), syncPathBase)
@ -394,12 +321,19 @@ F:
f.t.Fatal(err) f.t.Fatal(err)
case event := <-f.notify.Events(): case event := <-f.notify.Events():
if strings.Contains(event.Name, syncPath) { if strings.Contains(event.Path, syncPath) {
break F break F
} }
if strings.Contains(event.Name, anySyncPath) { if strings.Contains(event.Path, anySyncPath) {
continue continue
} }
// Don't bother tracking duplicate changes to the same path
// for testing.
if len(f.events) > 0 && f.events[len(f.events)-1].Path == event.Path {
continue
}
f.events = append(f.events, event) f.events = append(f.events, event)
case <-timeout: case <-timeout:

View File

@ -6,12 +6,11 @@ import (
"time" "time"
"github.com/windmilleng/fsevents" "github.com/windmilleng/fsevents"
"github.com/windmilleng/fsnotify"
) )
type darwinNotify struct { type darwinNotify struct {
stream *fsevents.EventStream stream *fsevents.EventStream
events chan fsnotify.Event events chan FileEvent
errors chan error errors chan error
stop chan struct{} stop chan struct{}
@ -35,7 +34,6 @@ func (d *darwinNotify) isTrackingPath(path string) bool {
} }
func (d *darwinNotify) loop() { func (d *darwinNotify) loop() {
lastCreate := ""
ignoredSpuriousEvent := false ignoredSpuriousEvent := false
for { for {
select { select {
@ -48,31 +46,18 @@ func (d *darwinNotify) loop() {
for _, e := range events { for _, e := range events {
e.Path = filepath.Join("/", e.Path) e.Path = filepath.Join("/", e.Path)
op := eventFlagsToOp(e.Flags)
// Sometimes we get duplicate CREATE events.
//
// This is exercised by TestEventOrdering, which creates lots of files
// and generates duplicate CREATE events for some of them.
if op == fsnotify.Create {
if lastCreate == e.Path {
continue
}
lastCreate = e.Path
}
// ignore the first event that says the watched directory // ignore the first event that says the watched directory
// has been created. these are fired spuriously on initiation. // has been created. these are fired spuriously on initiation.
if op == fsnotify.Create { if e.Flags&fsevents.ItemCreated == fsevents.ItemCreated {
if d.isTrackingPath(e.Path) && !ignoredSpuriousEvent { if d.isTrackingPath(e.Path) && !ignoredSpuriousEvent {
ignoredSpuriousEvent = true ignoredSpuriousEvent = true
continue continue
} }
} }
d.events <- fsnotify.Event{ d.events <- FileEvent{
Name: e.Path, Path: e.Path,
Op: op,
} }
} }
} }
@ -116,7 +101,7 @@ func (d *darwinNotify) Close() error {
return nil return nil
} }
func (d *darwinNotify) Events() chan fsnotify.Event { func (d *darwinNotify) Events() chan FileEvent {
return d.events return d.events
} }
@ -131,7 +116,7 @@ func NewWatcher() (Notify, error) {
Flags: fsevents.FileEvents, Flags: fsevents.FileEvents,
}, },
sm: &sync.Mutex{}, sm: &sync.Mutex{},
events: make(chan fsnotify.Event), events: make(chan FileEvent),
errors: make(chan error), errors: make(chan error),
stop: make(chan struct{}), stop: make(chan struct{}),
} }
@ -139,18 +124,4 @@ func NewWatcher() (Notify, error) {
return dw, nil return dw, nil
} }
func eventFlagsToOp(flags fsevents.EventFlags) fsnotify.Op { var _ Notify = &darwinNotify{}
if flags&fsevents.ItemRemoved != 0 {
return fsnotify.Remove
}
if flags&fsevents.ItemRenamed != 0 {
return fsnotify.Rename
}
if flags&fsevents.ItemChangeOwner != 0 {
return fsnotify.Chmod
}
if flags&fsevents.ItemCreated != 0 {
return fsnotify.Create
}
return fsnotify.Write
}

View File

@ -20,7 +20,7 @@ const inotifyMin = 8192
type linuxNotify struct { type linuxNotify struct {
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
events chan fsnotify.Event events chan fsnotify.Event
wrappedEvents chan fsnotify.Event wrappedEvents chan FileEvent
errors chan error errors chan error
watchList map[string]bool watchList map[string]bool
} }
@ -69,7 +69,7 @@ func (d *linuxNotify) Close() error {
return d.watcher.Close() return d.watcher.Close()
} }
func (d *linuxNotify) Events() chan fsnotify.Event { func (d *linuxNotify) Events() chan FileEvent {
return d.wrappedEvents return d.wrappedEvents
} }
@ -107,12 +107,12 @@ func (d *linuxNotify) loop() {
func (d *linuxNotify) sendEventIfWatched(e fsnotify.Event) { func (d *linuxNotify) sendEventIfWatched(e fsnotify.Event) {
if _, ok := d.watchList[e.Name]; ok { if _, ok := d.watchList[e.Name]; ok {
d.wrappedEvents <- e d.wrappedEvents <- FileEvent{e.Name}
} else { } else {
// TODO(dmiller): maybe use a prefix tree here? // TODO(dmiller): maybe use a prefix tree here?
for path := range d.watchList { for path := range d.watchList {
if pathIsChildOf(e.Name, path) { if pathIsChildOf(e.Name, path) {
d.wrappedEvents <- e d.wrappedEvents <- FileEvent{e.Name}
break break
} }
} }
@ -125,7 +125,7 @@ func NewWatcher() (*linuxNotify, error) {
return nil, err return nil, err
} }
wrappedEvents := make(chan fsnotify.Event) wrappedEvents := make(chan FileEvent)
wmw := &linuxNotify{ wmw := &linuxNotify{
watcher: fsw, watcher: fsw,
@ -171,3 +171,5 @@ func checkInotifyLimits() error {
return nil return nil
} }
var _ Notify = &linuxNotify{}