// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package finder import ( "fmt" "io/ioutil" "log" "os" "path/filepath" "reflect" "runtime/debug" "sort" "testing" "time" "android/soong/finder/fs" ) // some utils for tests to use func newFs() *fs.MockFs { return fs.NewMockFs(map[string][]byte{}) } func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder { return newFinderWithNumThreads(t, filesystem, cacheParams, 2) } func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder { f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads) if err != nil { fatal(t, err.Error()) } return f } func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) (*Finder, error) { cachePath := "/finder/finder-db" cacheDir := filepath.Dir(cachePath) filesystem.MkDirs(cacheDir) if cacheParams.WorkingDirectory == "" { cacheParams.WorkingDirectory = "/cwd" } logger := log.New(ioutil.Discard, "", 0) f, err := newImpl(cacheParams, filesystem, logger, cachePath, numThreads) return f, err } func finderWithSameParams(t *testing.T, original *Finder) *Finder { f, err := finderAndErrorWithSameParams(t, original) if err != nil { fatal(t, err.Error()) } return f } func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) { f, err := newImpl( original.cacheMetadata.Config.CacheParams, original.filesystem, original.logger, original.DbPath, original.numDbLoadingThreads, ) return f, err } func write(t *testing.T, path string, content string, filesystem *fs.MockFs) { parent := filepath.Dir(path) filesystem.MkDirs(parent) err := filesystem.WriteFile(path, []byte(content), 0777) if err != nil { fatal(t, err.Error()) } } func create(t *testing.T, path string, filesystem *fs.MockFs) { write(t, path, "hi", filesystem) } func delete(t *testing.T, path string, filesystem *fs.MockFs) { err := filesystem.Remove(path) if err != nil { fatal(t, err.Error()) } } func removeAll(t *testing.T, path string, filesystem *fs.MockFs) { err := filesystem.RemoveAll(path) if err != nil { fatal(t, err.Error()) } } func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) { err := filesystem.Rename(oldPath, newPath) if err != nil { fatal(t, err.Error()) } } func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) { parentPath := filepath.Dir(newPath) err := filesystem.MkDirs(parentPath) if err != nil { t.Fatal(err.Error()) } err = filesystem.Symlink(oldPath, newPath) if err != nil { fatal(t, err.Error()) } } func read(t *testing.T, path string, filesystem *fs.MockFs) string { reader, err := filesystem.Open(path) if err != nil { t.Fatalf(err.Error()) } bytes, err := ioutil.ReadAll(reader) if err != nil { t.Fatal(err.Error()) } return string(bytes) } func modTime(t *testing.T, path string, filesystem *fs.MockFs) time.Time { stats, err := filesystem.Lstat(path) if err != nil { t.Fatal(err.Error()) } return stats.ModTime() } func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs) { err := filesystem.SetReadable(path, readable) if err != nil { t.Fatal(err.Error()) } } func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) { err := filesystem.SetReadErr(path, readErr) if err != nil { t.Fatal(err.Error()) } } func fatal(t *testing.T, message string) { t.Error(message) debug.PrintStack() t.FailNow() } func assertSameResponse(t *testing.T, actual []string, expected []string) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(actual, expected) { fatal( t, fmt.Sprintf( "Expected Finder to return these %v paths:\n %v,\ninstead returned these %v paths: %v\n", len(expected), expected, len(actual), actual), ) } } func assertSameStatCalls(t *testing.T, actual []string, expected []string) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(actual, expected) { fatal( t, fmt.Sprintf( "Finder made incorrect Stat calls.\n"+ "Actual:\n"+ "%v\n"+ "Expected:\n"+ "%v\n"+ "\n", actual, expected), ) } } func assertSameReadDirCalls(t *testing.T, actual []string, expected []string) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(actual, expected) { fatal( t, fmt.Sprintf( "Finder made incorrect ReadDir calls.\n"+ "Actual:\n"+ "%v\n"+ "Expected:\n"+ "%v\n"+ "\n", actual, expected), ) } } // runSimpleTests creates a few files, searches for findme.txt, and checks for the expected matches func runSimpleTest(t *testing.T, existentPaths []string, expectedMatches []string) { filesystem := newFs() root := "/tmp" filesystem.MkDirs(root) for _, path := range existentPaths { create(t, filepath.Join(root, path), filesystem) } finder := newFinder(t, filesystem, CacheParams{ "/cwd", []string{root}, nil, nil, []string{"findme.txt", "skipme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt(root, "findme.txt") absoluteMatches := []string{} for i := range expectedMatches { absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i])) } assertSameResponse(t, foundPaths, absoluteMatches) } // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test func testAgainstSeveralThreadcounts(t *testing.T, tester func(t *testing.T, numThreads int)) { // test singlethreaded, multithreaded, and also using the same number of threads as // will be used on the current system threadCounts := []int{1, 2, defaultNumThreads} for _, numThreads := range threadCounts { testName := fmt.Sprintf("%v threads", numThreads) // store numThreads in a new variable to prevent numThreads from changing in each loop localNumThreads := numThreads t.Run(testName, func(t *testing.T) { tester(t, localNumThreads) }) } } // end of utils, start of individual tests func TestSingleFile(t *testing.T) { runSimpleTest(t, []string{"findme.txt"}, []string{"findme.txt"}, ) } func TestIncludeFiles(t *testing.T) { runSimpleTest(t, []string{"findme.txt", "skipme.txt"}, []string{"findme.txt"}, ) } func TestNestedDirectories(t *testing.T) { runSimpleTest(t, []string{"findme.txt", "skipme.txt", "subdir/findme.txt", "subdir/skipme.txt"}, []string{"findme.txt", "subdir/findme.txt"}, ) } func TestEmptyDirectory(t *testing.T) { runSimpleTest(t, []string{}, []string{}, ) } func TestEmptyPath(t *testing.T) { filesystem := newFs() root := "/tmp" create(t, filepath.Join(root, "findme.txt"), filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{root}, IncludeFiles: []string{"findme.txt", "skipme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("", "findme.txt") assertSameResponse(t, foundPaths, []string{}) } func TestFilesystemRoot(t *testing.T) { testWithNumThreads := func(t *testing.T, numThreads int) { filesystem := newFs() root := "/" createdPath := "/findme.txt" create(t, createdPath, filesystem) finder := newFinderWithNumThreads( t, filesystem, CacheParams{ RootDirs: []string{root}, IncludeFiles: []string{"findme.txt", "skipme.txt"}, }, numThreads, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt(root, "findme.txt") assertSameResponse(t, foundPaths, []string{createdPath}) } testAgainstSeveralThreadcounts(t, testWithNumThreads) } func TestNonexistentDir(t *testing.T) { filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) _, err := newFinderAndErr( t, filesystem, CacheParams{ RootDirs: []string{"/tmp/IDontExist"}, IncludeFiles: []string{"findme.txt", "skipme.txt"}, }, 1, ) if err == nil { fatal(t, "Did not fail when given a nonexistent root directory") } } func TestExcludeDirs(t *testing.T) { filesystem := newFs() create(t, "/tmp/exclude/findme.txt", filesystem) create(t, "/tmp/exclude/subdir/findme.txt", filesystem) create(t, "/tmp/subdir/exclude/findme.txt", filesystem) create(t, "/tmp/subdir/subdir/findme.txt", filesystem) create(t, "/tmp/subdir/findme.txt", filesystem) create(t, "/tmp/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, ExcludeDirs: []string{"exclude"}, IncludeFiles: []string{"findme.txt", "skipme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/subdir/findme.txt", "/tmp/subdir/subdir/findme.txt"}) } func TestPruneFiles(t *testing.T) { filesystem := newFs() create(t, "/tmp/out/findme.txt", filesystem) create(t, "/tmp/out/.ignore-out-dir", filesystem) create(t, "/tmp/out/child/findme.txt", filesystem) create(t, "/tmp/out2/.ignore-out-dir", filesystem) create(t, "/tmp/out2/sub/findme.txt", filesystem) create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/include/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, PruneFiles: []string{".ignore-out-dir"}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/include/findme.txt"}) } // TestRootDir tests that the value of RootDirs is used // tests of the filesystem root are in TestFilesystemRoot func TestRootDir(t *testing.T) { filesystem := newFs() create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/subdir/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) create(t, "/tmp/b/subdir/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp/a"}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/a/subdir/findme.txt"}) } func TestUncachedDir(t *testing.T) { filesystem := newFs() create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/subdir/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) create(t, "/tmp/b/subdir/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp/b"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt") // If the caller queries for a file that is in the cache, then computing the // correct answer won't be fast, and it would be easy for the caller to // fail to notice its slowness. Instead, we only ever search the cache for files // to return, which enforces that we can determine which files will be // interesting upfront. assertSameResponse(t, foundPaths, []string{}) finder.Shutdown() } func TestSearchingForFilesExcludedFromCache(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/misc.txt", filesystem) // set up the finder and run it finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "misc.txt") // If the caller queries for a file that is in the cache, then computing the // correct answer won't be fast, and it would be easy for the caller to // fail to notice its slowness. Instead, we only ever search the cache for files // to return, which enforces that we can determine which files will be // interesting upfront. assertSameResponse(t, foundPaths, []string{}) finder.Shutdown() } func TestRelativeFilePaths(t *testing.T) { filesystem := newFs() create(t, "/tmp/ignore/hi.txt", filesystem) create(t, "/tmp/include/hi.txt", filesystem) create(t, "/cwd/hi.txt", filesystem) create(t, "/cwd/a/hi.txt", filesystem) create(t, "/cwd/a/a/hi.txt", filesystem) create(t, "/rel/a/hi.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/cwd", "../rel", "/tmp/include"}, IncludeFiles: []string{"hi.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("a", "hi.txt") assertSameResponse(t, foundPaths, []string{"a/hi.txt", "a/a/hi.txt"}) foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt") assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"}) foundPaths = finder.FindNamedAt(".", "hi.txt") assertSameResponse(t, foundPaths, []string{"hi.txt", "a/hi.txt", "a/a/hi.txt"}) foundPaths = finder.FindNamedAt("/rel", "hi.txt") assertSameResponse(t, foundPaths, []string{"/rel/a/hi.txt"}) foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt") assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"}) } // have to run this test with the race-detector (`go test -race src/android/soong/finder/*.go`) // for there to be much chance of the test actually detecting any error that may be present func TestRootDirsContainedInOtherRootDirs(t *testing.T) { filesystem := newFs() create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/", "/tmp/a/b/c", "/tmp/a/b/c/d/e/f", "/tmp/a/b/c/d/e/f/g/h/i"}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt"}) } func TestFindFirst(t *testing.T) { filesystem := newFs() create(t, "/tmp/a/hi.txt", filesystem) create(t, "/tmp/b/hi.txt", filesystem) create(t, "/tmp/b/a/hi.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindFirstNamed("hi.txt") assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"}, ) } func TestConcurrentFindSameDirectory(t *testing.T) { testWithNumThreads := func(t *testing.T, numThreads int) { filesystem := newFs() // create a bunch of files and directories paths := []string{} for i := 0; i < 10; i++ { parentDir := fmt.Sprintf("/tmp/%v", i) for j := 0; j < 10; j++ { filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j)) paths = append(paths, filePath) } } sort.Strings(paths) for _, path := range paths { create(t, path, filesystem) } // set up a finder finder := newFinderWithNumThreads( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, numThreads, ) defer finder.Shutdown() numTests := 20 results := make(chan []string, numTests) // make several parallel calls to the finder for i := 0; i < numTests; i++ { go func() { foundPaths := finder.FindNamedAt("/tmp", "findme.txt") results <- foundPaths }() } // check that each response was correct for i := 0; i < numTests; i++ { foundPaths := <-results assertSameResponse(t, foundPaths, paths) } } testAgainstSeveralThreadcounts(t, testWithNumThreads) } func TestConcurrentFindDifferentDirectories(t *testing.T) { filesystem := newFs() // create a bunch of files and directories allFiles := []string{} numSubdirs := 10 rootPaths := []string{} queryAnswers := [][]string{} for i := 0; i < numSubdirs; i++ { parentDir := fmt.Sprintf("/tmp/%v", i) rootPaths = append(rootPaths, parentDir) queryAnswers = append(queryAnswers, []string{}) for j := 0; j < 10; j++ { filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j)) queryAnswers[i] = append(queryAnswers[i], filePath) allFiles = append(allFiles, filePath) } sort.Strings(queryAnswers[i]) } sort.Strings(allFiles) for _, path := range allFiles { create(t, path, filesystem) } // set up a finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() type testRun struct { path string foundMatches []string correctMatches []string } numTests := numSubdirs + 1 testRuns := make(chan testRun, numTests) searchAt := func(path string, correctMatches []string) { foundPaths := finder.FindNamedAt(path, "findme.txt") testRuns <- testRun{path, foundPaths, correctMatches} } // make several parallel calls to the finder go searchAt("/tmp", allFiles) for i := 0; i < len(rootPaths); i++ { go searchAt(rootPaths[i], queryAnswers[i]) } // check that each response was correct for i := 0; i < numTests; i++ { testRun := <-testRuns assertSameResponse(t, testRun.foundMatches, testRun.correctMatches) } } func TestStrangelyFormattedPaths(t *testing.T) { filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"//tmp//a//.."}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("//tmp//a//..", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt", "/tmp/findme.txt"}) } func TestCorruptedCacheHeader(t *testing.T) { filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) write(t, "/finder/finder-db", "sample header", filesystem) finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) defer finder.Shutdown() foundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/findme.txt"}) } func TestCanUseCache(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") // check the response of the first finder correctResponse := []string{"/tmp/a/findme.txt", "/tmp/findme.txt"} assertSameResponse(t, foundPaths, correctResponse) finder.Shutdown() // check results cacheText := read(t, finder.DbPath, filesystem) if len(cacheText) < 1 { t.Fatalf("saved cache db is empty\n") } if len(filesystem.StatCalls) == 0 { t.Fatal("No Stat calls recorded by mock filesystem") } if len(filesystem.ReadDirCalls) == 0 { t.Fatal("No ReadDir calls recorded by filesystem") } statCalls := filesystem.StatCalls filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{}) assertSameReadDirCalls(t, filesystem.StatCalls, statCalls) finder2.Shutdown() } func TestCorruptedCacheBody(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder correctResponse := []string{"/tmp/a/findme.txt", "/tmp/findme.txt"} assertSameResponse(t, foundPaths, correctResponse) numStatCalls := len(filesystem.StatCalls) numReadDirCalls := len(filesystem.ReadDirCalls) // load the cache file, corrupt it, and save it cacheReader, err := filesystem.Open(finder.DbPath) if err != nil { t.Fatal(err) } cacheData, err := ioutil.ReadAll(cacheReader) if err != nil { t.Fatal(err) } cacheData = append(cacheData, []byte("DontMindMe")...) filesystem.WriteFile(finder.DbPath, cacheData, 0777) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, correctResponse) numNewStatCalls := len(filesystem.StatCalls) numNewReadDirCalls := len(filesystem.ReadDirCalls) // It's permissable to make more Stat calls with a corrupted cache because // the Finder may restart once it detects corruption. // However, it may have already issued many Stat calls. // Because a corrupted db is not expected to be a common (or even a supported case), // we don't care to optimize it and don't cache the already-issued Stat calls if numNewReadDirCalls < numReadDirCalls { t.Fatalf( "Finder made fewer ReadDir calls with a corrupted cache (%v calls) than with no cache"+ " (%v calls)", numNewReadDirCalls, numReadDirCalls) } if numNewStatCalls < numStatCalls { t.Fatalf( "Finder made fewer Stat calls with a corrupted cache (%v calls) than with no cache (%v calls)", numNewStatCalls, numStatCalls) } finder2.Shutdown() } func TestStatCalls(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/a/findme.txt", filesystem) // run finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check response assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"}) } func TestFileAdded(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/ignoreme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/b/ignore.txt", filesystem) create(t, "/tmp/b/c/nope.txt", filesystem) create(t, "/tmp/b/c/d/irrelevant.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") filesystem.Clock.Tick() finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() create(t, "/tmp/b/c/findme.txt", filesystem) filesystem.Clock.Tick() filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"}) finder2.Shutdown() } func TestDirectoriesAdded(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/ignoreme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/b/ignore.txt", filesystem) create(t, "/tmp/b/c/nope.txt", filesystem) create(t, "/tmp/b/c/d/irrelevant.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() create(t, "/tmp/b/c/new/findme.txt", filesystem) create(t, "/tmp/b/c/new/new2/findme.txt", filesystem) create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"}) finder2.Shutdown() } func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/hi1.txt", filesystem) create(t, "/tmp/a/hi1.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi1.txt", "hi2.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "hi1.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"}) // modify the filesystem filesystem.Clock.Tick() create(t, "/tmp/hi2.txt", filesystem) create(t, "/tmp/a/hi2.txt", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindAll() // check results assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"}) finder2.Shutdown() } func TestFileDeleted(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/ignoreme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) create(t, "/tmp/b/c/nope.txt", filesystem) create(t, "/tmp/b/c/d/irrelevant.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() delete(t, "/tmp/b/findme.txt", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"}) finder2.Shutdown() } func TestDirectoriesDeleted(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/1/findme.txt", filesystem) create(t, "/tmp/a/1/2/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/1/findme.txt", "/tmp/a/1/2/findme.txt", "/tmp/b/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() removeAll(t, "/tmp/a/1", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"}) // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped // if the Finder detects the nonexistence of /tmp/a/1 // However, when resuming from cache, we don't want the Finder to necessarily wait // to stat a directory until after statting its parent. // So here we just include /tmp/a/1/2 in the list. // The Finder is currently implemented to always restat every dir and // to not short-circuit due to nonexistence of parents (but it will remove // missing dirs from the cache for next time) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"}) finder2.Shutdown() } func TestDirectoriesMoved(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/1/findme.txt", filesystem) create(t, "/tmp/a/1/2/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/1/findme.txt", "/tmp/a/1/2/findme.txt", "/tmp/b/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() move(t, "/tmp/a", "/tmp/c", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/b/findme.txt", "/tmp/c/findme.txt", "/tmp/c/1/findme.txt", "/tmp/c/1/2/findme.txt"}) // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped // if the Finder detects the nonexistence of /tmp/a/1 // However, when resuming from cache, we don't want the Finder to necessarily wait // to stat a directory until after statting its parent. // So here we just include /tmp/a/1/2 in the list. // The Finder is currently implemented to always restat every dir and // to not short-circuit due to nonexistence of parents (but it will remove // missing dirs from the cache for next time) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"}) finder2.Shutdown() } func TestDirectoriesSwapped(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/1/findme.txt", filesystem) create(t, "/tmp/a/1/2/findme.txt", filesystem) create(t, "/tmp/b/findme.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/1/findme.txt", "/tmp/a/1/2/findme.txt", "/tmp/b/findme.txt"}) // modify the filesystem filesystem.Clock.Tick() move(t, "/tmp/a", "/tmp/temp", filesystem) move(t, "/tmp/b", "/tmp/a", filesystem) move(t, "/tmp/temp", "/tmp/b", filesystem) filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt", "/tmp/b/1/findme.txt", "/tmp/b/1/2/findme.txt"}) // Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped // if the Finder detects the nonexistence of /tmp/a/1 // However, when resuming from cache, we don't want the Finder to necessarily wait // to stat a directory until after statting its parent. // So here we just include /tmp/a/1/2 in the list. // The Finder is currently implemented to always restat every dir and // to not short-circuit due to nonexistence of parents (but it will remove // missing dirs from the cache for next time) assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"}) finder2.Shutdown() } // runFsReplacementTest tests a change modifying properties of the filesystem itself: // runFsReplacementTest tests changing the user, the hostname, or the device number // runFsReplacementTest is a helper method called by other tests func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) { // setup fs1 create(t, "/tmp/findme.txt", fs1) create(t, "/tmp/a/findme.txt", fs1) create(t, "/tmp/a/a/findme.txt", fs1) // setup fs2 to have the same directories but different files create(t, "/tmp/findme.txt", fs2) create(t, "/tmp/a/findme.txt", fs2) create(t, "/tmp/a/a/ignoreme.txt", fs2) create(t, "/tmp/a/b/findme.txt", fs2) // run the first finder finder := newFinder( t, fs1, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // check the response of the first finder assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"}) // copy the cache data from the first filesystem to the second cacheContent := read(t, finder.DbPath, fs1) write(t, finder.DbPath, cacheContent, fs2) // run the second finder, with the same config and same cache contents but a different filesystem finder2 := newFinder( t, fs2, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths = finder2.FindNamedAt("/tmp", "findme.txt") // check results assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"}) assertSameStatCalls(t, fs2.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"}) assertSameReadDirCalls(t, fs2.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"}) finder2.Shutdown() } func TestChangeOfDevice(t *testing.T) { fs1 := newFs() // not as fine-grained mounting controls as a real filesystem, but should be adequate fs1.SetDeviceNumber(0) fs2 := newFs() fs2.SetDeviceNumber(1) runFsReplacementTest(t, fs1, fs2) } func TestChangeOfUserOrHost(t *testing.T) { fs1 := newFs() fs1.SetViewId("me@here") fs2 := newFs() fs2.SetViewId("you@there") runFsReplacementTest(t, fs1, fs2) } func TestConsistentCacheOrdering(t *testing.T) { // setup filesystem filesystem := newFs() for i := 0; i < 5; i++ { create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem) } // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) finder.FindNamedAt("/tmp", "findme.txt") finder.Shutdown() // read db file string1 := read(t, finder.DbPath, filesystem) err := filesystem.Remove(finder.DbPath) if err != nil { t.Fatal(err) } // run another finder finder2 := finderWithSameParams(t, finder) finder2.FindNamedAt("/tmp", "findme.txt") finder2.Shutdown() string2 := read(t, finder.DbPath, filesystem) if string1 != string2 { t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+ "Content of first file:\n"+ "\n"+ "%v"+ "\n"+ "\n"+ "Content of second file:\n"+ "\n"+ "%v\n"+ "\n", string1, string2, ) } } func TestNumSyscallsOfSecondFind(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/misc.txt", filesystem) // set up the finder and run it once finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"}) filesystem.ClearMetrics() // run the finder again and confirm it doesn't check the filesystem refoundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, refoundPaths, foundPaths) assertSameStatCalls(t, filesystem.StatCalls, []string{}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{}) finder.Shutdown() } func TestChangingParamsOfSecondFind(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/findme.txt", filesystem) create(t, "/tmp/a/findme.txt", filesystem) create(t, "/tmp/a/metoo.txt", filesystem) // set up the finder and run it once finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"findme.txt", "metoo.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "findme.txt") assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"}) filesystem.ClearMetrics() // run the finder again and confirm it gets the right answer without asking the filesystem refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt") assertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"}) assertSameStatCalls(t, filesystem.StatCalls, []string{}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{}) finder.Shutdown() } func TestSymlinkPointingToFile(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/a/hi.txt", filesystem) create(t, "/tmp/a/ignoreme.txt", filesystem) link(t, "/tmp/hi.txt", "a/hi.txt", filesystem) link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem) link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem) link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem) link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem) link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem) link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem) // set up the finder and run it once finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "hi.txt") // should search based on the name of the link rather than the destination or validity of the link correctResponse := []string{ "/tmp/a/hi.txt", "/tmp/hi.txt", "/tmp/b/hi.txt", "/tmp/c/hi.txt", "/tmp/d/hi.txt", "/tmp/f/hi.txt", } assertSameResponse(t, foundPaths, correctResponse) } func TestSymlinkPointingToDirectory(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/dir/hi.txt", filesystem) create(t, "/tmp/dir/ignoreme.txt", filesystem) link(t, "/tmp/links/dir", "../dir", filesystem) link(t, "/tmp/links/link", "../dir", filesystem) link(t, "/tmp/links/broken", "nothingHere", filesystem) link(t, "/tmp/links/recursive", "recursive", filesystem) // set up the finder and run it once finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "hi.txt") // should completely ignore symlinks that point to directories correctResponse := []string{ "/tmp/dir/hi.txt", } assertSameResponse(t, foundPaths, correctResponse) } // TestAddPruneFile confirms that adding a prune-file (into a directory for which we // already had a cache) causes the directory to be ignored func TestAddPruneFile(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/out/hi.txt", filesystem) create(t, "/tmp/out/a/hi.txt", filesystem) create(t, "/tmp/hi.txt", filesystem) // do find finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, PruneFiles: []string{".ignore-out-dir"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindNamedAt("/tmp", "hi.txt") // check result assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/out/hi.txt", "/tmp/out/a/hi.txt"}, ) finder.Shutdown() // modify filesystem filesystem.Clock.Tick() create(t, "/tmp/out/.ignore-out-dir", filesystem) // run another find and check its result finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindNamedAt("/tmp", "hi.txt") assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"}) finder2.Shutdown() } func TestUpdatingDbIffChanged(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/a/hi.txt", filesystem) create(t, "/tmp/b/bye.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindAll() filesystem.Clock.Tick() finder.Shutdown() // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"}) // modify the filesystem filesystem.Clock.Tick() create(t, "/tmp/b/hi.txt", filesystem) filesystem.Clock.Tick() filesystem.ClearMetrics() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindAll() finder2.Shutdown() // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"}) expectedDbWriteTime := filesystem.Clock.Time() actualDbWriteTime := modTime(t, finder2.DbPath, filesystem) if actualDbWriteTime != expectedDbWriteTime { t.Fatalf("Expected to write db at %v, actually wrote db at %v\n", expectedDbWriteTime, actualDbWriteTime) } // reset metrics filesystem.ClearMetrics() // run the third finder finder3 := finderWithSameParams(t, finder2) foundPaths = finder3.FindAll() // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"}) assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{}) finder3.Shutdown() actualDbWriteTime = modTime(t, finder3.DbPath, filesystem) if actualDbWriteTime != expectedDbWriteTime { t.Fatalf("Re-wrote db even when contents did not change") } } func TestDirectoryNotPermitted(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/hi.txt", filesystem) create(t, "/tmp/a/hi.txt", filesystem) create(t, "/tmp/a/a/hi.txt", filesystem) create(t, "/tmp/b/hi.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindAll() filesystem.Clock.Tick() finder.Shutdown() allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"} // check results assertSameResponse(t, foundPaths, allPaths) // modify the filesystem filesystem.Clock.Tick() setReadable(t, "/tmp/a", false, filesystem) filesystem.Clock.Tick() // run the second finder finder2 := finderWithSameParams(t, finder) foundPaths = finder2.FindAll() finder2.Shutdown() // check results assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"}) // modify the filesystem back setReadable(t, "/tmp/a", true, filesystem) // run the third finder finder3 := finderWithSameParams(t, finder2) foundPaths = finder3.FindAll() finder3.Shutdown() // check results assertSameResponse(t, foundPaths, allPaths) } func TestFileNotPermitted(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/hi.txt", filesystem) setReadable(t, "/tmp/hi.txt", false, filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindAll() filesystem.Clock.Tick() finder.Shutdown() // check results assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"}) } func TestCacheEntryPathUnexpectedError(t *testing.T) { // setup filesystem filesystem := newFs() create(t, "/tmp/a/hi.txt", filesystem) // run the first finder finder := newFinder( t, filesystem, CacheParams{ RootDirs: []string{"/tmp"}, IncludeFiles: []string{"hi.txt"}, }, ) foundPaths := finder.FindAll() filesystem.Clock.Tick() finder.Shutdown() // check results assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"}) // make the directory not readable setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem) // run the second finder _, err := finderAndErrorWithSameParams(t, finder) if err == nil { fatal(t, "Failed to detect unexpected filesystem error") } }