// 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")
}
}