github.com/qubitproducts/logspray@v0.2.14/sources/filesystem/filewatcher_test.go (about) 1 // Copyright 2016 Qubit Digital Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 // Package logspray is a collection of tools for streaming and indexing 14 // large volumes of dynamic logs. 15 16 package filesystem 17 18 import ( 19 "context" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "regexp" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/QubitProducts/logspray/sources" 30 ) 31 32 func TestFileSystemWatcher_Next1(t *testing.T) { 33 fsts := []struct { 34 fstest 35 watcher *Watcher 36 expect []map[string]sources.Update 37 }{ 38 { 39 fstest: fstest{ 40 preactions: []taction{}, 41 }, 42 watcher: &Watcher{}, 43 expect: []map[string]sources.Update{{}}, 44 }, 45 { 46 fstest: fstest{ 47 preactions: []taction{ 48 createFile("file1"), 49 }, 50 }, 51 watcher: &Watcher{}, 52 expect: []map[string]sources.Update{ 53 { 54 "file1": sources.Update{Action: sources.Add}, 55 }, 56 }, 57 }, 58 { 59 fstest: fstest{ 60 subdirs: []string{"dir1"}, 61 preactions: []taction{ 62 createFile("file1"), 63 createFile("dir1/ignored"), 64 }, 65 }, 66 watcher: &Watcher{}, 67 expect: []map[string]sources.Update{ 68 { 69 "file1": sources.Update{Action: sources.Add}, 70 }, 71 }, 72 }, 73 { 74 fstest: fstest{ 75 subdirs: []string{"dir1/dir2"}, 76 preactions: []taction{ 77 createFile("file1"), 78 createFile("dir1/dir2/file1"), 79 }, 80 }, 81 watcher: &Watcher{ 82 Recur: true, 83 }, 84 expect: []map[string]sources.Update{ 85 { 86 "file1": sources.Update{Action: sources.Add}, 87 "dir1/dir2/file1": sources.Update{Action: sources.Add}, 88 }, 89 }, 90 }, 91 { 92 fstest: fstest{ 93 actions: []taction{ 94 createFile("file1"), 95 }, 96 }, 97 watcher: &Watcher{}, 98 expect: []map[string]sources.Update{ 99 {}, 100 { 101 "file1": sources.Update{Action: sources.Add}, 102 }, 103 }, 104 }, 105 { 106 fstest: fstest{ 107 actions: []taction{ 108 createFile("file1"), 109 }, 110 }, 111 watcher: &Watcher{}, 112 expect: []map[string]sources.Update{ 113 {}, 114 { 115 "file1": sources.Update{Action: sources.Add}, 116 }, 117 }, 118 }, 119 { 120 fstest: fstest{ 121 actions: []taction{ 122 createFile("file1"), 123 createFile("file2"), 124 createFile("file3"), 125 createFile("file4"), 126 }, 127 }, 128 watcher: &Watcher{ 129 Recur: true, 130 }, 131 expect: []map[string]sources.Update{ 132 {}, 133 {"file1": sources.Update{Action: sources.Add}}, 134 {"file2": sources.Update{Action: sources.Add}}, 135 {"file3": sources.Update{Action: sources.Add}}, 136 {"file4": sources.Update{Action: sources.Add}}, 137 }, 138 }, 139 { 140 fstest: fstest{ 141 actions: []taction{ 142 createDir("dir1/dir2/dir3"), 143 createFile("dir1/dir2/dir3/file1"), 144 }, 145 }, 146 watcher: &Watcher{ 147 Recur: true, 148 }, 149 expect: []map[string]sources.Update{ 150 {}, 151 {"dir1": sources.Update{Action: sources.Add}}, 152 { 153 "dir1/dir2/dir3/file1": sources.Update{Action: sources.Add}, 154 }, 155 }, 156 }, 157 { 158 fstest: fstest{ 159 actions: []taction{ 160 createDir("dir1/dir2/dir3"), 161 createFile("dir1/dir2/dir3/file1"), 162 }, 163 }, 164 watcher: &Watcher{ 165 Recur: true, 166 NameRegexp: regexp.MustCompile("file1"), 167 }, 168 expect: []map[string]sources.Update{ 169 {}, 170 { 171 "dir1/dir2/dir3/file1": sources.Update{Action: sources.Add}, 172 }, 173 }, 174 }, 175 } 176 177 for i, fst := range fsts { 178 t.Run(fmt.Sprintf("%s/%d", t.Name(), i), func(t *testing.T) { 179 fst.run(t, func(ctx context.Context, t *testing.T, start func(context.Context, *testing.T)) { 180 var err error 181 w := fst.watcher 182 w.Path, err = os.Getwd() 183 if err != nil { 184 t.Skip(err) 185 return 186 } 187 188 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 189 //defer cancel() 190 ius, err := w.Next(ctx) 191 if err != nil { 192 t.Fatalf("Initial Next() got err = %v", err) 193 return 194 } 195 196 if len(ius) != len(fst.expect[0]) { 197 t.Fatalf("wrong initial file count expected = %d, got = %d", len(fst.expect[0]), len(ius)) 198 } 199 for _, u := range ius { 200 t.Logf("got update %v %s", u.Action, u.Target) 201 if u.Action != sources.Add { 202 t.Fatalf("Initial events should only be sources.Add") 203 } 204 if !strings.HasPrefix(u.Target, w.Path) { 205 t.Fatalf("File update for file not in path path = %s, got = %s", w.Path, u.Target) 206 } 207 p := u.Target[len(w.Path)+1:] 208 if _, ok := fst.expect[0][p]; !ok { 209 t.Fatalf("Got unexpected event path = %s", p) 210 } 211 } 212 213 start(ctx, t) 214 215 for i := range fst.expect[1:] { 216 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 217 us, err := w.Next(ctx) 218 if err != nil { 219 t.Fatalf("intermediate Next() got err = %v", err) 220 return 221 } 222 cancel() 223 224 for _, u := range us { 225 t.Logf("got update %v %s", u.Action, u.Target) 226 if !strings.HasPrefix(u.Target, w.Path) { 227 t.Fatalf("Intermediate update for file not in path path = %s, got = %s", w.Path, u.Target) 228 } 229 p := u.Target[len(w.Path)+1:] 230 if _, ok := fst.expect[i+1][p]; !ok { 231 t.Fatalf("Got intermediate unexpected event path = %s; expecting = %v", p, fst.expect[i+1]) 232 } 233 if u.Action != fst.expect[i+1][p].Action { 234 t.Fatalf("Got wrong action want = %v , got = %v", fst.expect[i+1][p].Action, u.Action) 235 } 236 } 237 } 238 239 // We'll wait a bit and see if we get any extra events 240 ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond) 241 fus, err := w.Next(ctx) 242 defer cancel() 243 if err == nil { 244 for _, u := range fus { 245 t.Logf("unexpected u = %v, f = %s", u.Action, u.Target) 246 } 247 t.Fatal("Got unexpected additional updates") 248 return 249 } 250 }) 251 }) 252 } 253 } 254 255 type taction func(context.Context, *testing.T) 256 257 type tactions []taction 258 259 func (as tactions) run(ctx context.Context, t *testing.T) { 260 for _, a := range as { 261 a(ctx, t) 262 // we lose notification events if they happen too quickly 263 time.Sleep(1 * time.Millisecond) 264 } 265 return 266 } 267 268 type fstest struct { 269 subdirs []string 270 preactions tactions 271 actions tactions 272 } 273 274 func (fst fstest) run(t *testing.T, tf func(context.Context, *testing.T, func(context.Context, *testing.T))) { 275 dir, err := ioutil.TempDir("", "fstest") 276 if err != nil { 277 t.Skip(err) 278 return 279 } 280 //defer os.RemoveAll(dir) // clean up 281 282 for _, d := range fst.subdirs { 283 path := filepath.Join(dir, d) 284 err = os.MkdirAll(path, 0777) 285 if err != nil { 286 t.Skip(err) 287 return 288 } 289 } 290 291 ctx, cancel := context.WithCancel(context.Background()) 292 defer cancel() 293 294 if err := os.Chdir(dir); err != nil { 295 t.Skip(err) 296 return 297 } 298 299 fst.preactions.run(ctx, t) 300 tf(ctx, t, fst.actions.run) 301 } 302 303 func createFile(fn string) taction { 304 return func(ctx context.Context, t *testing.T) { 305 t.Logf("create file %v", fn) 306 if _, err := os.Create(fn); err != nil { 307 t.Skip(err) 308 } 309 } 310 } 311 312 func removeFile(fn string) taction { 313 return func(ctx context.Context, t *testing.T) { 314 t.Logf("removing file %v", fn) 315 if err := os.Remove(fn); err != nil { 316 t.Skip(err) 317 } 318 } 319 } 320 321 func createDir(fn string) taction { 322 return func(ctx context.Context, t *testing.T) { 323 t.Logf("creating dir %v", fn) 324 if err := os.MkdirAll(fn, 0777); err != nil { 325 t.Skip(err) 326 } 327 } 328 } 329 330 func removeDir(fn string) taction { 331 return func(ctx context.Context, t *testing.T) { 332 t.Logf("removing dir %v", fn) 333 if err := os.RemoveAll(fn); err != nil { 334 t.Skip(err) 335 } 336 } 337 } 338 339 func pause(d time.Duration) taction { 340 return func(ctx context.Context, t *testing.T) { 341 t.Logf("pausing for %v", d) 342 select { 343 case <-ctx.Done(): 344 t.Skip(ctx.Err()) 345 case <-time.After(d): 346 } 347 } 348 } 349 350 func noop() taction { 351 return func(ctx context.Context, t *testing.T) { 352 t.Logf("noop") 353 return 354 } 355 }