1 // Copyright 2016 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
13 "golang.org/x/sys/unix"
16 // testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS.
18 // This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument.
20 // See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
21 // Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
22 func testExchangedataForWatcher(t *testing.T, watchDir bool) {
23 // Create directory to watch
24 testDir1 := tempMkdir(t)
26 // For the intermediate file
27 testDir2 := tempMkdir(t)
29 defer os.RemoveAll(testDir1)
30 defer os.RemoveAll(testDir2)
32 resolvedFilename := "TestFsnotifyEvents.file"
36 // 1. exchangedata (intermediate, resolved)
37 // 2. unlink intermediate
39 // Let's try to simulate that:
40 resolved := filepath.Join(testDir1, resolvedFilename)
41 intermediate := filepath.Join(testDir2, resolvedFilename+"~")
43 // Make sure we create the file before we start watching
44 createAndSyncFile(t, resolved)
46 watcher := newWatcher(t)
48 // Test both variants in isolation
50 addWatch(t, watcher, testDir1)
52 addWatch(t, watcher, resolved)
55 // Receive errors on the error channel on a separate goroutine
57 for err := range watcher.Errors {
58 t.Fatalf("error received: %s", err)
62 // Receive events on the event channel on a separate goroutine
63 eventstream := watcher.Events
64 var removeReceived counter
65 var createReceived counter
67 done := make(chan bool)
70 for event := range eventstream {
71 // Only count relevant events
72 if event.Name == filepath.Clean(resolved) {
73 if event.Op&Remove == Remove {
74 removeReceived.increment()
76 if event.Op&Create == Create {
77 createReceived.increment()
80 t.Logf("event received: %s", event)
85 // Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
86 for i := 1; i <= 3; i++ {
87 // The intermediate file is created in a folder outside the watcher
88 createAndSyncFile(t, intermediate)
91 if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
92 t.Fatalf("[%d] exchangedata failed: %s", i, err)
95 time.Sleep(50 * time.Millisecond)
97 // 2. Delete the intermediate file
98 err := os.Remove(intermediate)
101 t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
104 time.Sleep(50 * time.Millisecond)
108 // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
109 time.Sleep(500 * time.Millisecond)
111 // The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
112 if removeReceived.value() < 3 {
113 t.Fatal("fsnotify remove events have not been received after 500 ms")
116 if createReceived.value() < 3 {
117 t.Fatal("fsnotify create events have not been received after 500 ms")
121 t.Log("waiting for the event channel to become closed...")
124 t.Log("event channel closed")
125 case <-time.After(2 * time.Second):
126 t.Fatal("event stream was not closed after 2 seconds")
130 // TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
131 func TestExchangedataInWatchedDir(t *testing.T) {
132 testExchangedataForWatcher(t, true)
135 // TestExchangedataInWatchedDir test exchangedata operation on watched file.
136 func TestExchangedataInWatchedFile(t *testing.T) {
137 testExchangedataForWatcher(t, false)
140 func createAndSyncFile(t *testing.T, filepath string) {
141 f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
143 t.Fatalf("creating %s failed: %s", filepath, err)