Skip to content

Commit

Permalink
Refactor integration_darwin_test.go and integration_test.go
Browse files Browse the repository at this point in the history
  • Loading branch information
shogo82148 committed Mar 6, 2024
1 parent 5afea10 commit da086a8
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 111 deletions.
125 changes: 42 additions & 83 deletions integration_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strconv"
"strings"
"testing"
"time"

"golang.org/x/sys/unix"
)
Expand All @@ -25,12 +24,12 @@ func darwinVersion() (int, error) {
return strconv.Atoi(s)
}

// testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS.
// testExchangedataForWatcher tests the watcher with the exchangedata operation
// on macOS. This is widely used for atomic saves on macOS, e.g. TextMate and in
// Apple's NSDocument.
//
// This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument.
//
// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
// https://github.com/textmate/textmate/blob/cd016be2/Frameworks/io/src/swap_file_data.cc#L20
func testExchangedataForWatcher(t *testing.T, watchDir bool) {
osVersion, err := darwinVersion()
if err != nil {
Expand All @@ -40,14 +39,8 @@ func testExchangedataForWatcher(t *testing.T, watchDir bool) {
t.Skip("Exchangedata is deprecated in macOS 10.13")
}

// Create directory to watch
testDir1 := tempMkdir(t)

// For the intermediate file
testDir2 := tempMkdir(t)

defer os.RemoveAll(testDir1)
defer os.RemoveAll(testDir2)
testDir1 := t.TempDir() // Create directory to watch
testDir2 := t.TempDir() // For the intermediate file

resolvedFilename := "TestFsnotifyEvents.file"

Expand All @@ -63,105 +56,71 @@ func testExchangedataForWatcher(t *testing.T, watchDir bool) {
// Make sure we create the file before we start watching
createAndSyncFile(t, resolved)

watcher := newWatcher(t)
w := newCollector(t)
w.collect(t)

// Test both variants in isolation
if watchDir {
addWatch(t, watcher, testDir1)
addWatch(t, w.w, testDir1)
} else {
addWatch(t, watcher, resolved)
addWatch(t, w.w, resolved)
}

// Receive errors on the error channel on a separate goroutine
go func() {
for err := range watcher.Errors {
t.Errorf("error received: %s", err)
}
}()

// Receive events on the event channel on a separate goroutine
eventstream := watcher.Events
var removeReceived counter
var createReceived counter

done := make(chan bool)

go func() {
for event := range eventstream {
// Only count relevant events
if event.Name == filepath.Clean(resolved) {
if event.Op&Remove == Remove {
removeReceived.increment()
}
if event.Op&Create == Create {
createReceived.increment()
}
}
t.Logf("event received: %s", event)
}
done <- true
}()
// Repeat to make sure the watched file/directory "survives" the
// REMOVE/CREATE loop.

// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
for i := 1; i <= 3; i++ {
// The intermediate file is created in a folder outside the watcher
createAndSyncFile(t, intermediate)
createAndSyncFile(t, intermediate) // intermediate file is created outside the watcher

// 1. Swap
if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
if err := unix.Exchangedata(intermediate, resolved, 0); err != nil { // 1. Swap
t.Fatalf("[%d] exchangedata failed: %s", i, err)
}

time.Sleep(50 * time.Millisecond)

// 2. Delete the intermediate file
err := os.Remove(intermediate)

eventSeparator()
err := os.Remove(intermediate) // delete the intermediate file
if err != nil {
t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
}

time.Sleep(50 * time.Millisecond)

eventSeparator()
}

// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
time.Sleep(500 * time.Millisecond)

// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
if removeReceived.value() < 3 {
t.Fatal("fsnotify remove events have not been received after 500 ms")
events := w.stop(t)
var rm, create Events
for _, e := range events {
if e.Op&Create != 0 {
create = append(create, e)
}
if e.Op&Remove != 0 {
rm = append(rm, e)
}
}

if createReceived.value() < 3 {
t.Fatal("fsnotify create events have not been received after 500 ms")
if len(rm) < 3 {
t.Fatalf("less than 3 REMOVE events:\n%s", events)
}
if len(create) < 3 {
t.Fatalf("less than 3 CREATE events:\n%s", events)
}
}

func createAndSyncFile(t *testing.T, filepath string) {
f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating %s failed: %s", filepath, err)

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")
}
f1.Sync()
f1.Close()
}

// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
func TestExchangedataInWatchedDir(t *testing.T) {
t.Parallel()
testExchangedataForWatcher(t, true)
}

// TestExchangedataInWatchedDir test exchangedata operation on watched file.
func TestExchangedataInWatchedFile(t *testing.T) {
t.Parallel()
testExchangedataForWatcher(t, false)
}

func createAndSyncFile(t *testing.T, filepath string) {
f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating %s failed: %s", filepath, err)
}
f1.Sync()
f1.Close()
}
30 changes: 2 additions & 28 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package fsnotify

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
Expand All @@ -18,35 +18,9 @@ import (
"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
}

// tempMkFile makes a temporary file.
func tempMkFile(t *testing.T, dir string) string {
f, err := ioutil.TempFile(dir, "fsnotify")
f, err := os.CreateTemp(dir, "fsnotify")
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
Expand Down

0 comments on commit da086a8

Please sign in to comment.