compose/pkg/watch/notify_test.go

427 lines
7.6 KiB
Go
Raw Normal View History

2018-08-16 20:53:47 +02:00
package watch
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/windmilleng/fsnotify"
)
// Each implementation of the notify interface should have the same basic
// behavior.
func TestNoEvents(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
f.fsync()
f.assertEvents()
}
func TestEventOrdering(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
count := 8
dirs := make([]string, count)
for i, _ := range dirs {
dir, err := f.root.NewDir("watched")
if err != nil {
t.Fatal(err)
}
dirs[i] = dir.Path()
err = f.notify.Add(dir.Path())
if err != nil {
t.Fatal(err)
}
}
f.fsync()
f.events = nil
var expected []fsnotify.Event
for i, dir := range dirs {
base := fmt.Sprintf("%d.txt", i)
p := filepath.Join(dir, base)
err := ioutil.WriteFile(p, []byte(base), os.FileMode(0777))
if err != nil {
t.Fatal(err)
}
expected = append(expected, create(filepath.Join(dir, base)))
}
f.fsync()
f.filterJustCreateEvents()
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) {
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
// add a sub directory
subPath := filepath.Join(root.Path(), "sub")
2018-08-20 15:47:40 +02:00
err = os.MkdirAll(subPath, os.ModePerm)
if err != nil {
t.Fatal(err)
}
2018-08-16 20:53:47 +02:00
// watch parent
err = f.notify.Add(root.Path())
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
// change sub directory
changeFilePath := filepath.Join(subPath, "change")
_, err = os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatal(err)
}
// we should get notified
f.fsync()
f.assertEvents(create(changeFilePath))
}
func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
// watch parent
err = f.notify.Add(root.Path())
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
// add a sub directory
subPath := filepath.Join(root.Path(), "sub")
2018-08-20 15:47:40 +02:00
err = os.MkdirAll(subPath, os.ModePerm)
if err != nil {
f.t.Fatal(err)
}
2018-08-16 20:53:47 +02:00
// change something inside sub directory
changeFilePath := filepath.Join(subPath, "change")
_, err = os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatal(err)
}
// we should get notified
f.fsync()
// assert events
f.assertEvents(create(subPath), create(changeFilePath))
}
func TestWatchNonExistentPath(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
path := filepath.Join(root.Path(), "change")
err = f.notify.Add(path)
if err != nil {
t.Fatal(err)
}
d1 := []byte("hello\ngo\n")
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
f.fsync()
if runtime.GOOS == "darwin" {
f.assertEvents(create(path))
} else {
f.assertEvents(create(path), write(path))
}
}
func TestRemove(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
path := filepath.Join(root.Path(), "change")
if err != nil {
t.Fatal(err)
}
d1 := []byte("hello\ngo\n")
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
err = f.notify.Add(path)
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
err = os.Remove(path)
if err != nil {
t.Fatal(err)
}
f.fsync()
f.assertEvents(remove(path))
}
func TestRemoveAndAddBack(t *testing.T) {
t.Skip("Skipping broken test for now")
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
path := filepath.Join(root.Path(), "change")
if err != nil {
t.Fatal(err)
}
d1 := []byte("hello\ngo\n")
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
err = f.notify.Add(path)
if err != nil {
t.Fatal(err)
}
err = os.Remove(path)
if err != nil {
t.Fatal(err)
}
f.fsync()
f.assertEvents(remove(path))
f.events = nil
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
f.assertEvents(create(path))
}
func TestSingleFile(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("Broken on Linux")
}
f := newNotifyFixture(t)
defer f.tearDown()
root, err := f.root.NewDir("root")
if err != nil {
t.Fatal(err)
}
path := filepath.Join(root.Path(), "change")
if err != nil {
t.Fatal(err)
}
d1 := []byte("hello\ngo\n")
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
err = f.notify.Add(path)
if err != nil {
t.Fatal(err)
}
d2 := []byte("hello\nworld\n")
err = ioutil.WriteFile(path, d2, 0644)
if err != nil {
t.Fatal(err)
}
f.fsync()
f.assertEvents(create(path))
}
type notifyFixture struct {
t *testing.T
root *TempDir
watched *TempDir
notify Notify
events []fsnotify.Event
}
func newNotifyFixture(t *testing.T) *notifyFixture {
SetLimitChecksEnabled(false)
notify, err := NewWatcher()
if err != nil {
t.Fatal(err)
}
root, err := NewDir(t.Name())
if err != nil {
t.Fatal(err)
}
watched, err := root.NewDir("watched")
if err != nil {
t.Fatal(err)
}
err = notify.Add(watched.Path())
if err != nil {
t.Fatal(err)
}
return &notifyFixture{
t: t,
root: root,
watched: watched,
notify: notify,
}
}
func (f *notifyFixture) filterJustCreateEvents() {
var r []fsnotify.Event
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) {
f.t.Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected)
}
for i, actual := range f.events {
if actual != expected[i] {
f.t.Fatalf("Got event %v (expected %v)", actual, expected[i])
}
}
}
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() {
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
syncPath := filepath.Join(f.watched.Path(), syncPathBase)
anySyncPath := filepath.Join(f.watched.Path(), "sync-")
timeout := time.After(time.Second)
err := ioutil.WriteFile(syncPath, []byte(fmt.Sprintf("%s", time.Now())), os.FileMode(0777))
if err != nil {
f.t.Fatal(err)
}
F:
for {
select {
case err := <-f.notify.Errors():
f.t.Fatal(err)
case event := <-f.notify.Events():
if strings.Contains(event.Name, syncPath) {
break F
}
if strings.Contains(event.Name, anySyncPath) {
continue
}
f.events = append(f.events, event)
case <-timeout:
f.t.Fatalf("fsync: timeout")
}
}
if err != nil {
f.t.Fatal(err)
}
}
func (f *notifyFixture) tearDown() {
SetLimitChecksEnabled(true)
err := f.root.TearDown()
if err != nil {
f.t.Fatal(err)
}
err = f.notify.Close()
if err != nil {
f.t.Fatal(err)
}
}