blob: 15c3728b1ca08bcab83aaefd7c625f1d33deb3f4 [file] [log] [blame]
Jeff Gastonf1fd45e2017-08-09 18:25:28 -07001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package finder
16
17import (
18 "fmt"
Jeff Gastonb629e182017-08-14 16:49:18 -070019 "io/ioutil"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070020 "log"
Jeff Gastonb629e182017-08-14 16:49:18 -070021 "os"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070022 "path/filepath"
23 "reflect"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070024 "sort"
Jeff Gastonb629e182017-08-14 16:49:18 -070025 "testing"
26 "time"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070027
28 "android/soong/fs"
29 "runtime/debug"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070030)
31
32// some utils for tests to use
33func newFs() *fs.MockFs {
34 return fs.NewMockFs(map[string][]byte{})
35}
36
37func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
Jeff Gastonb629e182017-08-14 16:49:18 -070038 f, err := newFinderAndErr(t, filesystem, cacheParams)
39 if err != nil {
40 fatal(t, err.Error())
41 }
42 return f
43}
44
45func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) (*Finder, error) {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070046 cachePath := "/finder/finder-db"
47 cacheDir := filepath.Dir(cachePath)
48 filesystem.MkDirs(cacheDir)
49 if cacheParams.WorkingDirectory == "" {
50 cacheParams.WorkingDirectory = "/cwd"
51 }
52
53 logger := log.New(ioutil.Discard, "", 0)
Jeff Gastonb629e182017-08-14 16:49:18 -070054 f, err := New(cacheParams, filesystem, logger, cachePath)
55 return f, err
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070056}
57
58func finderWithSameParams(t *testing.T, original *Finder) *Finder {
Jeff Gastonb629e182017-08-14 16:49:18 -070059 f, err := finderAndErrorWithSameParams(t, original)
60 if err != nil {
61 fatal(t, err.Error())
62 }
63 return f
64}
65
66func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) {
67 f, err := New(
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070068 original.cacheMetadata.Config.CacheParams,
69 original.filesystem,
70 original.logger,
71 original.DbPath)
Jeff Gastonb629e182017-08-14 16:49:18 -070072 return f, err
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070073}
74
75func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
76 parent := filepath.Dir(path)
77 filesystem.MkDirs(parent)
78 err := filesystem.WriteFile(path, []byte(content), 0777)
79 if err != nil {
Jeff Gastonb629e182017-08-14 16:49:18 -070080 fatal(t, err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070081 }
82}
83
84func create(t *testing.T, path string, filesystem *fs.MockFs) {
85 write(t, path, "hi", filesystem)
86}
87
88func delete(t *testing.T, path string, filesystem *fs.MockFs) {
89 err := filesystem.Remove(path)
90 if err != nil {
Jeff Gastonb629e182017-08-14 16:49:18 -070091 fatal(t, err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070092 }
93}
94
95func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
96 err := filesystem.RemoveAll(path)
97 if err != nil {
Jeff Gastonb629e182017-08-14 16:49:18 -070098 fatal(t, err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070099 }
100}
101
102func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
103 err := filesystem.Rename(oldPath, newPath)
104 if err != nil {
Jeff Gastonb629e182017-08-14 16:49:18 -0700105 fatal(t, err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700106 }
107}
108
109func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) {
110 parentPath := filepath.Dir(newPath)
111 err := filesystem.MkDirs(parentPath)
112 if err != nil {
113 t.Fatal(err.Error())
114 }
115 err = filesystem.Symlink(oldPath, newPath)
116 if err != nil {
Jeff Gastonb629e182017-08-14 16:49:18 -0700117 fatal(t, err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700118 }
119}
120func read(t *testing.T, path string, filesystem *fs.MockFs) string {
121 reader, err := filesystem.Open(path)
122 if err != nil {
123 t.Fatalf(err.Error())
124 }
125 bytes, err := ioutil.ReadAll(reader)
126 if err != nil {
127 t.Fatal(err.Error())
128 }
129 return string(bytes)
130}
131func modTime(t *testing.T, path string, filesystem *fs.MockFs) time.Time {
132 stats, err := filesystem.Lstat(path)
133 if err != nil {
134 t.Fatal(err.Error())
135 }
136 return stats.ModTime()
137}
138func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs) {
139 err := filesystem.SetReadable(path, readable)
140 if err != nil {
141 t.Fatal(err.Error())
142 }
143}
Jeff Gastonb629e182017-08-14 16:49:18 -0700144
145func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) {
146 err := filesystem.SetReadErr(path, readErr)
147 if err != nil {
148 t.Fatal(err.Error())
149 }
150}
151
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700152func fatal(t *testing.T, message string) {
153 t.Error(message)
154 debug.PrintStack()
155 t.FailNow()
156}
Jeff Gastonb629e182017-08-14 16:49:18 -0700157
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700158func assertSameResponse(t *testing.T, actual []string, expected []string) {
159 sort.Strings(actual)
160 sort.Strings(expected)
161 if !reflect.DeepEqual(actual, expected) {
162 fatal(
163 t,
164 fmt.Sprintf(
165 "Expected Finder to return these %v paths:\n %v,\ninstead returned these %v paths: %v\n",
166 len(expected), expected, len(actual), actual),
167 )
168 }
169}
170
171func assertSameStatCalls(t *testing.T, actual []string, expected []string) {
172 sort.Strings(actual)
173 sort.Strings(expected)
174
175 if !reflect.DeepEqual(actual, expected) {
176 fatal(
177 t,
178 fmt.Sprintf(
179 "Finder made incorrect Stat calls.\n"+
180 "Actual:\n"+
181 "%v\n"+
182 "Expected:\n"+
183 "%v\n"+
184 "\n",
185 actual, expected),
186 )
187 }
188}
189func assertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
190 sort.Strings(actual)
191 sort.Strings(expected)
192
193 if !reflect.DeepEqual(actual, expected) {
194 fatal(
195 t,
196 fmt.Sprintf(
197 "Finder made incorrect ReadDir calls.\n"+
198 "Actual:\n"+
199 "%v\n"+
200 "Expected:\n"+
201 "%v\n"+
202 "\n",
203 actual, expected),
204 )
205 }
206}
207
208// runSimpleTests creates a few files, searches for findme.txt, and checks for the expected matches
209func runSimpleTest(t *testing.T, existentPaths []string, expectedMatches []string) {
210 filesystem := newFs()
211 root := "/tmp"
212 filesystem.MkDirs(root)
213 for _, path := range existentPaths {
214 create(t, filepath.Join(root, path), filesystem)
215 }
216
217 finder := newFinder(t,
218 filesystem,
219 CacheParams{
220 "/cwd",
221 []string{root},
222 nil,
223 nil,
224 []string{"findme.txt", "skipme.txt"},
225 },
226 )
227 defer finder.Shutdown()
228
229 foundPaths := finder.FindNamedAt(root, "findme.txt")
230 absoluteMatches := []string{}
231 for i := range expectedMatches {
232 absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i]))
233 }
234 assertSameResponse(t, foundPaths, absoluteMatches)
235}
236
237// end of utils, start of individual tests
238
239func TestSingleFile(t *testing.T) {
240 runSimpleTest(t,
241 []string{"findme.txt"},
242 []string{"findme.txt"},
243 )
244}
245
246func TestIncludeFiles(t *testing.T) {
247 runSimpleTest(t,
248 []string{"findme.txt", "skipme.txt"},
249 []string{"findme.txt"},
250 )
251}
252
253func TestNestedDirectories(t *testing.T) {
254 runSimpleTest(t,
255 []string{"findme.txt", "skipme.txt", "subdir/findme.txt", "subdir/skipme.txt"},
256 []string{"findme.txt", "subdir/findme.txt"},
257 )
258}
259
260func TestEmptyDirectory(t *testing.T) {
261 runSimpleTest(t,
262 []string{},
263 []string{},
264 )
265}
266
267func TestEmptyPath(t *testing.T) {
268 filesystem := newFs()
269 root := "/tmp"
270 create(t, filepath.Join(root, "findme.txt"), filesystem)
271
272 finder := newFinder(
273 t,
274 filesystem,
275 CacheParams{
276 RootDirs: []string{root},
277 IncludeFiles: []string{"findme.txt", "skipme.txt"},
278 },
279 )
280 defer finder.Shutdown()
281
282 foundPaths := finder.FindNamedAt("", "findme.txt")
283
284 assertSameResponse(t, foundPaths, []string{})
285}
286
287func TestFilesystemRoot(t *testing.T) {
288 filesystem := newFs()
289 root := "/"
290 createdPath := "/findme.txt"
291 create(t, createdPath, filesystem)
292
293 finder := newFinder(
294 t,
295 filesystem,
296 CacheParams{
297 RootDirs: []string{root},
298 IncludeFiles: []string{"findme.txt", "skipme.txt"},
299 },
300 )
301 defer finder.Shutdown()
302
303 foundPaths := finder.FindNamedAt(root, "findme.txt")
304
305 assertSameResponse(t, foundPaths, []string{createdPath})
306}
307
Jeff Gastonb629e182017-08-14 16:49:18 -0700308func TestNonexistentDir(t *testing.T) {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700309 filesystem := newFs()
310 create(t, "/tmp/findme.txt", filesystem)
311
Jeff Gastonb629e182017-08-14 16:49:18 -0700312 _, err := newFinderAndErr(
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700313 t,
314 filesystem,
315 CacheParams{
316 RootDirs: []string{"/tmp/IDontExist"},
317 IncludeFiles: []string{"findme.txt", "skipme.txt"},
318 },
319 )
Jeff Gastonb629e182017-08-14 16:49:18 -0700320 if err == nil {
321 fatal(t, "Did not fail when given a nonexistent root directory")
322 }
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700323}
324
325func TestExcludeDirs(t *testing.T) {
326 filesystem := newFs()
327 create(t, "/tmp/exclude/findme.txt", filesystem)
328 create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
329 create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
330 create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
331 create(t, "/tmp/subdir/findme.txt", filesystem)
332 create(t, "/tmp/findme.txt", filesystem)
333
334 finder := newFinder(
335 t,
336 filesystem,
337 CacheParams{
338 RootDirs: []string{"/tmp"},
339 ExcludeDirs: []string{"exclude"},
340 IncludeFiles: []string{"findme.txt", "skipme.txt"},
341 },
342 )
343 defer finder.Shutdown()
344
345 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
346
347 assertSameResponse(t, foundPaths,
348 []string{"/tmp/findme.txt",
349 "/tmp/subdir/findme.txt",
350 "/tmp/subdir/subdir/findme.txt"})
351}
352
353func TestPruneFiles(t *testing.T) {
354 filesystem := newFs()
355 create(t, "/tmp/out/findme.txt", filesystem)
356 create(t, "/tmp/out/.ignore-out-dir", filesystem)
357 create(t, "/tmp/out/child/findme.txt", filesystem)
358
359 create(t, "/tmp/out2/.ignore-out-dir", filesystem)
360 create(t, "/tmp/out2/sub/findme.txt", filesystem)
361
362 create(t, "/tmp/findme.txt", filesystem)
363 create(t, "/tmp/include/findme.txt", filesystem)
364
365 finder := newFinder(
366 t,
367 filesystem,
368 CacheParams{
369 RootDirs: []string{"/tmp"},
370 PruneFiles: []string{".ignore-out-dir"},
371 IncludeFiles: []string{"findme.txt"},
372 },
373 )
374 defer finder.Shutdown()
375
376 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
377
378 assertSameResponse(t, foundPaths,
379 []string{"/tmp/findme.txt",
380 "/tmp/include/findme.txt"})
381}
382
383func TestRootDir(t *testing.T) {
384 filesystem := newFs()
385 create(t, "/tmp/a/findme.txt", filesystem)
386 create(t, "/tmp/a/subdir/findme.txt", filesystem)
387 create(t, "/tmp/b/findme.txt", filesystem)
388 create(t, "/tmp/b/subdir/findme.txt", filesystem)
389
390 finder := newFinder(
391 t,
392 filesystem,
393 CacheParams{
394 RootDirs: []string{"/tmp/a"},
395 IncludeFiles: []string{"findme.txt"},
396 },
397 )
398 defer finder.Shutdown()
399
400 foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
401
402 assertSameResponse(t, foundPaths,
403 []string{"/tmp/a/findme.txt",
404 "/tmp/a/subdir/findme.txt"})
405}
406
407func TestUncachedDir(t *testing.T) {
408 filesystem := newFs()
409 create(t, "/tmp/a/findme.txt", filesystem)
410 create(t, "/tmp/a/subdir/findme.txt", filesystem)
411 create(t, "/tmp/b/findme.txt", filesystem)
412 create(t, "/tmp/b/subdir/findme.txt", filesystem)
413
414 finder := newFinder(
415 t,
416 filesystem,
417 CacheParams{
Jeff Gastonb629e182017-08-14 16:49:18 -0700418 RootDirs: []string{"/tmp/b"},
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700419 IncludeFiles: []string{"findme.txt"},
420 },
421 )
422
423 foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
424 // If the caller queries for a file that is in the cache, then computing the
425 // correct answer won't be fast, and it would be easy for the caller to
426 // fail to notice its slowness. Instead, we only ever search the cache for files
427 // to return, which enforces that we can determine which files will be
428 // interesting upfront.
429 assertSameResponse(t, foundPaths, []string{})
430
431 finder.Shutdown()
432}
433
434func TestSearchingForFilesExcludedFromCache(t *testing.T) {
435 // setup filesystem
436 filesystem := newFs()
437 create(t, "/tmp/findme.txt", filesystem)
438 create(t, "/tmp/a/findme.txt", filesystem)
439 create(t, "/tmp/a/misc.txt", filesystem)
440
441 // set up the finder and run it
442 finder := newFinder(
443 t,
444 filesystem,
445 CacheParams{
446 RootDirs: []string{"/tmp"},
447 IncludeFiles: []string{"findme.txt"},
448 },
449 )
450 foundPaths := finder.FindNamedAt("/tmp", "misc.txt")
451 // If the caller queries for a file that is in the cache, then computing the
452 // correct answer won't be fast, and it would be easy for the caller to
453 // fail to notice its slowness. Instead, we only ever search the cache for files
454 // to return, which enforces that we can determine which files will be
455 // interesting upfront.
456 assertSameResponse(t, foundPaths, []string{})
457
458 finder.Shutdown()
459}
460
461func TestRelativeFilePaths(t *testing.T) {
462 filesystem := newFs()
463
464 create(t, "/tmp/ignore/hi.txt", filesystem)
465 create(t, "/tmp/include/hi.txt", filesystem)
466 create(t, "/cwd/hi.txt", filesystem)
467 create(t, "/cwd/a/hi.txt", filesystem)
468 create(t, "/cwd/a/a/hi.txt", filesystem)
469
470 finder := newFinder(
471 t,
472 filesystem,
473 CacheParams{
474 RootDirs: []string{"/cwd", "/tmp/include"},
475 IncludeFiles: []string{"hi.txt"},
476 },
477 )
478 defer finder.Shutdown()
479
480 foundPaths := finder.FindNamedAt("a", "hi.txt")
481 assertSameResponse(t, foundPaths,
482 []string{"a/hi.txt",
483 "a/a/hi.txt"})
484
485 foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
486 assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
487
488 foundPaths = finder.FindNamedAt(".", "hi.txt")
489 assertSameResponse(t, foundPaths,
490 []string{"hi.txt",
491 "a/hi.txt",
492 "a/a/hi.txt"})
493
494 foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
495 assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
496}
497
498// have to run this test with the race-detector (`go test -race src/android/soong/finder/*.go`)
499// for there to be much chance of the test actually detecting any error that may be present
500func TestRootDirsContainedInOtherRootDirs(t *testing.T) {
501 filesystem := newFs()
502
503 create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
504
505 finder := newFinder(
506 t,
507 filesystem,
508 CacheParams{
Jeff Gastonb629e182017-08-14 16:49:18 -0700509 RootDirs: []string{"/", "/tmp/a/b/c", "/tmp/a/b/c/d/e/f", "/tmp/a/b/c/d/e/f/g/h/i"},
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700510 IncludeFiles: []string{"findme.txt"},
511 },
512 )
513 defer finder.Shutdown()
514
515 foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
516
517 assertSameResponse(t, foundPaths,
518 []string{"/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt"})
519}
520
521func TestFindFirst(t *testing.T) {
522 filesystem := newFs()
523 create(t, "/tmp/a/hi.txt", filesystem)
524 create(t, "/tmp/b/hi.txt", filesystem)
525 create(t, "/tmp/b/a/hi.txt", filesystem)
526
527 finder := newFinder(
528 t,
529 filesystem,
530 CacheParams{
531 RootDirs: []string{"/tmp"},
532 IncludeFiles: []string{"hi.txt"},
533 },
534 )
535 defer finder.Shutdown()
536
537 foundPaths := finder.FindFirstNamed("hi.txt")
538
539 assertSameResponse(t, foundPaths,
540 []string{"/tmp/a/hi.txt",
541 "/tmp/b/hi.txt"},
542 )
543}
544
545func TestConcurrentFindSameDirectory(t *testing.T) {
546 filesystem := newFs()
547
548 // create a bunch of files and directories
549 paths := []string{}
550 for i := 0; i < 10; i++ {
551 parentDir := fmt.Sprintf("/tmp/%v", i)
552 for j := 0; j < 10; j++ {
553 filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
554 paths = append(paths, filePath)
555 }
556 }
557 sort.Strings(paths)
558 for _, path := range paths {
559 create(t, path, filesystem)
560 }
561
562 // set up a finder
563 finder := newFinder(
564 t,
565 filesystem,
566 CacheParams{
567 RootDirs: []string{"/tmp"},
568 IncludeFiles: []string{"findme.txt"},
569 },
570 )
571 defer finder.Shutdown()
572
573 numTests := 20
574 results := make(chan []string, numTests)
575 // make several parallel calls to the finder
576 for i := 0; i < numTests; i++ {
577 go func() {
578 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
579 results <- foundPaths
580 }()
581 }
582
583 // check that each response was correct
584 for i := 0; i < numTests; i++ {
585 foundPaths := <-results
586 assertSameResponse(t, foundPaths, paths)
587 }
588}
589
590func TestConcurrentFindDifferentDirectories(t *testing.T) {
591 filesystem := newFs()
592
593 // create a bunch of files and directories
594 allFiles := []string{}
595 numSubdirs := 10
596 rootPaths := []string{}
597 queryAnswers := [][]string{}
598 for i := 0; i < numSubdirs; i++ {
599 parentDir := fmt.Sprintf("/tmp/%v", i)
600 rootPaths = append(rootPaths, parentDir)
601 queryAnswers = append(queryAnswers, []string{})
602 for j := 0; j < 10; j++ {
603 filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
604 queryAnswers[i] = append(queryAnswers[i], filePath)
605 allFiles = append(allFiles, filePath)
606 }
607 sort.Strings(queryAnswers[i])
608 }
609 sort.Strings(allFiles)
610 for _, path := range allFiles {
611 create(t, path, filesystem)
612 }
613
614 // set up a finder
615 finder := newFinder(
616 t,
617 filesystem,
618
619 CacheParams{
620 RootDirs: []string{"/tmp"},
621 IncludeFiles: []string{"findme.txt"},
622 },
623 )
624 defer finder.Shutdown()
625
626 type testRun struct {
627 path string
628 foundMatches []string
629 correctMatches []string
630 }
631
632 numTests := numSubdirs + 1
633 testRuns := make(chan testRun, numTests)
634
635 searchAt := func(path string, correctMatches []string) {
636 foundPaths := finder.FindNamedAt(path, "findme.txt")
637 testRuns <- testRun{path, foundPaths, correctMatches}
638 }
639
640 // make several parallel calls to the finder
641 go searchAt("/tmp", allFiles)
642 for i := 0; i < len(rootPaths); i++ {
643 go searchAt(rootPaths[i], queryAnswers[i])
644 }
645
646 // check that each response was correct
647 for i := 0; i < numTests; i++ {
648 testRun := <-testRuns
649 assertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
650 }
651}
652
653func TestStrangelyFormattedPaths(t *testing.T) {
654 filesystem := newFs()
655
656 create(t, "/tmp/findme.txt", filesystem)
657 create(t, "/tmp/a/findme.txt", filesystem)
658 create(t, "/tmp/b/findme.txt", filesystem)
659
660 finder := newFinder(
661 t,
662 filesystem,
663 CacheParams{
664 RootDirs: []string{"//tmp//a//.."},
665 IncludeFiles: []string{"findme.txt"},
666 },
667 )
668 defer finder.Shutdown()
669
670 foundPaths := finder.FindNamedAt("//tmp//a//..", "findme.txt")
671
672 assertSameResponse(t, foundPaths,
673 []string{"/tmp/a/findme.txt",
674 "/tmp/b/findme.txt",
675 "/tmp/findme.txt"})
676}
677
678func TestCorruptedCacheHeader(t *testing.T) {
679 filesystem := newFs()
680
681 create(t, "/tmp/findme.txt", filesystem)
682 create(t, "/tmp/a/findme.txt", filesystem)
683 write(t, "/finder/finder-db", "sample header", filesystem)
684
685 finder := newFinder(
686 t,
687 filesystem,
688 CacheParams{
689 RootDirs: []string{"/tmp"},
690 IncludeFiles: []string{"findme.txt"},
691 },
692 )
693 defer finder.Shutdown()
694
695 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
696
697 assertSameResponse(t, foundPaths,
698 []string{"/tmp/a/findme.txt",
699 "/tmp/findme.txt"})
700}
701
702func TestCanUseCache(t *testing.T) {
703 // setup filesystem
704 filesystem := newFs()
705 create(t, "/tmp/findme.txt", filesystem)
706 create(t, "/tmp/a/findme.txt", filesystem)
707
708 // run the first finder
709 finder := newFinder(
710 t,
711 filesystem,
712 CacheParams{
713 RootDirs: []string{"/tmp"},
714 IncludeFiles: []string{"findme.txt"},
715 },
716 )
717 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
718 // check the response of the first finder
719 correctResponse := []string{"/tmp/a/findme.txt",
720 "/tmp/findme.txt"}
721 assertSameResponse(t, foundPaths, correctResponse)
722 finder.Shutdown()
723
724 // check results
725 cacheText := read(t, finder.DbPath, filesystem)
726 if len(cacheText) < 1 {
727 t.Fatalf("saved cache db is empty\n")
728 }
729 if len(filesystem.StatCalls) == 0 {
730 t.Fatal("No Stat calls recorded by mock filesystem")
731 }
732 if len(filesystem.ReadDirCalls) == 0 {
733 t.Fatal("No ReadDir calls recorded by filesystem")
734 }
735 statCalls := filesystem.StatCalls
736 filesystem.ClearMetrics()
737
738 // run the second finder
739 finder2 := finderWithSameParams(t, finder)
740 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
741 // check results
742 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
743 assertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
744
745 finder2.Shutdown()
746}
747
748func TestCorruptedCacheBody(t *testing.T) {
749 // setup filesystem
750 filesystem := newFs()
751 create(t, "/tmp/findme.txt", filesystem)
752 create(t, "/tmp/a/findme.txt", filesystem)
753
754 // run the first finder
755 finder := newFinder(
756 t,
757 filesystem,
758 CacheParams{
759 RootDirs: []string{"/tmp"},
760 IncludeFiles: []string{"findme.txt"},
761 },
762 )
763 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
764 finder.Shutdown()
765
766 // check the response of the first finder
767 correctResponse := []string{"/tmp/a/findme.txt",
768 "/tmp/findme.txt"}
769 assertSameResponse(t, foundPaths, correctResponse)
770 numStatCalls := len(filesystem.StatCalls)
771 numReadDirCalls := len(filesystem.ReadDirCalls)
772
773 // load the cache file, corrupt it, and save it
774 cacheReader, err := filesystem.Open(finder.DbPath)
775 if err != nil {
776 t.Fatal(err)
777 }
778 cacheData, err := ioutil.ReadAll(cacheReader)
779 if err != nil {
780 t.Fatal(err)
781 }
782 cacheData = append(cacheData, []byte("DontMindMe")...)
783 filesystem.WriteFile(finder.DbPath, cacheData, 0777)
784 filesystem.ClearMetrics()
785
786 // run the second finder
787 finder2 := finderWithSameParams(t, finder)
788 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
789 // check results
790 assertSameResponse(t, foundPaths, correctResponse)
791 numNewStatCalls := len(filesystem.StatCalls)
792 numNewReadDirCalls := len(filesystem.ReadDirCalls)
793 // It's permissable to make more Stat calls with a corrupted cache because
794 // the Finder may restart once it detects corruption.
795 // However, it may have already issued many Stat calls.
796 // Because a corrupted db is not expected to be a common (or even a supported case),
797 // we don't care to optimize it and don't cache the already-issued Stat calls
798 if numNewReadDirCalls < numReadDirCalls {
799 t.Fatalf(
800 "Finder made fewer ReadDir calls with a corrupted cache (%v calls) than with no cache"+
801 " (%v calls)",
802 numNewReadDirCalls, numReadDirCalls)
803 }
804 if numNewStatCalls < numStatCalls {
805 t.Fatalf(
806 "Finder made fewer Stat calls with a corrupted cache (%v calls) than with no cache (%v calls)",
807 numNewStatCalls, numStatCalls)
808 }
809 finder2.Shutdown()
810}
811
812func TestStatCalls(t *testing.T) {
813 // setup filesystem
814 filesystem := newFs()
815 create(t, "/tmp/a/findme.txt", filesystem)
816
817 // run finder
818 finder := newFinder(
819 t,
820 filesystem,
821 CacheParams{
822 RootDirs: []string{"/tmp"},
823 IncludeFiles: []string{"findme.txt"},
824 },
825 )
826 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
827 finder.Shutdown()
828
829 // check response
830 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
831 assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
832 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
833}
834
835func TestFileAdded(t *testing.T) {
836 // setup filesystem
837 filesystem := newFs()
838 create(t, "/tmp/ignoreme.txt", filesystem)
839 create(t, "/tmp/a/findme.txt", filesystem)
840 create(t, "/tmp/b/ignore.txt", filesystem)
841 create(t, "/tmp/b/c/nope.txt", filesystem)
842 create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
843
844 // run the first finder
845 finder := newFinder(
846 t,
847 filesystem,
848 CacheParams{
849 RootDirs: []string{"/tmp"},
850 IncludeFiles: []string{"findme.txt"},
851 },
852 )
853 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
854 filesystem.Clock.Tick()
855 finder.Shutdown()
856 // check the response of the first finder
857 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
858
859 // modify the filesystem
860 filesystem.Clock.Tick()
861 create(t, "/tmp/b/c/findme.txt", filesystem)
862 filesystem.Clock.Tick()
863 filesystem.ClearMetrics()
864
865 // run the second finder
866 finder2 := finderWithSameParams(t, finder)
867 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
868
869 // check results
870 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
871 assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
872 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
873 finder2.Shutdown()
874
875}
876
877func TestDirectoriesAdded(t *testing.T) {
878 // setup filesystem
879 filesystem := newFs()
880 create(t, "/tmp/ignoreme.txt", filesystem)
881 create(t, "/tmp/a/findme.txt", filesystem)
882 create(t, "/tmp/b/ignore.txt", filesystem)
883 create(t, "/tmp/b/c/nope.txt", filesystem)
884 create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
885
886 // run the first finder
887 finder := newFinder(
888 t,
889 filesystem,
890 CacheParams{
891 RootDirs: []string{"/tmp"},
892 IncludeFiles: []string{"findme.txt"},
893 },
894 )
895 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
896 finder.Shutdown()
897 // check the response of the first finder
898 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
899
900 // modify the filesystem
901 filesystem.Clock.Tick()
902 create(t, "/tmp/b/c/new/findme.txt", filesystem)
903 create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
904 create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
905 filesystem.ClearMetrics()
906
907 // run the second finder
908 finder2 := finderWithSameParams(t, finder)
909 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
910
911 // check results
912 assertSameResponse(t, foundPaths,
913 []string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"})
914 assertSameStatCalls(t, filesystem.StatCalls,
915 []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
916 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
917
918 finder2.Shutdown()
919}
920
921func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) {
922 // setup filesystem
923 filesystem := newFs()
924 create(t, "/tmp/hi1.txt", filesystem)
925 create(t, "/tmp/a/hi1.txt", filesystem)
926
927 // run the first finder
928 finder := newFinder(
929 t,
930 filesystem,
931 CacheParams{
932 RootDirs: []string{"/tmp"},
933 IncludeFiles: []string{"hi1.txt", "hi2.txt"},
934 },
935 )
936 foundPaths := finder.FindNamedAt("/tmp", "hi1.txt")
937 finder.Shutdown()
938 // check the response of the first finder
939 assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
940
941 // modify the filesystem
942 filesystem.Clock.Tick()
943 create(t, "/tmp/hi2.txt", filesystem)
944 create(t, "/tmp/a/hi2.txt", filesystem)
945 filesystem.ClearMetrics()
946
947 // run the second finder
948 finder2 := finderWithSameParams(t, finder)
949 foundPaths = finder2.FindAll()
950
951 // check results
952 assertSameResponse(t, foundPaths,
953 []string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"})
954 assertSameStatCalls(t, filesystem.StatCalls,
955 []string{"/tmp", "/tmp/a"})
956 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
957
958 finder2.Shutdown()
959}
960
961func TestFileDeleted(t *testing.T) {
962 // setup filesystem
963 filesystem := newFs()
964 create(t, "/tmp/ignoreme.txt", filesystem)
965 create(t, "/tmp/a/findme.txt", filesystem)
966 create(t, "/tmp/b/findme.txt", filesystem)
967 create(t, "/tmp/b/c/nope.txt", filesystem)
968 create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
969
970 // run the first finder
971 finder := newFinder(
972 t,
973 filesystem,
974 CacheParams{
975 RootDirs: []string{"/tmp"},
976 IncludeFiles: []string{"findme.txt"},
977 },
978 )
979 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
980 finder.Shutdown()
981 // check the response of the first finder
982 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
983
984 // modify the filesystem
985 filesystem.Clock.Tick()
986 delete(t, "/tmp/b/findme.txt", filesystem)
987 filesystem.ClearMetrics()
988
989 // run the second finder
990 finder2 := finderWithSameParams(t, finder)
991 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
992
993 // check results
994 assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
995 assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
996 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
997
998 finder2.Shutdown()
999}
1000
1001func TestDirectoriesDeleted(t *testing.T) {
1002 // setup filesystem
1003 filesystem := newFs()
1004 create(t, "/tmp/findme.txt", filesystem)
1005 create(t, "/tmp/a/findme.txt", filesystem)
1006 create(t, "/tmp/a/1/findme.txt", filesystem)
1007 create(t, "/tmp/a/1/2/findme.txt", filesystem)
1008 create(t, "/tmp/b/findme.txt", filesystem)
1009
1010 // run the first finder
1011 finder := newFinder(
1012 t,
1013 filesystem,
1014 CacheParams{
1015 RootDirs: []string{"/tmp"},
1016 IncludeFiles: []string{"findme.txt"},
1017 },
1018 )
1019 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1020 finder.Shutdown()
1021 // check the response of the first finder
1022 assertSameResponse(t, foundPaths,
1023 []string{"/tmp/findme.txt",
1024 "/tmp/a/findme.txt",
1025 "/tmp/a/1/findme.txt",
1026 "/tmp/a/1/2/findme.txt",
1027 "/tmp/b/findme.txt"})
1028
1029 // modify the filesystem
1030 filesystem.Clock.Tick()
1031 removeAll(t, "/tmp/a/1", filesystem)
1032 filesystem.ClearMetrics()
1033
1034 // run the second finder
1035 finder2 := finderWithSameParams(t, finder)
1036 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1037
1038 // check results
1039 assertSameResponse(t, foundPaths,
1040 []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"})
1041 // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1042 // if the Finder detects the nonexistence of /tmp/a/1
1043 // However, when resuming from cache, we don't want the Finder to necessarily wait
1044 // to stat a directory until after statting its parent.
1045 // So here we just include /tmp/a/1/2 in the list.
1046 // The Finder is currently implemented to always restat every dir and
1047 // to not short-circuit due to nonexistence of parents (but it will remove
1048 // missing dirs from the cache for next time)
1049 assertSameStatCalls(t, filesystem.StatCalls,
1050 []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"})
1051 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
1052
1053 finder2.Shutdown()
1054}
1055
1056func TestDirectoriesMoved(t *testing.T) {
1057 // setup filesystem
1058 filesystem := newFs()
1059 create(t, "/tmp/findme.txt", filesystem)
1060 create(t, "/tmp/a/findme.txt", filesystem)
1061 create(t, "/tmp/a/1/findme.txt", filesystem)
1062 create(t, "/tmp/a/1/2/findme.txt", filesystem)
1063 create(t, "/tmp/b/findme.txt", filesystem)
1064
1065 // run the first finder
1066 finder := newFinder(
1067 t,
1068 filesystem,
1069 CacheParams{
1070 RootDirs: []string{"/tmp"},
1071 IncludeFiles: []string{"findme.txt"},
1072 },
1073 )
1074 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1075 finder.Shutdown()
1076 // check the response of the first finder
1077 assertSameResponse(t, foundPaths,
1078 []string{"/tmp/findme.txt",
1079 "/tmp/a/findme.txt",
1080 "/tmp/a/1/findme.txt",
1081 "/tmp/a/1/2/findme.txt",
1082 "/tmp/b/findme.txt"})
1083
1084 // modify the filesystem
1085 filesystem.Clock.Tick()
1086 move(t, "/tmp/a", "/tmp/c", filesystem)
1087 filesystem.ClearMetrics()
1088
1089 // run the second finder
1090 finder2 := finderWithSameParams(t, finder)
1091 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1092
1093 // check results
1094 assertSameResponse(t, foundPaths,
1095 []string{"/tmp/findme.txt",
1096 "/tmp/b/findme.txt",
1097 "/tmp/c/findme.txt",
1098 "/tmp/c/1/findme.txt",
1099 "/tmp/c/1/2/findme.txt"})
1100 // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1101 // if the Finder detects the nonexistence of /tmp/a/1
1102 // However, when resuming from cache, we don't want the Finder to necessarily wait
1103 // to stat a directory until after statting its parent.
1104 // So here we just include /tmp/a/1/2 in the list.
1105 // The Finder is currently implemented to always restat every dir and
1106 // to not short-circuit due to nonexistence of parents (but it will remove
1107 // missing dirs from the cache for next time)
1108 assertSameStatCalls(t, filesystem.StatCalls,
1109 []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
1110 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
1111 finder2.Shutdown()
1112}
1113
1114func TestDirectoriesSwapped(t *testing.T) {
1115 // setup filesystem
1116 filesystem := newFs()
1117 create(t, "/tmp/findme.txt", filesystem)
1118 create(t, "/tmp/a/findme.txt", filesystem)
1119 create(t, "/tmp/a/1/findme.txt", filesystem)
1120 create(t, "/tmp/a/1/2/findme.txt", filesystem)
1121 create(t, "/tmp/b/findme.txt", filesystem)
1122
1123 // run the first finder
1124 finder := newFinder(
1125 t,
1126 filesystem,
1127 CacheParams{
1128 RootDirs: []string{"/tmp"},
1129 IncludeFiles: []string{"findme.txt"},
1130 },
1131 )
1132 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1133 finder.Shutdown()
1134 // check the response of the first finder
1135 assertSameResponse(t, foundPaths,
1136 []string{"/tmp/findme.txt",
1137 "/tmp/a/findme.txt",
1138 "/tmp/a/1/findme.txt",
1139 "/tmp/a/1/2/findme.txt",
1140 "/tmp/b/findme.txt"})
1141
1142 // modify the filesystem
1143 filesystem.Clock.Tick()
1144 move(t, "/tmp/a", "/tmp/temp", filesystem)
1145 move(t, "/tmp/b", "/tmp/a", filesystem)
1146 move(t, "/tmp/temp", "/tmp/b", filesystem)
1147 filesystem.ClearMetrics()
1148
1149 // run the second finder
1150 finder2 := finderWithSameParams(t, finder)
1151 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1152
1153 // check results
1154 assertSameResponse(t, foundPaths,
1155 []string{"/tmp/findme.txt",
1156 "/tmp/a/findme.txt",
1157 "/tmp/b/findme.txt",
1158 "/tmp/b/1/findme.txt",
1159 "/tmp/b/1/2/findme.txt"})
1160 // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1161 // if the Finder detects the nonexistence of /tmp/a/1
1162 // However, when resuming from cache, we don't want the Finder to necessarily wait
1163 // to stat a directory until after statting its parent.
1164 // So here we just include /tmp/a/1/2 in the list.
1165 // The Finder is currently implemented to always restat every dir and
1166 // to not short-circuit due to nonexistence of parents (but it will remove
1167 // missing dirs from the cache for next time)
1168 assertSameStatCalls(t, filesystem.StatCalls,
1169 []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
1170 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
1171 finder2.Shutdown()
1172}
1173
1174// runFsReplacementTest tests a change modifying properties of the filesystem itself:
1175// runFsReplacementTest tests changing the user, the hostname, or the device number
1176// runFsReplacementTest is a helper method called by other tests
1177func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) {
1178 // setup fs1
1179 create(t, "/tmp/findme.txt", fs1)
1180 create(t, "/tmp/a/findme.txt", fs1)
1181 create(t, "/tmp/a/a/findme.txt", fs1)
1182
1183 // setup fs2 to have the same directories but different files
1184 create(t, "/tmp/findme.txt", fs2)
1185 create(t, "/tmp/a/findme.txt", fs2)
1186 create(t, "/tmp/a/a/ignoreme.txt", fs2)
1187 create(t, "/tmp/a/b/findme.txt", fs2)
1188
1189 // run the first finder
1190 finder := newFinder(
1191 t,
1192 fs1,
1193 CacheParams{
1194 RootDirs: []string{"/tmp"},
1195 IncludeFiles: []string{"findme.txt"},
1196 },
1197 )
1198 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1199 finder.Shutdown()
1200 // check the response of the first finder
1201 assertSameResponse(t, foundPaths,
1202 []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"})
1203
1204 // copy the cache data from the first filesystem to the second
1205 cacheContent := read(t, finder.DbPath, fs1)
1206 write(t, finder.DbPath, cacheContent, fs2)
1207
1208 // run the second finder, with the same config and same cache contents but a different filesystem
1209 finder2 := newFinder(
1210 t,
1211 fs2,
1212 CacheParams{
1213 RootDirs: []string{"/tmp"},
1214 IncludeFiles: []string{"findme.txt"},
1215 },
1216 )
1217 foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1218
1219 // check results
1220 assertSameResponse(t, foundPaths,
1221 []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"})
1222 assertSameStatCalls(t, fs2.StatCalls,
1223 []string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
1224 assertSameReadDirCalls(t, fs2.ReadDirCalls,
1225 []string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
1226 finder2.Shutdown()
1227}
1228
1229func TestChangeOfDevice(t *testing.T) {
1230 fs1 := newFs()
1231 // not as fine-grained mounting controls as a real filesystem, but should be adequate
1232 fs1.SetDeviceNumber(0)
1233
1234 fs2 := newFs()
1235 fs2.SetDeviceNumber(1)
1236
1237 runFsReplacementTest(t, fs1, fs2)
1238}
1239
1240func TestChangeOfUserOrHost(t *testing.T) {
1241 fs1 := newFs()
1242 fs1.SetViewId("me@here")
1243
1244 fs2 := newFs()
1245 fs2.SetViewId("you@there")
1246
1247 runFsReplacementTest(t, fs1, fs2)
1248}
1249
1250func TestConsistentCacheOrdering(t *testing.T) {
1251 // setup filesystem
1252 filesystem := newFs()
1253 for i := 0; i < 5; i++ {
1254 create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
1255 }
1256
1257 // run the first finder
1258 finder := newFinder(
1259 t,
1260 filesystem,
1261 CacheParams{
1262 RootDirs: []string{"/tmp"},
1263 IncludeFiles: []string{"findme.txt"},
1264 },
1265 )
1266 finder.FindNamedAt("/tmp", "findme.txt")
1267 finder.Shutdown()
1268
1269 // read db file
1270 string1 := read(t, finder.DbPath, filesystem)
1271
1272 err := filesystem.Remove(finder.DbPath)
1273 if err != nil {
1274 t.Fatal(err)
1275 }
1276
1277 // run another finder
1278 finder2 := finderWithSameParams(t, finder)
1279 finder2.FindNamedAt("/tmp", "findme.txt")
1280 finder2.Shutdown()
1281
1282 string2 := read(t, finder.DbPath, filesystem)
1283
1284 if string1 != string2 {
1285 t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+
1286 "Content of first file:\n"+
1287 "\n"+
1288 "%v"+
1289 "\n"+
1290 "\n"+
1291 "Content of second file:\n"+
1292 "\n"+
1293 "%v\n"+
1294 "\n",
1295 string1,
1296 string2,
1297 )
1298 }
1299
1300}
1301
1302func TestNumSyscallsOfSecondFind(t *testing.T) {
1303 // setup filesystem
1304 filesystem := newFs()
1305 create(t, "/tmp/findme.txt", filesystem)
1306 create(t, "/tmp/a/findme.txt", filesystem)
1307 create(t, "/tmp/a/misc.txt", filesystem)
1308
1309 // set up the finder and run it once
1310 finder := newFinder(
1311 t,
1312 filesystem,
1313 CacheParams{
1314 RootDirs: []string{"/tmp"},
1315 IncludeFiles: []string{"findme.txt"},
1316 },
1317 )
1318 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1319 assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
1320
1321 filesystem.ClearMetrics()
1322
1323 // run the finder again and confirm it doesn't check the filesystem
1324 refoundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1325 assertSameResponse(t, refoundPaths, foundPaths)
1326 assertSameStatCalls(t, filesystem.StatCalls, []string{})
1327 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1328
1329 finder.Shutdown()
1330}
1331
1332func TestChangingParamsOfSecondFind(t *testing.T) {
1333 // setup filesystem
1334 filesystem := newFs()
1335 create(t, "/tmp/findme.txt", filesystem)
1336 create(t, "/tmp/a/findme.txt", filesystem)
1337 create(t, "/tmp/a/metoo.txt", filesystem)
1338
1339 // set up the finder and run it once
1340 finder := newFinder(
1341 t,
1342 filesystem,
1343 CacheParams{
1344 RootDirs: []string{"/tmp"},
1345 IncludeFiles: []string{"findme.txt", "metoo.txt"},
1346 },
1347 )
1348 foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1349 assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
1350
1351 filesystem.ClearMetrics()
1352
1353 // run the finder again and confirm it gets the right answer without asking the filesystem
1354 refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt")
1355 assertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
1356 assertSameStatCalls(t, filesystem.StatCalls, []string{})
1357 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1358
1359 finder.Shutdown()
1360}
1361
1362func TestSymlinkPointingToFile(t *testing.T) {
1363 // setup filesystem
1364 filesystem := newFs()
1365 create(t, "/tmp/a/hi.txt", filesystem)
1366 create(t, "/tmp/a/ignoreme.txt", filesystem)
1367 link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
1368 link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
1369 link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
1370 link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
1371 link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
1372 link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
1373 link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
1374
1375 // set up the finder and run it once
1376 finder := newFinder(
1377 t,
1378 filesystem,
1379 CacheParams{
1380 RootDirs: []string{"/tmp"},
1381 IncludeFiles: []string{"hi.txt"},
1382 },
1383 )
1384 foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1385 // should search based on the name of the link rather than the destination or validity of the link
1386 correctResponse := []string{
1387 "/tmp/a/hi.txt",
1388 "/tmp/hi.txt",
1389 "/tmp/b/hi.txt",
1390 "/tmp/c/hi.txt",
1391 "/tmp/d/hi.txt",
1392 "/tmp/f/hi.txt",
1393 }
1394 assertSameResponse(t, foundPaths, correctResponse)
1395
1396}
1397
1398func TestSymlinkPointingToDirectory(t *testing.T) {
1399 // setup filesystem
1400 filesystem := newFs()
1401 create(t, "/tmp/dir/hi.txt", filesystem)
1402 create(t, "/tmp/dir/ignoreme.txt", filesystem)
1403
1404 link(t, "/tmp/links/dir", "../dir", filesystem)
1405 link(t, "/tmp/links/link", "../dir", filesystem)
1406 link(t, "/tmp/links/broken", "nothingHere", filesystem)
1407 link(t, "/tmp/links/recursive", "recursive", filesystem)
1408
1409 // set up the finder and run it once
1410 finder := newFinder(
1411 t,
1412 filesystem,
1413 CacheParams{
1414 RootDirs: []string{"/tmp"},
1415 IncludeFiles: []string{"hi.txt"},
1416 },
1417 )
1418
1419 foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1420
1421 // should completely ignore symlinks that point to directories
1422 correctResponse := []string{
1423 "/tmp/dir/hi.txt",
1424 }
1425 assertSameResponse(t, foundPaths, correctResponse)
1426
1427}
1428
1429// TestAddPruneFile confirms that adding a prune-file (into a directory for which we
1430// already had a cache) causes the directory to be ignored
1431func TestAddPruneFile(t *testing.T) {
1432 // setup filesystem
1433 filesystem := newFs()
1434 create(t, "/tmp/out/hi.txt", filesystem)
1435 create(t, "/tmp/out/a/hi.txt", filesystem)
1436 create(t, "/tmp/hi.txt", filesystem)
1437
1438 // do find
1439 finder := newFinder(
1440 t,
1441 filesystem,
1442 CacheParams{
1443 RootDirs: []string{"/tmp"},
1444 PruneFiles: []string{".ignore-out-dir"},
1445 IncludeFiles: []string{"hi.txt"},
1446 },
1447 )
1448
1449 foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1450
1451 // check result
1452 assertSameResponse(t, foundPaths,
1453 []string{"/tmp/hi.txt",
1454 "/tmp/out/hi.txt",
1455 "/tmp/out/a/hi.txt"},
1456 )
1457 finder.Shutdown()
1458
1459 // modify filesystem
1460 filesystem.Clock.Tick()
1461 create(t, "/tmp/out/.ignore-out-dir", filesystem)
1462 // run another find and check its result
1463 finder2 := finderWithSameParams(t, finder)
1464 foundPaths = finder2.FindNamedAt("/tmp", "hi.txt")
1465 assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
1466 finder2.Shutdown()
1467}
1468
1469func TestUpdatingDbIffChanged(t *testing.T) {
1470 // setup filesystem
1471 filesystem := newFs()
1472 create(t, "/tmp/a/hi.txt", filesystem)
1473 create(t, "/tmp/b/bye.txt", filesystem)
1474
1475 // run the first finder
1476 finder := newFinder(
1477 t,
1478 filesystem,
1479 CacheParams{
1480 RootDirs: []string{"/tmp"},
1481 IncludeFiles: []string{"hi.txt"},
1482 },
1483 )
1484 foundPaths := finder.FindAll()
1485 filesystem.Clock.Tick()
1486 finder.Shutdown()
1487 // check results
1488 assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
1489
1490 // modify the filesystem
1491 filesystem.Clock.Tick()
1492 create(t, "/tmp/b/hi.txt", filesystem)
1493 filesystem.Clock.Tick()
1494 filesystem.ClearMetrics()
1495
1496 // run the second finder
1497 finder2 := finderWithSameParams(t, finder)
1498 foundPaths = finder2.FindAll()
1499 finder2.Shutdown()
1500 // check results
1501 assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
1502 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
1503 expectedDbWriteTime := filesystem.Clock.Time()
1504 actualDbWriteTime := modTime(t, finder2.DbPath, filesystem)
1505 if actualDbWriteTime != expectedDbWriteTime {
1506 t.Fatalf("Expected to write db at %v, actually wrote db at %v\n",
1507 expectedDbWriteTime, actualDbWriteTime)
1508 }
1509
1510 // reset metrics
1511 filesystem.ClearMetrics()
1512
1513 // run the third finder
1514 finder3 := finderWithSameParams(t, finder2)
1515 foundPaths = finder3.FindAll()
1516
1517 // check results
1518 assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
1519 assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1520 finder3.Shutdown()
1521 actualDbWriteTime = modTime(t, finder3.DbPath, filesystem)
1522 if actualDbWriteTime != expectedDbWriteTime {
1523 t.Fatalf("Re-wrote db even when contents did not change")
1524 }
1525
1526}
1527
1528func TestDirectoryNotPermitted(t *testing.T) {
1529 // setup filesystem
1530 filesystem := newFs()
1531 create(t, "/tmp/hi.txt", filesystem)
1532 create(t, "/tmp/a/hi.txt", filesystem)
1533 create(t, "/tmp/a/a/hi.txt", filesystem)
1534 create(t, "/tmp/b/hi.txt", filesystem)
1535
1536 // run the first finder
1537 finder := newFinder(
1538 t,
1539 filesystem,
1540 CacheParams{
1541 RootDirs: []string{"/tmp"},
1542 IncludeFiles: []string{"hi.txt"},
1543 },
1544 )
1545 foundPaths := finder.FindAll()
1546 filesystem.Clock.Tick()
1547 finder.Shutdown()
1548 allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"}
1549 // check results
1550 assertSameResponse(t, foundPaths, allPaths)
1551
1552 // modify the filesystem
1553 filesystem.Clock.Tick()
1554
1555 setReadable(t, "/tmp/a", false, filesystem)
1556 filesystem.Clock.Tick()
1557
1558 // run the second finder
1559 finder2 := finderWithSameParams(t, finder)
1560 foundPaths = finder2.FindAll()
1561 finder2.Shutdown()
1562 // check results
1563 assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
1564
1565 // modify the filesystem back
1566 setReadable(t, "/tmp/a", true, filesystem)
1567
1568 // run the third finder
1569 finder3 := finderWithSameParams(t, finder2)
1570 foundPaths = finder3.FindAll()
1571 finder3.Shutdown()
1572 // check results
1573 assertSameResponse(t, foundPaths, allPaths)
1574}
1575
1576func TestFileNotPermitted(t *testing.T) {
1577 // setup filesystem
1578 filesystem := newFs()
1579 create(t, "/tmp/hi.txt", filesystem)
1580 setReadable(t, "/tmp/hi.txt", false, filesystem)
1581
1582 // run the first finder
1583 finder := newFinder(
1584 t,
1585 filesystem,
1586 CacheParams{
1587 RootDirs: []string{"/tmp"},
1588 IncludeFiles: []string{"hi.txt"},
1589 },
1590 )
1591 foundPaths := finder.FindAll()
1592 filesystem.Clock.Tick()
1593 finder.Shutdown()
1594 // check results
1595 assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
1596}
Jeff Gastonb629e182017-08-14 16:49:18 -07001597
1598func TestCacheEntryPathUnexpectedError(t *testing.T) {
1599 // setup filesystem
1600 filesystem := newFs()
1601 create(t, "/tmp/a/hi.txt", filesystem)
1602
1603 // run the first finder
1604 finder := newFinder(
1605 t,
1606 filesystem,
1607 CacheParams{
1608 RootDirs: []string{"/tmp"},
1609 IncludeFiles: []string{"hi.txt"},
1610 },
1611 )
1612 foundPaths := finder.FindAll()
1613 filesystem.Clock.Tick()
1614 finder.Shutdown()
1615 // check results
1616 assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
1617
1618 // make the directory not readable
1619 setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
1620
1621 // run the second finder
1622 _, err := finderAndErrorWithSameParams(t, finder)
1623 if err == nil {
1624 fatal(t, "Failed to detect unexpected filesystem error")
1625 }
1626}