// 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.
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.Watch(dir); err != nil {
t.Fatalf("watcher.Watch(%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.Error {
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.Event
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.IsDelete() {
deleteReceived.increment()
}
if event.IsModify() {
modifyReceived.increment()
}
if event.IsCreate() {
createReceived.increment()
}
if event.IsRename() {
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.Error {
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.Event
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.IsDelete() {
deleteReceived.increment()
}
if event.IsCreate() {
createReceived.increment()
}
if event.IsModify() {
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 *t