|
@@ -0,0 +1,1120 @@
|
|
|
|
+// Copyright 2010 The Go Authors. All rights reserved.
|
|
|
|
+// Use of this source code is governed by a BSD-style
|
|
|
|
+// license that can be found in the LICENSE file.
|
|
|
|
+
|
|
|
|
+// +build !plan9,!solaris
|
|
|
|
+
|
|
|
|
+package fsnotify
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "io/ioutil"
|
|
|
|
+ "os"
|
|
|
|
+ "os/exec"
|
|
|
|
+ "path/filepath"
|
|
|
|
+ "runtime"
|
|
|
|
+ "sync/atomic"
|
|
|
|
+ "testing"
|
|
|
|
+ "time"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// An atomic counter
|
|
|
|
+type counter struct {
|
|
|
|
+ val int32
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (c *counter) increment() {
|
|
|
|
+ atomic.AddInt32(&c.val, 1)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (c *counter) value() int32 {
|
|
|
|
+ return atomic.LoadInt32(&c.val)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (c *counter) reset() {
|
|
|
|
+ atomic.StoreInt32(&c.val, 0)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// tempMkdir makes a temporary directory
|
|
|
|
+func tempMkdir(t *testing.T) string {
|
|
|
|
+ dir, err := ioutil.TempDir("", "fsnotify")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("failed to create test directory: %s", err)
|
|
|
|
+ }
|
|
|
|
+ return dir
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// newWatcher initializes an fsnotify Watcher instance.
|
|
|
|
+func newWatcher(t *testing.T) *Watcher {
|
|
|
|
+ watcher, err := NewWatcher()
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("NewWatcher() failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ return watcher
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// addWatch adds a watch for a directory
|
|
|
|
+func addWatch(t *testing.T, watcher *Watcher, dir string) {
|
|
|
|
+ if err := watcher.Add(dir); err != nil {
|
|
|
|
+ t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyMultipleOperations(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create directory that's not watched
|
|
|
|
+ testDirToMoveFiles := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDirToMoveFiles)
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
|
|
|
|
+ testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var createReceived, modifyReceived, deleteReceived, renameReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ if event.Op&Remove == Remove {
|
|
|
|
+ deleteReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Write == Write {
|
|
|
|
+ modifyReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Create == Create {
|
|
|
|
+ createReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Rename == Rename {
|
|
|
|
+ renameReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ time.Sleep(time.Millisecond)
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ if err := testRename(testFile, testFileRenamed); err != nil {
|
|
|
|
+ t.Fatalf("rename failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Modify the file outside of the watched dir
|
|
|
|
+ f, err = os.Open(testFileRenamed)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("open test renamed file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // Recreate the file that was moved
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Close()
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ cReceived := createReceived.value()
|
|
|
|
+ if cReceived != 2 {
|
|
|
|
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
|
|
|
|
+ }
|
|
|
|
+ mReceived := modifyReceived.value()
|
|
|
|
+ if mReceived != 1 {
|
|
|
|
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
|
|
|
|
+ }
|
|
|
|
+ dReceived := deleteReceived.value()
|
|
|
|
+ rReceived := renameReceived.value()
|
|
|
|
+ if dReceived+rReceived != 1 {
|
|
|
|
+ t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyMultipleCreates(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var createReceived, modifyReceived, deleteReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ if event.Op&Remove == Remove {
|
|
|
|
+ deleteReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Create == Create {
|
|
|
|
+ createReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Write == Write {
|
|
|
|
+ modifyReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ time.Sleep(time.Millisecond)
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ os.Remove(testFile)
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // Recreate the file
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Close()
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // Modify
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ time.Sleep(time.Millisecond)
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // Modify
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ time.Sleep(time.Millisecond)
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ cReceived := createReceived.value()
|
|
|
|
+ if cReceived != 2 {
|
|
|
|
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
|
|
|
|
+ }
|
|
|
|
+ mReceived := modifyReceived.value()
|
|
|
|
+ if mReceived < 3 {
|
|
|
|
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
|
|
|
|
+ }
|
|
|
|
+ dReceived := deleteReceived.value()
|
|
|
|
+ if dReceived != 1 {
|
|
|
|
+ t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyDirOnly(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create a file before watching directory
|
|
|
|
+ // This should NOT add any events to the fsnotify event queue
|
|
|
|
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
|
|
|
|
+ {
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var createReceived, modifyReceived, deleteReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ if event.Op&Remove == Remove {
|
|
|
|
+ deleteReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Write == Write {
|
|
|
|
+ modifyReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Create == Create {
|
|
|
|
+ createReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ time.Sleep(time.Millisecond)
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
|
|
|
|
+
|
|
|
|
+ os.Remove(testFile)
|
|
|
|
+ os.Remove(testFileAlreadyExists)
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ cReceived := createReceived.value()
|
|
|
|
+ if cReceived != 1 {
|
|
|
|
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
|
|
|
|
+ }
|
|
|
|
+ mReceived := modifyReceived.value()
|
|
|
|
+ if mReceived != 1 {
|
|
|
|
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
|
|
|
|
+ }
|
|
|
|
+ dReceived := deleteReceived.value()
|
|
|
|
+ if dReceived != 2 {
|
|
|
|
+ t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyDeleteWatchedDir(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+ defer watcher.Close()
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create a file before watching directory
|
|
|
|
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
|
|
|
|
+ {
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Add a watch for testFile
|
|
|
|
+ addWatch(t, watcher, testFileAlreadyExists)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var deleteReceived counter
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ if event.Op&Remove == Remove {
|
|
|
|
+ deleteReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ dReceived := deleteReceived.value()
|
|
|
|
+ if dReceived < 2 {
|
|
|
|
+ t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifySubDir(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
|
|
|
|
+ testSubDir := filepath.Join(testDir, "sub")
|
|
|
|
+ testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var createReceived, deleteReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ if event.Op&Create == Create {
|
|
|
|
+ createReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Remove == Remove {
|
|
|
|
+ deleteReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Create sub-directory
|
|
|
|
+ if err := os.Mkdir(testSubDir, 0777); err != nil {
|
|
|
|
+ t.Fatalf("failed to create test sub-directory: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ // Create a file (Should not see this! we are not watching subdir)
|
|
|
|
+ var fs *os.File
|
|
|
|
+ fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ fs.Sync()
|
|
|
|
+ fs.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(200 * time.Millisecond)
|
|
|
|
+
|
|
|
|
+ // Make sure receive deletes for both file and sub-directory
|
|
|
|
+ os.RemoveAll(testSubDir)
|
|
|
|
+ os.Remove(testFile1)
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ cReceived := createReceived.value()
|
|
|
|
+ if cReceived != 2 {
|
|
|
|
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
|
|
|
|
+ }
|
|
|
|
+ dReceived := deleteReceived.value()
|
|
|
|
+ if dReceived != 2 {
|
|
|
|
+ t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyRename(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
|
|
|
|
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var renameReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
|
|
|
|
+ if event.Op&Rename == Rename {
|
|
|
|
+ renameReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ // Add a watch for testFile
|
|
|
|
+ addWatch(t, watcher, testFile)
|
|
|
|
+
|
|
|
|
+ if err := testRename(testFile, testFileRenamed); err != nil {
|
|
|
|
+ t.Fatalf("rename failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ if renameReceived.value() == 0 {
|
|
|
|
+ t.Fatal("fsnotify rename events have not been received after 500 ms")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ os.Remove(testFileRenamed)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyRenameToCreate(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create directory to get file
|
|
|
|
+ testDirFrom := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDirFrom)
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
|
|
|
|
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var createReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
|
|
|
|
+ if event.Op&Create == Create {
|
|
|
|
+ createReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ if err := testRename(testFile, testFileRenamed); err != nil {
|
|
|
|
+ t.Fatalf("rename failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ if createReceived.value() == 0 {
|
|
|
|
+ t.Fatal("fsnotify create events have not been received after 500 ms")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ os.Remove(testFileRenamed)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyRenameToOverwrite(t *testing.T) {
|
|
|
|
+ switch runtime.GOOS {
|
|
|
|
+ case "plan9", "windows":
|
|
|
|
+ t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create directory to get file
|
|
|
|
+ testDirFrom := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDirFrom)
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
|
|
|
|
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ var fr *os.File
|
|
|
|
+ fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ fr.Sync()
|
|
|
|
+ fr.Close()
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ var eventReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testFileRenamed) {
|
|
|
|
+ eventReceived.increment()
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ if err := testRename(testFile, testFileRenamed); err != nil {
|
|
|
|
+ t.Fatalf("rename failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ if eventReceived.value() == 0 {
|
|
|
|
+ t.Fatal("fsnotify events have not been received after 500 ms")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(2 * time.Second):
|
|
|
|
+ t.Fatal("event stream was not closed after 2 seconds")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ os.Remove(testFileRenamed)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestRemovalOfWatch(t *testing.T) {
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create a file before watching directory
|
|
|
|
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
|
|
|
|
+ {
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+ defer watcher.Close()
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+ if err := watcher.Remove(testDir); err != nil {
|
|
|
|
+ t.Fatalf("Could not remove the watch: %v\n", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ go func() {
|
|
|
|
+ select {
|
|
|
|
+ case ev := <-watcher.Events:
|
|
|
|
+ t.Fatalf("We received event: %v\n", ev)
|
|
|
|
+ case <-time.After(500 * time.Millisecond):
|
|
|
|
+ t.Log("No event received, as expected.")
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ time.Sleep(200 * time.Millisecond)
|
|
|
|
+ // Modify the file outside of the watched dir
|
|
|
|
+ f, err := os.Open(testFileAlreadyExists)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("Open test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+ if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
|
|
|
|
+ t.Fatalf("chmod failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ time.Sleep(400 * time.Millisecond)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyAttrib(t *testing.T) {
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ t.Skip("attributes don't work on Windows.")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for err := range watcher.Errors {
|
|
|
|
+ t.Fatalf("error received: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
|
|
|
|
+
|
|
|
|
+ // Receive events on the event channel on a separate goroutine
|
|
|
|
+ eventstream := watcher.Events
|
|
|
|
+ // The modifyReceived counter counts IsModify events that are not IsAttrib,
|
|
|
|
+ // and the attribReceived counts IsAttrib events (which are also IsModify as
|
|
|
|
+ // a consequence).
|
|
|
|
+ var modifyReceived counter
|
|
|
|
+ var attribReceived counter
|
|
|
|
+ done := make(chan bool)
|
|
|
|
+ go func() {
|
|
|
|
+ for event := range eventstream {
|
|
|
|
+ // Only count relevant events
|
|
|
|
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
|
|
|
|
+ if event.Op&Write == Write {
|
|
|
|
+ modifyReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ if event.Op&Chmod == Chmod {
|
|
|
|
+ attribReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ t.Logf("event received: %s", event)
|
|
|
|
+ } else {
|
|
|
|
+ t.Logf("unexpected event received: %s", event)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ done <- true
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Create a file
|
|
|
|
+ // This should add at least one event to the fsnotify event queue
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+
|
|
|
|
+ f.WriteString("data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ // Add a watch for testFile
|
|
|
|
+ addWatch(t, watcher, testFile)
|
|
|
|
+
|
|
|
|
+ if err := os.Chmod(testFile, 0700); err != nil {
|
|
|
|
+ t.Fatalf("chmod failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ // Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+ if modifyReceived.value() != 0 {
|
|
|
|
+ t.Fatal("received an unexpected modify event when creating a test file")
|
|
|
|
+ }
|
|
|
|
+ if attribReceived.value() == 0 {
|
|
|
|
+ t.Fatal("fsnotify attribute events have not received after 500 ms")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Modifying the contents of the file does not set the attrib flag (although eg. the mtime
|
|
|
|
+ // might have been modified).
|
|
|
|
+ modifyReceived.reset()
|
|
|
|
+ attribReceived.reset()
|
|
|
|
+
|
|
|
|
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("reopening test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ f.WriteString("more data")
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+
|
|
|
|
+ if modifyReceived.value() != 1 {
|
|
|
|
+ t.Fatal("didn't receive a modify event after changing test file contents")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if attribReceived.value() != 0 {
|
|
|
|
+ t.Fatal("did receive an unexpected attrib event after changing test file contents")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ modifyReceived.reset()
|
|
|
|
+ attribReceived.reset()
|
|
|
|
+
|
|
|
|
+ // Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
|
|
|
|
+ // of the file are not changed though)
|
|
|
|
+ if err := os.Chmod(testFile, 0600); err != nil {
|
|
|
|
+ t.Fatalf("chmod failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+
|
|
|
|
+ if attribReceived.value() != 1 {
|
|
|
|
+ t.Fatal("didn't receive an attribute change after 500ms")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+ t.Log("waiting for the event channel to become closed...")
|
|
|
|
+ select {
|
|
|
|
+ case <-done:
|
|
|
|
+ t.Log("event channel closed")
|
|
|
|
+ case <-time.After(1e9):
|
|
|
|
+ t.Fatal("event stream was not closed after 1 second")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ os.Remove(testFile)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyClose(t *testing.T) {
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+ watcher.Close()
|
|
|
|
+
|
|
|
|
+ var done int32
|
|
|
|
+ go func() {
|
|
|
|
+ watcher.Close()
|
|
|
|
+ atomic.StoreInt32(&done, 1)
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ time.Sleep(50e6) // 50 ms
|
|
|
|
+ if atomic.LoadInt32(&done) == 0 {
|
|
|
|
+ t.Fatal("double Close() test failed: second Close() call didn't return")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ if err := watcher.Add(testDir); err == nil {
|
|
|
|
+ t.Fatal("expected error on Watch() after Close(), got nil")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFsnotifyFakeSymlink(t *testing.T) {
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ t.Skip("symlinks don't work on Windows.")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ var errorsReceived counter
|
|
|
|
+ // Receive errors on the error channel on a separate goroutine
|
|
|
|
+ go func() {
|
|
|
|
+ for errors := range watcher.Errors {
|
|
|
|
+ t.Logf("Received error: %s", errors)
|
|
|
|
+ errorsReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // Count the CREATE events received
|
|
|
|
+ var createEventsReceived, otherEventsReceived counter
|
|
|
|
+ go func() {
|
|
|
|
+ for ev := range watcher.Events {
|
|
|
|
+ t.Logf("event received: %s", ev)
|
|
|
|
+ if ev.Op&Create == Create {
|
|
|
|
+ createEventsReceived.increment()
|
|
|
|
+ } else {
|
|
|
|
+ otherEventsReceived.increment()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
|
|
|
|
+ t.Fatalf("Failed to create bogus symlink: %s", err)
|
|
|
|
+ }
|
|
|
|
+ t.Logf("Created bogus symlink")
|
|
|
|
+
|
|
|
|
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
|
|
|
+ time.Sleep(500 * time.Millisecond)
|
|
|
|
+
|
|
|
|
+ // Should not be error, just no events for broken links (watching nothing)
|
|
|
|
+ if errorsReceived.value() > 0 {
|
|
|
|
+ t.Fatal("fsnotify errors have been received.")
|
|
|
|
+ }
|
|
|
|
+ if otherEventsReceived.value() > 0 {
|
|
|
|
+ t.Fatal("fsnotify other events received on the broken link")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Except for 1 create event (for the link itself)
|
|
|
|
+ if createEventsReceived.value() == 0 {
|
|
|
|
+ t.Fatal("fsnotify create events were not received after 500 ms")
|
|
|
|
+ }
|
|
|
|
+ if createEventsReceived.value() > 1 {
|
|
|
|
+ t.Fatal("fsnotify more create events received than expected")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Try closing the fsnotify instance
|
|
|
|
+ t.Log("calling Close()")
|
|
|
|
+ watcher.Close()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
|
|
|
|
+// See https://codereview.appspot.com/103300045/
|
|
|
|
+// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
|
|
|
|
+func TestConcurrentRemovalOfWatch(t *testing.T) {
|
|
|
|
+ if runtime.GOOS != "darwin" {
|
|
|
|
+ t.Skip("regression test for race only present on darwin")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Create directory to watch
|
|
|
|
+ testDir := tempMkdir(t)
|
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
|
+
|
|
|
|
+ // Create a file before watching directory
|
|
|
|
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
|
|
|
|
+ {
|
|
|
|
+ var f *os.File
|
|
|
|
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatalf("creating test file failed: %s", err)
|
|
|
|
+ }
|
|
|
|
+ f.Sync()
|
|
|
|
+ f.Close()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ watcher := newWatcher(t)
|
|
|
|
+ defer watcher.Close()
|
|
|
|
+
|
|
|
|
+ addWatch(t, watcher, testDir)
|
|
|
|
+
|
|
|
|
+ // Test that RemoveWatch can be invoked concurrently, with no data races.
|
|
|
|
+ removed1 := make(chan struct{})
|
|
|
|
+ go func() {
|
|
|
|
+ defer close(removed1)
|
|
|
|
+ watcher.Remove(testDir)
|
|
|
|
+ }()
|
|
|
|
+ removed2 := make(chan struct{})
|
|
|
|
+ go func() {
|
|
|
|
+ close(removed2)
|
|
|
|
+ watcher.Remove(testDir)
|
|
|
|
+ }()
|
|
|
|
+ <-removed1
|
|
|
|
+ <-removed2
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func testRename(file1, file2 string) error {
|
|
|
|
+ switch runtime.GOOS {
|
|
|
|
+ case "windows", "plan9":
|
|
|
|
+ return os.Rename(file1, file2)
|
|
|
|
+ default:
|
|
|
|
+ cmd := exec.Command("mv", file1, file2)
|
|
|
|
+ return cmd.Run()
|
|
|
|
+ }
|
|
|
|
+}
|