From b6d161bf16031e06fc8d532e35a53c73ad20f84f Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Thu, 20 Jul 2017 16:01:05 -0700 Subject: 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 --- finder/finder_test.go | 1573 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1573 insertions(+) create mode 100644 finder/finder_test.go (limited to 'finder/finder_test.go') 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"}) +} -- cgit v1.2.3