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. 4 5package fsnotify 6 7import ( 8 "os" 9 "path/filepath" 10 "testing" 11 "time" 12 13 "golang.org/x/sys/unix" 14) 15 16// testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS. 17// 18// This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument. 19// 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 22func testExchangedataForWatcher(t *testing.T, watchDir bool) { 23 // Create directory to watch 24 testDir1 := tempMkdir(t) 25 26 // For the intermediate file 27 testDir2 := tempMkdir(t) 28 29 defer os.RemoveAll(testDir1) 30 defer os.RemoveAll(testDir2) 31 32 resolvedFilename := "TestFsnotifyEvents.file" 33 34 // TextMate does: 35 // 36 // 1. exchangedata (intermediate, resolved) 37 // 2. unlink intermediate 38 // 39 // Let's try to simulate that: 40 resolved := filepath.Join(testDir1, resolvedFilename) 41 intermediate := filepath.Join(testDir2, resolvedFilename+"~") 42 43 // Make sure we create the file before we start watching 44 createAndSyncFile(t, resolved) 45 46 watcher := newWatcher(t) 47 48 // Test both variants in isolation 49 if watchDir { 50 addWatch(t, watcher, testDir1) 51 } else { 52 addWatch(t, watcher, resolved) 53 } 54 55 // Receive errors on the error channel on a separate goroutine 56 go func() { 57 for err := range watcher.Errors { 58 t.Fatalf("error received: %s", err) 59 } 60 }() 61 62 // Receive events on the event channel on a separate goroutine 63 eventstream := watcher.Events 64 var removeReceived counter 65 var createReceived counter 66 67 done := make(chan bool) 68 69 go func() { 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() 75 } 76 if event.Op&Create == Create { 77 createReceived.increment() 78 } 79 } 80 t.Logf("event received: %s", event) 81 } 82 done <- true 83 }() 84 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) 89 90 // 1. Swap 91 if err := unix.Exchangedata(intermediate, resolved, 0); err != nil { 92 t.Fatalf("[%d] exchangedata failed: %s", i, err) 93 } 94 95 time.Sleep(50 * time.Millisecond) 96 97 // 2. Delete the intermediate file 98 err := os.Remove(intermediate) 99 100 if err != nil { 101 t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err) 102 } 103 104 time.Sleep(50 * time.Millisecond) 105 106 } 107 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) 110 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") 114 } 115 116 if createReceived.value() < 3 { 117 t.Fatal("fsnotify create events have not been received after 500 ms") 118 } 119 120 watcher.Close() 121 t.Log("waiting for the event channel to become closed...") 122 select { 123 case <-done: 124 t.Log("event channel closed") 125 case <-time.After(2 * time.Second): 126 t.Fatal("event stream was not closed after 2 seconds") 127 } 128} 129 130// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir. 131func TestExchangedataInWatchedDir(t *testing.T) { 132 testExchangedataForWatcher(t, true) 133} 134 135// TestExchangedataInWatchedDir test exchangedata operation on watched file. 136func TestExchangedataInWatchedFile(t *testing.T) { 137 testExchangedataForWatcher(t, false) 138} 139 140func createAndSyncFile(t *testing.T, filepath string) { 141 f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666) 142 if err != nil { 143 t.Fatalf("creating %s failed: %s", filepath, err) 144 } 145 f1.Sync() 146 f1.Close() 147} 148