compose/pkg/watch/notify_test.go

603 lines
11 KiB
Go
Raw Normal View History

2018-08-16 20:53:47 +02:00
package watch
import (
"bytes"
"context"
2018-08-16 20:53:47 +02:00
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
2018-08-16 20:53:47 +02:00
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
2019-06-24 17:28:29 +02:00
"github.com/windmilleng/tilt/internal/logger"
"github.com/windmilleng/tilt/internal/testutils/tempdir"
2018-08-16 20:53:47 +02:00
)
// Each implementation of the notify interface should have the same basic
// behavior.
func TestNoEvents(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
f.assertEvents()
}
func TestEventOrdering(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
count := 8
dirs := make([]string, count)
for i, _ := range dirs {
dir := f.TempDir("watched")
dirs[i] = dir
err := f.notify.Add(dir)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
}
f.fsync()
f.events = nil
var expected []string
2018-08-16 20:53:47 +02:00
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, filepath.Join(dir, base))
2018-08-16 20:53:47 +02:00
}
f.assertEvents(expected...)
}
// Simulate a git branch switch that creates a bunch
// of directories, creates files in them, then deletes
// them all quickly. Make sure there are no errors.
func TestGitBranchSwitch(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
count := 10
dirs := make([]string, count)
for i, _ := range dirs {
dir := f.TempDir("watched")
dirs[i] = dir
err := f.notify.Add(dir)
if err != nil {
t.Fatal(err)
}
}
f.fsync()
f.events = nil
// consume all the events in the background
ctx, cancel := context.WithCancel(context.Background())
done := f.consumeEventsInBackground(ctx)
for i, dir := range dirs {
for j := 0; j < count; j++ {
base := fmt.Sprintf("x/y/dir-%d/x.txt", j)
p := filepath.Join(dir, base)
f.WriteFile(p, "contents")
}
if i != 0 {
os.RemoveAll(dir)
}
}
cancel()
err := <-done
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
// Make sure the watch on the first dir still works.
dir := dirs[0]
path := filepath.Join(dir, "change")
f.WriteFile(path, "hello\n")
f.fsync()
f.assertEvents(path)
// Make sure there are no errors in the out stream
assert.Equal(t, "", f.out.String())
}
2018-08-16 20:53:47 +02:00
func TestWatchesAreRecursive(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
2018-08-16 20:53:47 +02:00
// add a sub directory
subPath := filepath.Join(root, "sub")
f.MkdirAll(subPath)
2018-08-16 20:53:47 +02:00
// watch parent
err := f.notify.Add(root)
2018-08-16 20:53:47 +02:00
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)
}
f.assertEvents(changeFilePath)
2018-08-16 20:53:47 +02:00
}
func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
2018-08-16 20:53:47 +02:00
// watch parent
err := f.notify.Add(root)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
2018-08-16 20:53:47 +02:00
// add a sub directory
subPath := filepath.Join(root, "sub")
f.MkdirAll(subPath)
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)
}
f.assertEvents(subPath, changeFilePath)
2018-08-16 20:53:47 +02:00
}
func TestWatchNonExistentPath(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
path := filepath.Join(root, "change")
2018-08-16 20:53:47 +02:00
err := f.notify.Add(path)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
f.fsync()
d1 := "hello\ngo\n"
f.WriteFile(path, d1)
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
}
func TestWatchNonExistentPathDoesNotFireSiblingEvent(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
watchedFile := filepath.Join(root, "a.txt")
unwatchedSibling := filepath.Join(root, "b.txt")
err := f.notify.Add(watchedFile)
if err != nil {
t.Fatal(err)
}
f.fsync()
d1 := "hello\ngo\n"
f.WriteFile(unwatchedSibling, d1)
f.assertEvents()
}
2018-08-16 20:53:47 +02:00
func TestRemove(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
path := filepath.Join(root, "change")
2018-08-16 20:53:47 +02:00
d1 := "hello\ngo\n"
f.WriteFile(path, d1)
2018-08-16 20:53:47 +02:00
err := f.notify.Add(path)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
err = os.Remove(path)
if err != nil {
t.Fatal(err)
}
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
}
func TestRemoveAndAddBack(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
path := filepath.Join(f.watched, "change")
2018-08-16 20:53:47 +02:00
d1 := []byte("hello\ngo\n")
2018-08-22 21:48:33 +02:00
err := ioutil.WriteFile(path, d1, 0644)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
err = f.notify.Add(path)
if err != nil {
t.Fatal(err)
}
2018-08-22 21:48:33 +02:00
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
err = os.Remove(path)
if err != nil {
t.Fatal(err)
}
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
f.events = nil
err = ioutil.WriteFile(path, d1, 0644)
if err != nil {
t.Fatal(err)
}
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
}
func TestSingleFile(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
path := filepath.Join(root, "change")
2018-08-16 20:53:47 +02:00
d1 := "hello\ngo\n"
f.WriteFile(path, d1)
2018-08-16 20:53:47 +02:00
err := f.notify.Add(path)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
f.fsync()
2018-08-16 20:53:47 +02:00
d2 := []byte("hello\nworld\n")
err = ioutil.WriteFile(path, d2, 0644)
if err != nil {
t.Fatal(err)
}
f.assertEvents(path)
2018-08-16 20:53:47 +02:00
}
2018-08-22 21:59:46 +02:00
func TestWriteBrokenLink(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
link := filepath.Join(f.watched, "brokenLink")
missingFile := filepath.Join(f.watched, "missingFile")
2018-08-22 21:59:46 +02:00
err := os.Symlink(missingFile, link)
if err != nil {
t.Fatal(err)
}
f.assertEvents(link)
}
func TestWriteGoodLink(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
goodFile := filepath.Join(f.watched, "goodFile")
2018-08-22 21:59:46 +02:00
err := ioutil.WriteFile(goodFile, []byte("hello"), 0644)
if err != nil {
t.Fatal(err)
}
link := filepath.Join(f.watched, "goodFileSymlink")
2018-08-22 21:59:46 +02:00
err = os.Symlink(goodFile, link)
if err != nil {
t.Fatal(err)
}
f.assertEvents(goodFile, link)
}
2018-09-14 23:13:36 +02:00
func TestWatchBrokenLink(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
newRoot, err := NewDir(t.Name())
if err != nil {
t.Fatal(err)
}
defer newRoot.TearDown()
link := filepath.Join(newRoot.Path(), "brokenLink")
missingFile := filepath.Join(newRoot.Path(), "missingFile")
err = os.Symlink(missingFile, link)
if err != nil {
t.Fatal(err)
}
err = f.notify.Add(newRoot.Path())
if err != nil {
t.Fatal(err)
}
os.Remove(link)
f.assertEvents(link)
}
func TestMoveAndReplace(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.TempDir("root")
file := filepath.Join(root, "myfile")
f.WriteFile(file, "hello")
err := f.notify.Add(file)
if err != nil {
t.Fatal(err)
}
tmpFile := filepath.Join(root, ".myfile.swp")
f.WriteFile(tmpFile, "world")
err = os.Rename(tmpFile, file)
if err != nil {
t.Fatal(err)
}
f.assertEvents(file)
}
func TestWatchBothDirAndFile(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
dir := f.JoinPath("foo")
fileA := f.JoinPath("foo", "a")
fileB := f.JoinPath("foo", "b")
f.WriteFile(fileA, "a")
f.WriteFile(fileB, "b")
f.watch(fileA)
f.watch(dir)
f.fsync()
f.events = nil
f.WriteFile(fileB, "b-new")
f.assertEvents(fileB)
}
func TestWatchNonexistentFileInNonexistentDirectoryCreatedSimultaneously(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.JoinPath("root")
err := os.Mkdir(root, 0777)
if err != nil {
t.Fatal(err)
}
file := f.JoinPath("root", "parent", "a")
f.watch(file)
f.fsync()
f.events = nil
f.WriteFile(file, "hello")
f.assertEvents(file)
}
func TestWatchNonexistentDirectory(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
root := f.JoinPath("root")
err := os.Mkdir(root, 0777)
if err != nil {
t.Fatal(err)
}
parent := f.JoinPath("parent")
file := f.JoinPath("parent", "a")
f.watch(parent)
f.fsync()
f.events = nil
err = os.Mkdir(parent, 0777)
if err != nil {
t.Fatal(err)
}
if runtime.GOOS == "darwin" {
// for directories that were the root of an Add, we don't report creation, cf. watcher_darwin.go
f.assertEvents()
} else {
f.assertEvents(parent)
}
f.WriteFile(file, "hello")
if runtime.GOOS == "darwin" {
// mac doesn't return the dir change as part of file creation
f.assertEvents(file)
} else {
f.assertEvents(parent, file)
}
}
// doesn't work on linux
// func TestWatchNonexistentFileInNonexistentDirectory(t *testing.T) {
// f := newNotifyFixture(t)
// defer f.tearDown()
// root := f.JoinPath("root")
// err := os.Mkdir(root, 0777)
// if err != nil {
// t.Fatal(err)
// }
// parent := f.JoinPath("parent")
// file := f.JoinPath("parent", "a")
// f.watch(file)
// f.assertEvents()
// err = os.Mkdir(parent, 0777)
// if err != nil {
// t.Fatal(err)
// }
// f.assertEvents()
// f.WriteFile(file, "hello")
// f.assertEvents(file)
// }
2018-08-16 20:53:47 +02:00
type notifyFixture struct {
out *bytes.Buffer
*tempdir.TempDirFixture
2018-08-16 20:53:47 +02:00
notify Notify
watched string
events []FileEvent
2018-08-16 20:53:47 +02:00
}
func newNotifyFixture(t *testing.T) *notifyFixture {
out := bytes.NewBuffer(nil)
notify, err := NewWatcher(logger.NewLogger(logger.DebugLvl, out))
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
f := tempdir.NewTempDirFixture(t)
watched := f.TempDir("watched")
2018-08-16 20:53:47 +02:00
err = notify.Add(watched)
2018-08-16 20:53:47 +02:00
if err != nil {
t.Fatal(err)
}
return &notifyFixture{
TempDirFixture: f,
watched: watched,
notify: notify,
out: out,
2018-08-16 20:53:47 +02:00
}
}
func (f *notifyFixture) watch(path string) {
err := f.notify.Add(path)
if err != nil {
f.T().Fatalf("notify.Add: %s", path)
}
}
func (f *notifyFixture) assertEvents(expected ...string) {
f.fsync()
2018-08-16 20:53:47 +02:00
if len(f.events) != len(expected) {
f.T().Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected)
2018-08-16 20:53:47 +02:00
}
for i, actual := range f.events {
e := FileEvent{expected[i]}
if actual != e {
f.T().Fatalf("Got event %v (expected %v)", actual, e)
2018-08-16 20:53:47 +02:00
}
}
}
func (f *notifyFixture) consumeEventsInBackground(ctx context.Context) chan error {
done := make(chan error)
go func() {
for {
select {
case <-ctx.Done():
close(done)
return
case err := <-f.notify.Errors():
done <- err
close(done)
return
case <-f.notify.Events():
}
}
}()
return done
}
2018-08-16 20:53:47 +02:00
func (f *notifyFixture) fsync() {
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
syncPath := filepath.Join(f.watched, syncPathBase)
anySyncPath := filepath.Join(f.watched, "sync-")
2018-08-16 20:53:47 +02:00
timeout := time.After(time.Second)
f.WriteFile(syncPath, fmt.Sprintf("%s", time.Now()))
2018-08-16 20:53:47 +02:00
F:
for {
select {
case err := <-f.notify.Errors():
f.T().Fatal(err)
2018-08-16 20:53:47 +02:00
case event := <-f.notify.Events():
if strings.Contains(event.Path, syncPath) {
2018-08-16 20:53:47 +02:00
break F
}
if strings.Contains(event.Path, anySyncPath) {
2018-08-16 20:53:47 +02:00
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
}
2018-08-16 20:53:47 +02:00
f.events = append(f.events, event)
case <-timeout:
f.T().Fatalf("fsync: timeout")
2018-08-16 20:53:47 +02:00
}
}
}
func (f *notifyFixture) tearDown() {
err := f.notify.Close()
2018-08-16 20:53:47 +02:00
if err != nil {
f.T().Fatal(err)
2018-08-16 20:53:47 +02:00
}
// drain channels from watcher
go func() {
for _ = range f.notify.Events() {
}
}()
go func() {
for _ = range f.notify.Errors() {
}
}()
f.TempDirFixture.TearDown()
2018-08-16 20:53:47 +02:00
}