github.com/FabianKramm/notify@v0.9.3-0.20210719135015-4705c29227a1/notify_test.go (about) 1 // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. 2 // Use of this source code is governed by the MIT license that can be 3 // found in the LICENSE file. 4 5 // +build darwin linux freebsd dragonfly netbsd openbsd windows solaris 6 7 package notify 8 9 import ( 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "testing" 14 "time" 15 ) 16 17 func TestNotifyExample(t *testing.T) { 18 n := NewNotifyTest(t, "testdata/vfs.txt") 19 defer n.Close() 20 21 ch := NewChans(3) 22 23 // Watch-points can be set explicitly via Watch/Stop calls... 24 n.Watch("src/github.com/rjeczalik/fs", ch[0], Write) 25 n.Watch("src/github.com/pblaszczyk/qttu", ch[0], Write) 26 n.Watch("src/github.com/pblaszczyk/qttu/...", ch[1], Create) 27 n.Watch("src/github.com/rjeczalik/fs/cmd/...", ch[2], Remove) 28 29 cases := []NCase{ 30 // i=0 31 { 32 Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")), 33 Receiver: Chans{ch[0]}, 34 }, 35 // TODO(rjeczalik): #62 36 // i=1 37 // { 38 // Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")), 39 // Receiver: Chans{ch[0]}, 40 // }, 41 // i=2 42 { 43 Event: write(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")), 44 Receiver: nil, 45 }, 46 // i=3 47 { 48 Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swp"), 49 Receiver: Chans{ch[1]}, 50 }, 51 // i=4 52 { 53 Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swo"), 54 Receiver: Chans{ch[1]}, 55 }, 56 // i=5 57 { 58 Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go"), 59 Receiver: Chans{ch[2]}, 60 }, 61 } 62 63 n.ExpectNotifyEvents(cases, ch) 64 65 // ...or using Call structures. 66 stops := [...]Call{ 67 // i=0 68 { 69 F: FuncStop, 70 C: ch[0], 71 }, 72 // i=1 73 { 74 F: FuncStop, 75 C: ch[1], 76 }, 77 } 78 79 n.Call(stops[:]...) 80 81 cases = []NCase{ 82 // i=0 83 { 84 Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")), 85 Receiver: nil, 86 }, 87 // i=1 88 { 89 Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")), 90 Receiver: nil, 91 }, 92 // i=2 93 { 94 Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swr"), 95 Receiver: nil, 96 }, 97 // i=3 98 { 99 Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/main.go"), 100 Receiver: Chans{ch[2]}, 101 }, 102 } 103 104 n.ExpectNotifyEvents(cases, ch) 105 } 106 107 func TestStop(t *testing.T) { 108 t.Skip("TODO(rjeczalik)") 109 } 110 111 func TestRenameInRoot(t *testing.T) { 112 tmpDir, err := ioutil.TempDir("", "notify_test-") 113 if err != nil { 114 t.Fatal(err) 115 } 116 defer os.RemoveAll(tmpDir) 117 118 c := make(chan EventInfo, 100) 119 first := filepath.Join(tmpDir, "foo") 120 second := filepath.Join(tmpDir, "bar") 121 file := filepath.Join(second, "file") 122 123 mustT(t, os.Mkdir(first, 0777)) 124 125 if err := Watch(tmpDir+"/...", c, All); err != nil { 126 t.Fatal(err) 127 } 128 defer Stop(c) 129 130 mustT(t, os.Rename(first, second)) 131 time.Sleep(50 * time.Millisecond) // Need some time to process rename. 132 fd, err := os.Create(file) 133 mustT(t, err) 134 fd.Close() 135 136 timeout := time.After(time.Second) 137 for { 138 select { 139 case ev := <-c: 140 if ev.Path() == file { 141 return 142 } 143 t.Log(ev.Path()) 144 case <-timeout: 145 t.Fatal("timed out before receiving event") 146 } 147 } 148 } 149 150 func prepareTestDir(t *testing.T) string { 151 tmpDir, err := ioutil.TempDir("", "notify_test-") 152 if err != nil { 153 t.Fatal(err) 154 } 155 156 // resolve paths on OSX 157 s, err := filepath.EvalSymlinks(tmpDir) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 // create test dir 163 err = os.MkdirAll(filepath.Join(s, "a/b/c"), 0755) 164 if err != nil { 165 t.Fatal(err) 166 } 167 168 return s 169 } 170 171 func mustWatch(t *testing.T, path string) chan EventInfo { 172 c := make(chan EventInfo, 1) 173 err := Watch(path+"...", c, All) 174 if err != nil { 175 t.Fatal(err) 176 } 177 178 return c 179 } 180 181 func TestAddParentAfterStop(t *testing.T) { 182 tmpDir := prepareTestDir(t) 183 defer os.RemoveAll(tmpDir) 184 185 // watch a child and parent path across multiple channels. 186 // this can happen in any order. 187 ch1 := mustWatch(t, filepath.Join(tmpDir, "a/b")) 188 ch2 := mustWatch(t, filepath.Join(tmpDir, "a/b/c")) 189 defer Stop(ch2) 190 191 // unwatch ./a/b -- this is what causes the panic on the next line. 192 // note that this also fails if we notify.Stop(ch1) instead. 193 Stop(ch1) 194 195 // add parent watchpoint 196 ch3 := mustWatch(t, filepath.Join(tmpDir, "a")) 197 defer Stop(ch3) 198 199 // fire an event 200 filePath := filepath.Join(tmpDir, "a/b/c/d") 201 go func() { _ = ioutil.WriteFile(filePath, []byte("X"), 0664) }() 202 203 timeout := time.After(5 * time.Second) 204 for { 205 select { 206 case ev := <-ch2: 207 t.Log(ev.Path(), ev.Event()) 208 if ev.Path() == filePath && (ev.Event() == Create || ev.Event() == Write) { 209 return 210 } 211 case <-timeout: 212 t.Fatal("timed out before receiving event") 213 } 214 } 215 } 216 217 func TestStopChild(t *testing.T) { 218 tmpDir := prepareTestDir(t) 219 defer os.RemoveAll(tmpDir) 220 221 // watch a child and parent path across multiple channels. 222 // this can happen in any order. 223 ch1 := mustWatch(t, filepath.Join(tmpDir, "a")) 224 defer Stop(ch1) 225 ch2 := mustWatch(t, filepath.Join(tmpDir, "a/b/c")) 226 227 // this leads to tmpDir/a being unwatched 228 Stop(ch2) 229 230 // fire an event 231 filePath := filepath.Join(tmpDir, "a/b/c/d") 232 go func() { _ = ioutil.WriteFile(filePath, []byte("X"), 0664) }() 233 234 timeout := time.After(5 * time.Second) 235 for { 236 select { 237 case ev := <-ch1: 238 t.Log(ev.Path(), ev.Event()) 239 if ev.Path() == filePath && (ev.Event() == Create || ev.Event() == Write) { 240 return 241 } 242 case <-timeout: 243 t.Fatal("timed out before receiving event") 244 } 245 } 246 } 247 248 func TestRecreated(t *testing.T) { 249 tmpDir, err := ioutil.TempDir("", "notify_test-") 250 if err != nil { 251 t.Fatal(err) 252 } 253 defer os.RemoveAll(tmpDir) 254 255 dir := filepath.Join(tmpDir, "folder") 256 file := filepath.Join(dir, "file") 257 258 // Start watching 259 eventChan := make(chan EventInfo, 1000) 260 mustT(t, Watch(tmpDir+"/...", eventChan, All)) 261 defer Stop(eventChan) 262 263 recreateFolder := func() { 264 // Give the sync some time to process events 265 _ = os.RemoveAll(dir) 266 mustT(t, os.Mkdir(dir, 0777)) 267 time.Sleep(100 * time.Millisecond) 268 269 // Create a file 270 mustT(t, ioutil.WriteFile(file, []byte("abc"), 0666)) 271 } 272 timeout := time.After(5 * time.Second) 273 checkCreated := func() { 274 for { 275 select { 276 case ev := <-eventChan: 277 t.Log(ev.Path(), ev.Event()) 278 if ev.Path() == file && ev.Event() == Create { 279 return 280 } 281 case <-timeout: 282 t.Fatal("timed out before receiving event") 283 } 284 } 285 } 286 287 // 1. Create a folder and a file within it 288 // This will create a node in the internal tree for the subfolder test/folder 289 // Will create a new inotify watch for the folder 290 t.Log("######## First ########") 291 recreateFolder() 292 checkCreated() 293 294 // 2. Create a folder and a file within it again 295 // This will set the events for the subfolder test/folder in the internal tree 296 // Will create a new inotify watch for the folder because events differ 297 t.Log("######## Second ########") 298 recreateFolder() 299 checkCreated() 300 301 // 3. Create a folder and a file within it yet again 302 // This time no new inotify watch will be created, because the events 303 // and node already exist in the internal tree and all subsequent events 304 // are lost, hence there is no event for the created file here anymore 305 t.Log("######## Third ########") 306 recreateFolder() 307 checkCreated() 308 } 309 310 func mustT(t testing.TB, err error) { 311 t.Helper() 312 if err != nil { 313 t.Fatal(err) 314 } 315 }