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