github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/watcher/filenotify/poller_test.go (about) 1 package filenotify 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "testing" 9 "time" 10 11 qt "github.com/frankban/quicktest" 12 "github.com/fsnotify/fsnotify" 13 "github.com/gohugoio/hugo/htesting" 14 ) 15 16 const ( 17 subdir1 = "subdir1" 18 subdir2 = "subdir2" 19 watchWaitTime = 200 * time.Millisecond 20 ) 21 22 var ( 23 isMacOs = runtime.GOOS == "darwin" 24 isWindows = runtime.GOOS == "windows" 25 isCI = htesting.IsCI() 26 ) 27 28 func TestPollerAddRemove(t *testing.T) { 29 c := qt.New(t) 30 w := NewPollingWatcher(watchWaitTime) 31 32 c.Assert(w.Add("foo"), qt.Not(qt.IsNil)) 33 c.Assert(w.Remove("foo"), qt.Not(qt.IsNil)) 34 35 f, err := os.CreateTemp("", "asdf") 36 if err != nil { 37 t.Fatal(err) 38 } 39 c.Cleanup(func() { 40 c.Assert(w.Close(), qt.IsNil) 41 os.Remove(f.Name()) 42 }) 43 c.Assert(w.Add(f.Name()), qt.IsNil) 44 c.Assert(w.Remove(f.Name()), qt.IsNil) 45 46 } 47 48 func TestPollerEvent(t *testing.T) { 49 c := qt.New(t) 50 51 for _, poll := range []bool{true, false} { 52 if !(poll || isMacOs) || isCI { 53 // Only run the fsnotify tests on MacOS locally. 54 continue 55 } 56 method := "fsnotify" 57 if poll { 58 method = "poll" 59 } 60 61 c.Run(fmt.Sprintf("%s, Watch dir", method), func(c *qt.C) { 62 dir, w := preparePollTest(c, poll) 63 subdir := filepath.Join(dir, subdir1) 64 c.Assert(w.Add(subdir), qt.IsNil) 65 66 filename := filepath.Join(subdir, "file1") 67 68 // Write to one file. 69 c.Assert(os.WriteFile(filename, []byte("changed"), 0600), qt.IsNil) 70 71 var expected []fsnotify.Event 72 73 if poll { 74 expected = append(expected, fsnotify.Event{Name: filename, Op: fsnotify.Write}) 75 assertEvents(c, w, expected...) 76 } else { 77 // fsnotify sometimes emits Chmod before Write, 78 // which is hard to test, so skip it here. 79 drainEvents(c, w) 80 } 81 82 // Remove one file. 83 filename = filepath.Join(subdir, "file2") 84 c.Assert(os.Remove(filename), qt.IsNil) 85 assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Remove}) 86 87 // Add one file. 88 filename = filepath.Join(subdir, "file3") 89 c.Assert(os.WriteFile(filename, []byte("new"), 0600), qt.IsNil) 90 assertEvents(c, w, fsnotify.Event{Name: filename, Op: fsnotify.Create}) 91 92 // Remove entire directory. 93 subdir = filepath.Join(dir, subdir2) 94 c.Assert(w.Add(subdir), qt.IsNil) 95 96 c.Assert(os.RemoveAll(subdir), qt.IsNil) 97 98 expected = expected[:0] 99 100 // This looks like a bug in fsnotify on MacOS. There are 101 // 3 files in this directory, yet we get Remove events 102 // for one of them + the directory. 103 if !poll { 104 expected = append(expected, fsnotify.Event{Name: filepath.Join(subdir, "file2"), Op: fsnotify.Remove}) 105 } 106 expected = append(expected, fsnotify.Event{Name: subdir, Op: fsnotify.Remove}) 107 assertEvents(c, w, expected...) 108 109 }) 110 111 c.Run(fmt.Sprintf("%s, Add should not trigger event", method), func(c *qt.C) { 112 dir, w := preparePollTest(c, poll) 113 subdir := filepath.Join(dir, subdir1) 114 w.Add(subdir) 115 assertEvents(c, w) 116 // Create a new sub directory and add it to the watcher. 117 subdir = filepath.Join(dir, subdir1, subdir2) 118 c.Assert(os.Mkdir(subdir, 0777), qt.IsNil) 119 w.Add(subdir) 120 // This should create only one event. 121 assertEvents(c, w, fsnotify.Event{Name: subdir, Op: fsnotify.Create}) 122 }) 123 124 } 125 } 126 127 func TestPollerClose(t *testing.T) { 128 c := qt.New(t) 129 w := NewPollingWatcher(watchWaitTime) 130 f1, err := os.CreateTemp("", "f1") 131 c.Assert(err, qt.IsNil) 132 defer os.Remove(f1.Name()) 133 f2, err := os.CreateTemp("", "f2") 134 c.Assert(err, qt.IsNil) 135 filename1 := f1.Name() 136 filename2 := f2.Name() 137 f1.Close() 138 f2.Close() 139 140 c.Assert(w.Add(filename1), qt.IsNil) 141 c.Assert(w.Add(filename2), qt.IsNil) 142 c.Assert(w.Close(), qt.IsNil) 143 c.Assert(w.Close(), qt.IsNil) 144 c.Assert(os.WriteFile(filename1, []byte("new"), 0600), qt.IsNil) 145 c.Assert(os.WriteFile(filename2, []byte("new"), 0600), qt.IsNil) 146 // No more event as the watchers are closed. 147 assertEvents(c, w) 148 149 f2, err = os.CreateTemp("", "f2") 150 c.Assert(err, qt.IsNil) 151 152 defer os.Remove(f2.Name()) 153 154 c.Assert(w.Add(f2.Name()), qt.Not(qt.IsNil)) 155 156 } 157 158 func TestCheckChange(t *testing.T) { 159 c := qt.New(t) 160 161 dir := prepareTestDirWithSomeFiles(c, "check-change") 162 163 stat := func(s ...string) os.FileInfo { 164 fi, err := os.Stat(filepath.Join(append([]string{dir}, s...)...)) 165 c.Assert(err, qt.IsNil) 166 return fi 167 } 168 169 f0, f1, f2 := stat(subdir2, "file0"), stat(subdir2, "file1"), stat(subdir2, "file2") 170 d1 := stat(subdir1) 171 172 // Note that on Windows, only the 0200 bit (owner writable) of mode is used. 173 c.Assert(os.Chmod(filepath.Join(filepath.Join(dir, subdir2, "file1")), 0400), qt.IsNil) 174 f1_2 := stat(subdir2, "file1") 175 176 c.Assert(os.WriteFile(filepath.Join(filepath.Join(dir, subdir2, "file2")), []byte("changed"), 0600), qt.IsNil) 177 f2_2 := stat(subdir2, "file2") 178 179 c.Assert(checkChange(f0, nil), qt.Equals, fsnotify.Remove) 180 c.Assert(checkChange(nil, f0), qt.Equals, fsnotify.Create) 181 c.Assert(checkChange(f1, f1_2), qt.Equals, fsnotify.Chmod) 182 c.Assert(checkChange(f2, f2_2), qt.Equals, fsnotify.Write) 183 c.Assert(checkChange(nil, nil), qt.Equals, fsnotify.Op(0)) 184 c.Assert(checkChange(d1, f1), qt.Equals, fsnotify.Op(0)) 185 c.Assert(checkChange(f1, d1), qt.Equals, fsnotify.Op(0)) 186 } 187 188 func BenchmarkPoller(b *testing.B) { 189 runBench := func(b *testing.B, item *itemToWatch) { 190 b.ResetTimer() 191 for i := 0; i < b.N; i++ { 192 evs, err := item.checkForChanges() 193 if err != nil { 194 b.Fatal(err) 195 } 196 if len(evs) != 0 { 197 b.Fatal("got events") 198 } 199 200 } 201 202 } 203 204 b.Run("Check for changes in dir", func(b *testing.B) { 205 c := qt.New(b) 206 dir := prepareTestDirWithSomeFiles(c, "bench-check") 207 item, err := newItemToWatch(dir) 208 c.Assert(err, qt.IsNil) 209 runBench(b, item) 210 211 }) 212 213 b.Run("Check for changes in file", func(b *testing.B) { 214 c := qt.New(b) 215 dir := prepareTestDirWithSomeFiles(c, "bench-check-file") 216 filename := filepath.Join(dir, subdir1, "file1") 217 item, err := newItemToWatch(filename) 218 c.Assert(err, qt.IsNil) 219 runBench(b, item) 220 }) 221 222 } 223 224 func prepareTestDirWithSomeFiles(c *qt.C, id string) string { 225 dir := c.TB.TempDir() 226 c.Assert(os.MkdirAll(filepath.Join(dir, subdir1), 0777), qt.IsNil) 227 c.Assert(os.MkdirAll(filepath.Join(dir, subdir2), 0777), qt.IsNil) 228 229 for i := 0; i < 3; i++ { 230 c.Assert(os.WriteFile(filepath.Join(dir, subdir1, fmt.Sprintf("file%d", i)), []byte("hello1"), 0600), qt.IsNil) 231 } 232 233 for i := 0; i < 3; i++ { 234 c.Assert(os.WriteFile(filepath.Join(dir, subdir2, fmt.Sprintf("file%d", i)), []byte("hello2"), 0600), qt.IsNil) 235 } 236 237 c.Cleanup(func() { 238 os.RemoveAll(dir) 239 }) 240 241 return dir 242 } 243 244 func preparePollTest(c *qt.C, poll bool) (string, FileWatcher) { 245 var w FileWatcher 246 if poll { 247 w = NewPollingWatcher(watchWaitTime) 248 } else { 249 var err error 250 w, err = NewEventWatcher() 251 c.Assert(err, qt.IsNil) 252 } 253 254 dir := prepareTestDirWithSomeFiles(c, fmt.Sprint(poll)) 255 256 c.Cleanup(func() { 257 w.Close() 258 }) 259 return dir, w 260 } 261 262 func assertEvents(c *qt.C, w FileWatcher, evs ...fsnotify.Event) { 263 c.Helper() 264 i := 0 265 check := func() error { 266 for { 267 select { 268 case got := <-w.Events(): 269 if i > len(evs)-1 { 270 return fmt.Errorf("got too many event(s): %q", got) 271 } 272 expected := evs[i] 273 i++ 274 if expected.Name != got.Name { 275 return fmt.Errorf("got wrong filename, expected %q: %v", expected.Name, got.Name) 276 } else if got.Op&expected.Op != expected.Op { 277 return fmt.Errorf("got wrong event type, expected %q: %v", expected.Op, got.Op) 278 } 279 case e := <-w.Errors(): 280 return fmt.Errorf("got unexpected error waiting for events %v", e) 281 case <-time.After(watchWaitTime + (watchWaitTime / 2)): 282 return nil 283 } 284 } 285 } 286 c.Assert(check(), qt.IsNil) 287 c.Assert(i, qt.Equals, len(evs)) 288 } 289 290 func drainEvents(c *qt.C, w FileWatcher) { 291 c.Helper() 292 check := func() error { 293 for { 294 select { 295 case <-w.Events(): 296 case e := <-w.Errors(): 297 return fmt.Errorf("got unexpected error waiting for events %v", e) 298 case <-time.After(watchWaitTime * 2): 299 return nil 300 } 301 } 302 } 303 c.Assert(check(), qt.IsNil) 304 }