aboutsummaryrefslogtreecommitdiffstats
path: root/finder/finder_test.go
diff options
context:
space:
mode:
authorJeff Gaston <jeffrygaston@google.com>2017-07-20 16:01:05 -0700
committerJeff Gaston <jeffrygaston@google.com>2017-08-08 14:19:55 -0700
commitb6d161bf16031e06fc8d532e35a53c73ad20f84f (patch)
treec14a049796ec9ff6b353d510fb05604399bae066 /finder/finder_test.go
parent31384debd7e119f8cd054ad89a53adb3dbd6a75d (diff)
downloadbuild_soong-b6d161bf16031e06fc8d532e35a53c73ad20f84f.tar.gz
build_soong-b6d161bf16031e06fc8d532e35a53c73ad20f84f.tar.bz2
build_soong-b6d161bf16031e06fc8d532e35a53c73ad20f84f.zip
Cacheable, multithreaded finder.
It can find every Android.bp in internal master in about 2.5 sec the first time and 0.3 sec subsequent times Bug: 62455338 Test: m -j blueprint_tools # which runs the unit tests Test: m -j blueprint_tools && \ out/soong/host/linux-x86/bin/finder \ -v --db /tmp/mydb \ --names Android.mk \ --prune-files .android-out-dir \ --exclude-dirs .git,.repo \ . \ >/tmp/finder-log 2>&1 Change-Id: I5ab2650459a1dae0d5d076faf411ec2d053c743d
Diffstat (limited to 'finder/finder_test.go')
-rw-r--r--finder/finder_test.go1573
1 files changed, 1573 insertions, 0 deletions
diff --git a/finder/finder_test.go b/finder/finder_test.go
new file mode 100644
index 00000000..60e5eb28
--- /dev/null
+++ b/finder/finder_test.go
@@ -0,0 +1,1573 @@
+// 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"
+ "log"
+ "path/filepath"
+ "reflect"
+ "testing"
+
+ "sort"
+
+ "io/ioutil"
+
+ "android/soong/fs"
+ "runtime/debug"
+ "time"
+)
+
+// 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 {
+ cachePath := "/finder/finder-db"
+ cacheDir := filepath.Dir(cachePath)
+ filesystem.MkDirs(cacheDir)
+ if cacheParams.WorkingDirectory == "" {
+ cacheParams.WorkingDirectory = "/cwd"
+ }
+
+ logger := log.New(ioutil.Discard, "", 0)
+ finder := New(cacheParams, filesystem, logger, cachePath)
+ return finder
+}
+
+func finderWithSameParams(t *testing.T, original *Finder) *Finder {
+ return New(
+ original.cacheMetadata.Config.CacheParams,
+ original.filesystem,
+ original.logger,
+ original.DbPath)
+}
+
+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 {
+ t.Fatal(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 {
+ t.Fatal(err.Error())
+ }
+}
+
+func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
+ err := filesystem.RemoveAll(path)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+}
+
+func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
+ err := filesystem.Rename(oldPath, newPath)
+ if err != nil {
+ t.Fatal(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 {
+ t.Fatal(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 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)
+}
+
+// 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) {
+ filesystem := newFs()
+ root := "/"
+ createdPath := "/findme.txt"
+ create(t, createdPath, filesystem)
+
+ finder := newFinder(
+ t,
+ filesystem,
+ CacheParams{
+ RootDirs: []string{root},
+ IncludeFiles: []string{"findme.txt", "skipme.txt"},
+ },
+ )
+ defer finder.Shutdown()
+
+ foundPaths := finder.FindNamedAt(root, "findme.txt")
+
+ assertSameResponse(t, foundPaths, []string{createdPath})
+}
+
+func TestNonexistentPath(t *testing.T) {
+ filesystem := newFs()
+ create(t, "/tmp/findme.txt", filesystem)
+
+ finder := newFinder(
+ t,
+ filesystem,
+ CacheParams{
+ RootDirs: []string{"/tmp/IDontExist"},
+ IncludeFiles: []string{"findme.txt", "skipme.txt"},
+ },
+ )
+ defer finder.Shutdown()
+
+ foundPaths := finder.FindNamedAt("/tmp/IAlsoDontExist", "findme.txt")
+
+ assertSameResponse(t, foundPaths, []string{})
+}
+
+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"})
+}
+
+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{"/IDoNotExist"},
+ 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)
+
+ finder := newFinder(
+ t,
+ filesystem,
+ CacheParams{
+ RootDirs: []string{"/cwd", "/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("/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{"/", "/a/b/c", "/a/b/c/d/e/f", "/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) {
+ 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 := newFinder(
+ t,
+ filesystem,
+ CacheParams{
+ RootDirs: []string{"/tmp"},
+ IncludeFiles: []string{"findme.txt"},
+ },
+ )
+ 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)
+ }
+}
+
+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"})
+}