Home | History | Annotate | Download | only in finder
      1 // 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 
     15 package finder
     16 
     17 import (
     18 	"fmt"
     19 	"io/ioutil"
     20 	"log"
     21 	"os"
     22 	"path/filepath"
     23 	"reflect"
     24 	"runtime/debug"
     25 	"sort"
     26 	"testing"
     27 	"time"
     28 
     29 	"android/soong/finder/fs"
     30 )
     31 
     32 // some utils for tests to use
     33 func newFs() *fs.MockFs {
     34 	return fs.NewMockFs(map[string][]byte{})
     35 }
     36 
     37 func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
     38 	return newFinderWithNumThreads(t, filesystem, cacheParams, 2)
     39 }
     40 
     41 func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder {
     42 	f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads)
     43 	if err != nil {
     44 		fatal(t, err.Error())
     45 	}
     46 	return f
     47 }
     48 
     49 func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) (*Finder, error) {
     50 	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)
     58 	f, err := newImpl(cacheParams, filesystem, logger, cachePath, numThreads)
     59 	return f, err
     60 }
     61 
     62 func finderWithSameParams(t *testing.T, original *Finder) *Finder {
     63 	f, err := finderAndErrorWithSameParams(t, original)
     64 	if err != nil {
     65 		fatal(t, err.Error())
     66 	}
     67 	return f
     68 }
     69 
     70 func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) {
     71 	f, err := newImpl(
     72 		original.cacheMetadata.Config.CacheParams,
     73 		original.filesystem,
     74 		original.logger,
     75 		original.DbPath,
     76 		original.numDbLoadingThreads,
     77 	)
     78 	return f, err
     79 }
     80 
     81 func 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 {
     86 		fatal(t, err.Error())
     87 	}
     88 }
     89 
     90 func create(t *testing.T, path string, filesystem *fs.MockFs) {
     91 	write(t, path, "hi", filesystem)
     92 }
     93 
     94 func delete(t *testing.T, path string, filesystem *fs.MockFs) {
     95 	err := filesystem.Remove(path)
     96 	if err != nil {
     97 		fatal(t, err.Error())
     98 	}
     99 }
    100 
    101 func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
    102 	err := filesystem.RemoveAll(path)
    103 	if err != nil {
    104 		fatal(t, err.Error())
    105 	}
    106 }
    107 
    108 func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
    109 	err := filesystem.Rename(oldPath, newPath)
    110 	if err != nil {
    111 		fatal(t, err.Error())
    112 	}
    113 }
    114 
    115 func 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 {
    123 		fatal(t, err.Error())
    124 	}
    125 }
    126 func 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 }
    137 func 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 }
    144 func 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 }
    150 
    151 func 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 
    158 func fatal(t *testing.T, message string) {
    159 	t.Error(message)
    160 	debug.PrintStack()
    161 	t.FailNow()
    162 }
    163 
    164 func 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 
    177 func 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 }
    195 func 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
    215 func 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 
    243 // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
    244 func 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 
    258 // end of utils, start of individual tests
    259 
    260 func TestSingleFile(t *testing.T) {
    261 	runSimpleTest(t,
    262 		[]string{"findme.txt"},
    263 		[]string{"findme.txt"},
    264 	)
    265 }
    266 
    267 func TestIncludeFiles(t *testing.T) {
    268 	runSimpleTest(t,
    269 		[]string{"findme.txt", "skipme.txt"},
    270 		[]string{"findme.txt"},
    271 	)
    272 }
    273 
    274 func 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 
    281 func TestEmptyDirectory(t *testing.T) {
    282 	runSimpleTest(t,
    283 		[]string{},
    284 		[]string{},
    285 	)
    286 }
    287 
    288 func 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 
    308 func TestFilesystemRoot(t *testing.T) {
    309 
    310 	testWithNumThreads := func(t *testing.T, numThreads int) {
    311 		filesystem := newFs()
    312 		root := "/"
    313 		createdPath := "/findme.txt"
    314 		create(t, createdPath, filesystem)
    315 
    316 		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()
    326 
    327 		foundPaths := finder.FindNamedAt(root, "findme.txt")
    328 
    329 		assertSameResponse(t, foundPaths, []string{createdPath})
    330 	}
    331 
    332 	testAgainstSeveralThreadcounts(t, testWithNumThreads)
    333 }
    334 
    335 func TestNonexistentDir(t *testing.T) {
    336 	filesystem := newFs()
    337 	create(t, "/tmp/findme.txt", filesystem)
    338 
    339 	_, err := newFinderAndErr(
    340 		t,
    341 		filesystem,
    342 		CacheParams{
    343 			RootDirs:     []string{"/tmp/IDontExist"},
    344 			IncludeFiles: []string{"findme.txt", "skipme.txt"},
    345 		},
    346 		1,
    347 	)
    348 	if err == nil {
    349 		fatal(t, "Did not fail when given a nonexistent root directory")
    350 	}
    351 }
    352 
    353 func 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 
    381 func 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 
    411 // TestRootDir tests that the value of RootDirs is used
    412 // tests of the filesystem root are in TestFilesystemRoot
    413 func 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 
    437 func 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{
    448 			RootDirs:     []string{"/tmp/b"},
    449 			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 
    464 func 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 
    491 func 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)
    499 	create(t, "/rel/a/hi.txt", filesystem)
    500 
    501 	finder := newFinder(
    502 		t,
    503 		filesystem,
    504 		CacheParams{
    505 			RootDirs:     []string{"/cwd", "../rel", "/tmp/include"},
    506 			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 
    525 	foundPaths = finder.FindNamedAt("/rel", "hi.txt")
    526 	assertSameResponse(t, foundPaths,
    527 		[]string{"/rel/a/hi.txt"})
    528 
    529 	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
    535 func 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{
    544 			RootDirs:     []string{"/", "/tmp/a/b/c", "/tmp/a/b/c/d/e/f", "/tmp/a/b/c/d/e/f/g/h/i"},
    545 			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 
    556 func 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 
    580 func TestConcurrentFindSameDirectory(t *testing.T) {
    581 
    582 	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)
    625 		}
    626 	}
    627 
    628 	testAgainstSeveralThreadcounts(t, testWithNumThreads)
    629 }
    630 
    631 func 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 
    694 func 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 
    719 func 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 
    743 func 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 
    789 func 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 
    853 func 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 
    876 func 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 
    918 func 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 
    962 func 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 
   1002 func 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 
   1042 func 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 
   1097 func 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 
   1155 func 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
   1218 func 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 
   1270 func 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 
   1281 func 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 
   1291 func 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 
   1343 func 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 
   1373 func 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 
   1403 func 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 
   1439 func 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
   1472 func 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 
   1510 func 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 
   1569 func 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 
   1617 func 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 }
   1638 
   1639 func 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 }
   1668