github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/finder/finder_test.go (about)

     1  // Copyright 2017 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package finder
    16  
    17  import (
    18  	"fmt"
    19  	"io/ioutil"
    20  	"log"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"runtime/debug"
    25  	"sort"
    26  	"testing"
    27  	"time"
    28  
    29  	"android/soong/finder/fs"
    30  )
    31  
    32  // some utils for tests to use
    33  func newFs() *fs.MockFs {
    34  	return fs.NewMockFs(map[string][]byte{})
    35  }
    36  
    37  func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
    38  	return newFinderWithNumThreads(t, filesystem, cacheParams, 2)
    39  }
    40  
    41  func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder {
    42  	f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads)
    43  	if err != nil {
    44  		fatal(t, err.Error())
    45  	}
    46  	return f
    47  }
    48  
    49  func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) (*Finder, error) {
    50  	cachePath := "/finder/finder-db"
    51  	cacheDir := filepath.Dir(cachePath)
    52  	filesystem.MkDirs(cacheDir)
    53  	if cacheParams.WorkingDirectory == "" {
    54  		cacheParams.WorkingDirectory = "/cwd"
    55  	}
    56  
    57  	logger := log.New(ioutil.Discard, "", 0)
    58  	f, err := newImpl(cacheParams, filesystem, logger, cachePath, numThreads)
    59  	return f, err
    60  }
    61  
    62  func finderWithSameParams(t *testing.T, original *Finder) *Finder {
    63  	f, err := finderAndErrorWithSameParams(t, original)
    64  	if err != nil {
    65  		fatal(t, err.Error())
    66  	}
    67  	return f
    68  }
    69  
    70  func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) {
    71  	f, err := newImpl(
    72  		original.cacheMetadata.Config.CacheParams,
    73  		original.filesystem,
    74  		original.logger,
    75  		original.DbPath,
    76  		original.numDbLoadingThreads,
    77  	)
    78  	return f, err
    79  }
    80  
    81  func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
    82  	parent := filepath.Dir(path)
    83  	filesystem.MkDirs(parent)
    84  	err := filesystem.WriteFile(path, []byte(content), 0777)
    85  	if err != nil {
    86  		fatal(t, err.Error())
    87  	}
    88  }
    89  
    90  func create(t *testing.T, path string, filesystem *fs.MockFs) {
    91  	write(t, path, "hi", filesystem)
    92  }
    93  
    94  func delete(t *testing.T, path string, filesystem *fs.MockFs) {
    95  	err := filesystem.Remove(path)
    96  	if err != nil {
    97  		fatal(t, err.Error())
    98  	}
    99  }
   100  
   101  func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
   102  	err := filesystem.RemoveAll(path)
   103  	if err != nil {
   104  		fatal(t, err.Error())
   105  	}
   106  }
   107  
   108  func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
   109  	err := filesystem.Rename(oldPath, newPath)
   110  	if err != nil {
   111  		fatal(t, err.Error())
   112  	}
   113  }
   114  
   115  func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) {
   116  	parentPath := filepath.Dir(newPath)
   117  	err := filesystem.MkDirs(parentPath)
   118  	if err != nil {
   119  		t.Fatal(err.Error())
   120  	}
   121  	err = filesystem.Symlink(oldPath, newPath)
   122  	if err != nil {
   123  		fatal(t, err.Error())
   124  	}
   125  }
   126  func read(t *testing.T, path string, filesystem *fs.MockFs) string {
   127  	reader, err := filesystem.Open(path)
   128  	if err != nil {
   129  		t.Fatalf(err.Error())
   130  	}
   131  	bytes, err := ioutil.ReadAll(reader)
   132  	if err != nil {
   133  		t.Fatal(err.Error())
   134  	}
   135  	return string(bytes)
   136  }
   137  func modTime(t *testing.T, path string, filesystem *fs.MockFs) time.Time {
   138  	stats, err := filesystem.Lstat(path)
   139  	if err != nil {
   140  		t.Fatal(err.Error())
   141  	}
   142  	return stats.ModTime()
   143  }
   144  func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs) {
   145  	err := filesystem.SetReadable(path, readable)
   146  	if err != nil {
   147  		t.Fatal(err.Error())
   148  	}
   149  }
   150  
   151  func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) {
   152  	err := filesystem.SetReadErr(path, readErr)
   153  	if err != nil {
   154  		t.Fatal(err.Error())
   155  	}
   156  }
   157  
   158  func fatal(t *testing.T, message string) {
   159  	t.Error(message)
   160  	debug.PrintStack()
   161  	t.FailNow()
   162  }
   163  
   164  func assertSameResponse(t *testing.T, actual []string, expected []string) {
   165  	sort.Strings(actual)
   166  	sort.Strings(expected)
   167  	if !reflect.DeepEqual(actual, expected) {
   168  		fatal(
   169  			t,
   170  			fmt.Sprintf(
   171  				"Expected Finder to return these %v paths:\n  %v,\ninstead returned these %v paths:  %v\n",
   172  				len(expected), expected, len(actual), actual),
   173  		)
   174  	}
   175  }
   176  
   177  func assertSameStatCalls(t *testing.T, actual []string, expected []string) {
   178  	sort.Strings(actual)
   179  	sort.Strings(expected)
   180  
   181  	if !reflect.DeepEqual(actual, expected) {
   182  		fatal(
   183  			t,
   184  			fmt.Sprintf(
   185  				"Finder made incorrect Stat calls.\n"+
   186  					"Actual:\n"+
   187  					"%v\n"+
   188  					"Expected:\n"+
   189  					"%v\n"+
   190  					"\n",
   191  				actual, expected),
   192  		)
   193  	}
   194  }
   195  func assertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
   196  	sort.Strings(actual)
   197  	sort.Strings(expected)
   198  
   199  	if !reflect.DeepEqual(actual, expected) {
   200  		fatal(
   201  			t,
   202  			fmt.Sprintf(
   203  				"Finder made incorrect ReadDir calls.\n"+
   204  					"Actual:\n"+
   205  					"%v\n"+
   206  					"Expected:\n"+
   207  					"%v\n"+
   208  					"\n",
   209  				actual, expected),
   210  		)
   211  	}
   212  }
   213  
   214  // runSimpleTests creates a few files, searches for findme.txt, and checks for the expected matches
   215  func runSimpleTest(t *testing.T, existentPaths []string, expectedMatches []string) {
   216  	filesystem := newFs()
   217  	root := "/tmp"
   218  	filesystem.MkDirs(root)
   219  	for _, path := range existentPaths {
   220  		create(t, filepath.Join(root, path), filesystem)
   221  	}
   222  
   223  	finder := newFinder(t,
   224  		filesystem,
   225  		CacheParams{
   226  			"/cwd",
   227  			[]string{root},
   228  			nil,
   229  			nil,
   230  			[]string{"findme.txt", "skipme.txt"},
   231  		},
   232  	)
   233  	defer finder.Shutdown()
   234  
   235  	foundPaths := finder.FindNamedAt(root, "findme.txt")
   236  	absoluteMatches := []string{}
   237  	for i := range expectedMatches {
   238  		absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i]))
   239  	}
   240  	assertSameResponse(t, foundPaths, absoluteMatches)
   241  }
   242  
   243  // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
   244  func testAgainstSeveralThreadcounts(t *testing.T, tester func(t *testing.T, numThreads int)) {
   245  	// test singlethreaded, multithreaded, and also using the same number of threads as
   246  	// will be used on the current system
   247  	threadCounts := []int{1, 2, defaultNumThreads}
   248  	for _, numThreads := range threadCounts {
   249  		testName := fmt.Sprintf("%v threads", numThreads)
   250  		// store numThreads in a new variable to prevent numThreads from changing in each loop
   251  		localNumThreads := numThreads
   252  		t.Run(testName, func(t *testing.T) {
   253  			tester(t, localNumThreads)
   254  		})
   255  	}
   256  }
   257  
   258  // end of utils, start of individual tests
   259  
   260  func TestSingleFile(t *testing.T) {
   261  	runSimpleTest(t,
   262  		[]string{"findme.txt"},
   263  		[]string{"findme.txt"},
   264  	)
   265  }
   266  
   267  func TestIncludeFiles(t *testing.T) {
   268  	runSimpleTest(t,
   269  		[]string{"findme.txt", "skipme.txt"},
   270  		[]string{"findme.txt"},
   271  	)
   272  }
   273  
   274  func TestNestedDirectories(t *testing.T) {
   275  	runSimpleTest(t,
   276  		[]string{"findme.txt", "skipme.txt", "subdir/findme.txt", "subdir/skipme.txt"},
   277  		[]string{"findme.txt", "subdir/findme.txt"},
   278  	)
   279  }
   280  
   281  func TestEmptyDirectory(t *testing.T) {
   282  	runSimpleTest(t,
   283  		[]string{},
   284  		[]string{},
   285  	)
   286  }
   287  
   288  func TestEmptyPath(t *testing.T) {
   289  	filesystem := newFs()
   290  	root := "/tmp"
   291  	create(t, filepath.Join(root, "findme.txt"), filesystem)
   292  
   293  	finder := newFinder(
   294  		t,
   295  		filesystem,
   296  		CacheParams{
   297  			RootDirs:     []string{root},
   298  			IncludeFiles: []string{"findme.txt", "skipme.txt"},
   299  		},
   300  	)
   301  	defer finder.Shutdown()
   302  
   303  	foundPaths := finder.FindNamedAt("", "findme.txt")
   304  
   305  	assertSameResponse(t, foundPaths, []string{})
   306  }
   307  
   308  func TestFilesystemRoot(t *testing.T) {
   309  
   310  	testWithNumThreads := func(t *testing.T, numThreads int) {
   311  		filesystem := newFs()
   312  		root := "/"
   313  		createdPath := "/findme.txt"
   314  		create(t, createdPath, filesystem)
   315  
   316  		finder := newFinderWithNumThreads(
   317  			t,
   318  			filesystem,
   319  			CacheParams{
   320  				RootDirs:     []string{root},
   321  				IncludeFiles: []string{"findme.txt", "skipme.txt"},
   322  			},
   323  			numThreads,
   324  		)
   325  		defer finder.Shutdown()
   326  
   327  		foundPaths := finder.FindNamedAt(root, "findme.txt")
   328  
   329  		assertSameResponse(t, foundPaths, []string{createdPath})
   330  	}
   331  
   332  	testAgainstSeveralThreadcounts(t, testWithNumThreads)
   333  }
   334  
   335  func TestNonexistentDir(t *testing.T) {
   336  	filesystem := newFs()
   337  	create(t, "/tmp/findme.txt", filesystem)
   338  
   339  	_, err := newFinderAndErr(
   340  		t,
   341  		filesystem,
   342  		CacheParams{
   343  			RootDirs:     []string{"/tmp/IDontExist"},
   344  			IncludeFiles: []string{"findme.txt", "skipme.txt"},
   345  		},
   346  		1,
   347  	)
   348  	if err == nil {
   349  		fatal(t, "Did not fail when given a nonexistent root directory")
   350  	}
   351  }
   352  
   353  func TestExcludeDirs(t *testing.T) {
   354  	filesystem := newFs()
   355  	create(t, "/tmp/exclude/findme.txt", filesystem)
   356  	create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
   357  	create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
   358  	create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
   359  	create(t, "/tmp/subdir/findme.txt", filesystem)
   360  	create(t, "/tmp/findme.txt", filesystem)
   361  
   362  	finder := newFinder(
   363  		t,
   364  		filesystem,
   365  		CacheParams{
   366  			RootDirs:     []string{"/tmp"},
   367  			ExcludeDirs:  []string{"exclude"},
   368  			IncludeFiles: []string{"findme.txt", "skipme.txt"},
   369  		},
   370  	)
   371  	defer finder.Shutdown()
   372  
   373  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   374  
   375  	assertSameResponse(t, foundPaths,
   376  		[]string{"/tmp/findme.txt",
   377  			"/tmp/subdir/findme.txt",
   378  			"/tmp/subdir/subdir/findme.txt"})
   379  }
   380  
   381  func TestPruneFiles(t *testing.T) {
   382  	filesystem := newFs()
   383  	create(t, "/tmp/out/findme.txt", filesystem)
   384  	create(t, "/tmp/out/.ignore-out-dir", filesystem)
   385  	create(t, "/tmp/out/child/findme.txt", filesystem)
   386  
   387  	create(t, "/tmp/out2/.ignore-out-dir", filesystem)
   388  	create(t, "/tmp/out2/sub/findme.txt", filesystem)
   389  
   390  	create(t, "/tmp/findme.txt", filesystem)
   391  	create(t, "/tmp/include/findme.txt", filesystem)
   392  
   393  	finder := newFinder(
   394  		t,
   395  		filesystem,
   396  		CacheParams{
   397  			RootDirs:     []string{"/tmp"},
   398  			PruneFiles:   []string{".ignore-out-dir"},
   399  			IncludeFiles: []string{"findme.txt"},
   400  		},
   401  	)
   402  	defer finder.Shutdown()
   403  
   404  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   405  
   406  	assertSameResponse(t, foundPaths,
   407  		[]string{"/tmp/findme.txt",
   408  			"/tmp/include/findme.txt"})
   409  }
   410  
   411  // TestRootDir tests that the value of RootDirs is used
   412  // tests of the filesystem root are in TestFilesystemRoot
   413  func TestRootDir(t *testing.T) {
   414  	filesystem := newFs()
   415  	create(t, "/tmp/a/findme.txt", filesystem)
   416  	create(t, "/tmp/a/subdir/findme.txt", filesystem)
   417  	create(t, "/tmp/b/findme.txt", filesystem)
   418  	create(t, "/tmp/b/subdir/findme.txt", filesystem)
   419  
   420  	finder := newFinder(
   421  		t,
   422  		filesystem,
   423  		CacheParams{
   424  			RootDirs:     []string{"/tmp/a"},
   425  			IncludeFiles: []string{"findme.txt"},
   426  		},
   427  	)
   428  	defer finder.Shutdown()
   429  
   430  	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
   431  
   432  	assertSameResponse(t, foundPaths,
   433  		[]string{"/tmp/a/findme.txt",
   434  			"/tmp/a/subdir/findme.txt"})
   435  }
   436  
   437  func TestUncachedDir(t *testing.T) {
   438  	filesystem := newFs()
   439  	create(t, "/tmp/a/findme.txt", filesystem)
   440  	create(t, "/tmp/a/subdir/findme.txt", filesystem)
   441  	create(t, "/tmp/b/findme.txt", filesystem)
   442  	create(t, "/tmp/b/subdir/findme.txt", filesystem)
   443  
   444  	finder := newFinder(
   445  		t,
   446  		filesystem,
   447  		CacheParams{
   448  			RootDirs:     []string{"/tmp/b"},
   449  			IncludeFiles: []string{"findme.txt"},
   450  		},
   451  	)
   452  
   453  	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
   454  	// If the caller queries for a file that is in the cache, then computing the
   455  	// correct answer won't be fast, and it would be easy for the caller to
   456  	// fail to notice its slowness. Instead, we only ever search the cache for files
   457  	// to return, which enforces that we can determine which files will be
   458  	// interesting upfront.
   459  	assertSameResponse(t, foundPaths, []string{})
   460  
   461  	finder.Shutdown()
   462  }
   463  
   464  func TestSearchingForFilesExcludedFromCache(t *testing.T) {
   465  	// setup filesystem
   466  	filesystem := newFs()
   467  	create(t, "/tmp/findme.txt", filesystem)
   468  	create(t, "/tmp/a/findme.txt", filesystem)
   469  	create(t, "/tmp/a/misc.txt", filesystem)
   470  
   471  	// set up the finder and run it
   472  	finder := newFinder(
   473  		t,
   474  		filesystem,
   475  		CacheParams{
   476  			RootDirs:     []string{"/tmp"},
   477  			IncludeFiles: []string{"findme.txt"},
   478  		},
   479  	)
   480  	foundPaths := finder.FindNamedAt("/tmp", "misc.txt")
   481  	// If the caller queries for a file that is in the cache, then computing the
   482  	// correct answer won't be fast, and it would be easy for the caller to
   483  	// fail to notice its slowness. Instead, we only ever search the cache for files
   484  	// to return, which enforces that we can determine which files will be
   485  	// interesting upfront.
   486  	assertSameResponse(t, foundPaths, []string{})
   487  
   488  	finder.Shutdown()
   489  }
   490  
   491  func TestRelativeFilePaths(t *testing.T) {
   492  	filesystem := newFs()
   493  
   494  	create(t, "/tmp/ignore/hi.txt", filesystem)
   495  	create(t, "/tmp/include/hi.txt", filesystem)
   496  	create(t, "/cwd/hi.txt", filesystem)
   497  	create(t, "/cwd/a/hi.txt", filesystem)
   498  	create(t, "/cwd/a/a/hi.txt", filesystem)
   499  	create(t, "/rel/a/hi.txt", filesystem)
   500  
   501  	finder := newFinder(
   502  		t,
   503  		filesystem,
   504  		CacheParams{
   505  			RootDirs:     []string{"/cwd", "../rel", "/tmp/include"},
   506  			IncludeFiles: []string{"hi.txt"},
   507  		},
   508  	)
   509  	defer finder.Shutdown()
   510  
   511  	foundPaths := finder.FindNamedAt("a", "hi.txt")
   512  	assertSameResponse(t, foundPaths,
   513  		[]string{"a/hi.txt",
   514  			"a/a/hi.txt"})
   515  
   516  	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
   517  	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
   518  
   519  	foundPaths = finder.FindNamedAt(".", "hi.txt")
   520  	assertSameResponse(t, foundPaths,
   521  		[]string{"hi.txt",
   522  			"a/hi.txt",
   523  			"a/a/hi.txt"})
   524  
   525  	foundPaths = finder.FindNamedAt("/rel", "hi.txt")
   526  	assertSameResponse(t, foundPaths,
   527  		[]string{"/rel/a/hi.txt"})
   528  
   529  	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
   530  	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
   531  }
   532  
   533  // have to run this test with the race-detector (`go test -race src/android/soong/finder/*.go`)
   534  // for there to be much chance of the test actually detecting any error that may be present
   535  func TestRootDirsContainedInOtherRootDirs(t *testing.T) {
   536  	filesystem := newFs()
   537  
   538  	create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
   539  
   540  	finder := newFinder(
   541  		t,
   542  		filesystem,
   543  		CacheParams{
   544  			RootDirs:     []string{"/", "/tmp/a/b/c", "/tmp/a/b/c/d/e/f", "/tmp/a/b/c/d/e/f/g/h/i"},
   545  			IncludeFiles: []string{"findme.txt"},
   546  		},
   547  	)
   548  	defer finder.Shutdown()
   549  
   550  	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
   551  
   552  	assertSameResponse(t, foundPaths,
   553  		[]string{"/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt"})
   554  }
   555  
   556  func TestFindFirst(t *testing.T) {
   557  	filesystem := newFs()
   558  	create(t, "/tmp/a/hi.txt", filesystem)
   559  	create(t, "/tmp/b/hi.txt", filesystem)
   560  	create(t, "/tmp/b/a/hi.txt", filesystem)
   561  
   562  	finder := newFinder(
   563  		t,
   564  		filesystem,
   565  		CacheParams{
   566  			RootDirs:     []string{"/tmp"},
   567  			IncludeFiles: []string{"hi.txt"},
   568  		},
   569  	)
   570  	defer finder.Shutdown()
   571  
   572  	foundPaths := finder.FindFirstNamed("hi.txt")
   573  
   574  	assertSameResponse(t, foundPaths,
   575  		[]string{"/tmp/a/hi.txt",
   576  			"/tmp/b/hi.txt"},
   577  	)
   578  }
   579  
   580  func TestConcurrentFindSameDirectory(t *testing.T) {
   581  
   582  	testWithNumThreads := func(t *testing.T, numThreads int) {
   583  		filesystem := newFs()
   584  
   585  		// create a bunch of files and directories
   586  		paths := []string{}
   587  		for i := 0; i < 10; i++ {
   588  			parentDir := fmt.Sprintf("/tmp/%v", i)
   589  			for j := 0; j < 10; j++ {
   590  				filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
   591  				paths = append(paths, filePath)
   592  			}
   593  		}
   594  		sort.Strings(paths)
   595  		for _, path := range paths {
   596  			create(t, path, filesystem)
   597  		}
   598  
   599  		// set up a finder
   600  		finder := newFinderWithNumThreads(
   601  			t,
   602  			filesystem,
   603  			CacheParams{
   604  				RootDirs:     []string{"/tmp"},
   605  				IncludeFiles: []string{"findme.txt"},
   606  			},
   607  			numThreads,
   608  		)
   609  		defer finder.Shutdown()
   610  
   611  		numTests := 20
   612  		results := make(chan []string, numTests)
   613  		// make several parallel calls to the finder
   614  		for i := 0; i < numTests; i++ {
   615  			go func() {
   616  				foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   617  				results <- foundPaths
   618  			}()
   619  		}
   620  
   621  		// check that each response was correct
   622  		for i := 0; i < numTests; i++ {
   623  			foundPaths := <-results
   624  			assertSameResponse(t, foundPaths, paths)
   625  		}
   626  	}
   627  
   628  	testAgainstSeveralThreadcounts(t, testWithNumThreads)
   629  }
   630  
   631  func TestConcurrentFindDifferentDirectories(t *testing.T) {
   632  	filesystem := newFs()
   633  
   634  	// create a bunch of files and directories
   635  	allFiles := []string{}
   636  	numSubdirs := 10
   637  	rootPaths := []string{}
   638  	queryAnswers := [][]string{}
   639  	for i := 0; i < numSubdirs; i++ {
   640  		parentDir := fmt.Sprintf("/tmp/%v", i)
   641  		rootPaths = append(rootPaths, parentDir)
   642  		queryAnswers = append(queryAnswers, []string{})
   643  		for j := 0; j < 10; j++ {
   644  			filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
   645  			queryAnswers[i] = append(queryAnswers[i], filePath)
   646  			allFiles = append(allFiles, filePath)
   647  		}
   648  		sort.Strings(queryAnswers[i])
   649  	}
   650  	sort.Strings(allFiles)
   651  	for _, path := range allFiles {
   652  		create(t, path, filesystem)
   653  	}
   654  
   655  	// set up a finder
   656  	finder := newFinder(
   657  		t,
   658  		filesystem,
   659  
   660  		CacheParams{
   661  			RootDirs:     []string{"/tmp"},
   662  			IncludeFiles: []string{"findme.txt"},
   663  		},
   664  	)
   665  	defer finder.Shutdown()
   666  
   667  	type testRun struct {
   668  		path           string
   669  		foundMatches   []string
   670  		correctMatches []string
   671  	}
   672  
   673  	numTests := numSubdirs + 1
   674  	testRuns := make(chan testRun, numTests)
   675  
   676  	searchAt := func(path string, correctMatches []string) {
   677  		foundPaths := finder.FindNamedAt(path, "findme.txt")
   678  		testRuns <- testRun{path, foundPaths, correctMatches}
   679  	}
   680  
   681  	// make several parallel calls to the finder
   682  	go searchAt("/tmp", allFiles)
   683  	for i := 0; i < len(rootPaths); i++ {
   684  		go searchAt(rootPaths[i], queryAnswers[i])
   685  	}
   686  
   687  	// check that each response was correct
   688  	for i := 0; i < numTests; i++ {
   689  		testRun := <-testRuns
   690  		assertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
   691  	}
   692  }
   693  
   694  func TestStrangelyFormattedPaths(t *testing.T) {
   695  	filesystem := newFs()
   696  
   697  	create(t, "/tmp/findme.txt", filesystem)
   698  	create(t, "/tmp/a/findme.txt", filesystem)
   699  	create(t, "/tmp/b/findme.txt", filesystem)
   700  
   701  	finder := newFinder(
   702  		t,
   703  		filesystem,
   704  		CacheParams{
   705  			RootDirs:     []string{"//tmp//a//.."},
   706  			IncludeFiles: []string{"findme.txt"},
   707  		},
   708  	)
   709  	defer finder.Shutdown()
   710  
   711  	foundPaths := finder.FindNamedAt("//tmp//a//..", "findme.txt")
   712  
   713  	assertSameResponse(t, foundPaths,
   714  		[]string{"/tmp/a/findme.txt",
   715  			"/tmp/b/findme.txt",
   716  			"/tmp/findme.txt"})
   717  }
   718  
   719  func TestCorruptedCacheHeader(t *testing.T) {
   720  	filesystem := newFs()
   721  
   722  	create(t, "/tmp/findme.txt", filesystem)
   723  	create(t, "/tmp/a/findme.txt", filesystem)
   724  	write(t, "/finder/finder-db", "sample header", filesystem)
   725  
   726  	finder := newFinder(
   727  		t,
   728  		filesystem,
   729  		CacheParams{
   730  			RootDirs:     []string{"/tmp"},
   731  			IncludeFiles: []string{"findme.txt"},
   732  		},
   733  	)
   734  	defer finder.Shutdown()
   735  
   736  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   737  
   738  	assertSameResponse(t, foundPaths,
   739  		[]string{"/tmp/a/findme.txt",
   740  			"/tmp/findme.txt"})
   741  }
   742  
   743  func TestCanUseCache(t *testing.T) {
   744  	// setup filesystem
   745  	filesystem := newFs()
   746  	create(t, "/tmp/findme.txt", filesystem)
   747  	create(t, "/tmp/a/findme.txt", filesystem)
   748  
   749  	// run the first finder
   750  	finder := newFinder(
   751  		t,
   752  		filesystem,
   753  		CacheParams{
   754  			RootDirs:     []string{"/tmp"},
   755  			IncludeFiles: []string{"findme.txt"},
   756  		},
   757  	)
   758  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   759  	// check the response of the first finder
   760  	correctResponse := []string{"/tmp/a/findme.txt",
   761  		"/tmp/findme.txt"}
   762  	assertSameResponse(t, foundPaths, correctResponse)
   763  	finder.Shutdown()
   764  
   765  	// check results
   766  	cacheText := read(t, finder.DbPath, filesystem)
   767  	if len(cacheText) < 1 {
   768  		t.Fatalf("saved cache db is empty\n")
   769  	}
   770  	if len(filesystem.StatCalls) == 0 {
   771  		t.Fatal("No Stat calls recorded by mock filesystem")
   772  	}
   773  	if len(filesystem.ReadDirCalls) == 0 {
   774  		t.Fatal("No ReadDir calls recorded by filesystem")
   775  	}
   776  	statCalls := filesystem.StatCalls
   777  	filesystem.ClearMetrics()
   778  
   779  	// run the second finder
   780  	finder2 := finderWithSameParams(t, finder)
   781  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
   782  	// check results
   783  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
   784  	assertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
   785  
   786  	finder2.Shutdown()
   787  }
   788  
   789  func TestCorruptedCacheBody(t *testing.T) {
   790  	// setup filesystem
   791  	filesystem := newFs()
   792  	create(t, "/tmp/findme.txt", filesystem)
   793  	create(t, "/tmp/a/findme.txt", filesystem)
   794  
   795  	// run the first finder
   796  	finder := newFinder(
   797  		t,
   798  		filesystem,
   799  		CacheParams{
   800  			RootDirs:     []string{"/tmp"},
   801  			IncludeFiles: []string{"findme.txt"},
   802  		},
   803  	)
   804  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   805  	finder.Shutdown()
   806  
   807  	// check the response of the first finder
   808  	correctResponse := []string{"/tmp/a/findme.txt",
   809  		"/tmp/findme.txt"}
   810  	assertSameResponse(t, foundPaths, correctResponse)
   811  	numStatCalls := len(filesystem.StatCalls)
   812  	numReadDirCalls := len(filesystem.ReadDirCalls)
   813  
   814  	// load the cache file, corrupt it, and save it
   815  	cacheReader, err := filesystem.Open(finder.DbPath)
   816  	if err != nil {
   817  		t.Fatal(err)
   818  	}
   819  	cacheData, err := ioutil.ReadAll(cacheReader)
   820  	if err != nil {
   821  		t.Fatal(err)
   822  	}
   823  	cacheData = append(cacheData, []byte("DontMindMe")...)
   824  	filesystem.WriteFile(finder.DbPath, cacheData, 0777)
   825  	filesystem.ClearMetrics()
   826  
   827  	// run the second finder
   828  	finder2 := finderWithSameParams(t, finder)
   829  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
   830  	// check results
   831  	assertSameResponse(t, foundPaths, correctResponse)
   832  	numNewStatCalls := len(filesystem.StatCalls)
   833  	numNewReadDirCalls := len(filesystem.ReadDirCalls)
   834  	// It's permissable to make more Stat calls with a corrupted cache because
   835  	// the Finder may restart once it detects corruption.
   836  	// However, it may have already issued many Stat calls.
   837  	// Because a corrupted db is not expected to be a common (or even a supported case),
   838  	// we don't care to optimize it and don't cache the already-issued Stat calls
   839  	if numNewReadDirCalls < numReadDirCalls {
   840  		t.Fatalf(
   841  			"Finder made fewer ReadDir calls with a corrupted cache (%v calls) than with no cache"+
   842  				" (%v calls)",
   843  			numNewReadDirCalls, numReadDirCalls)
   844  	}
   845  	if numNewStatCalls < numStatCalls {
   846  		t.Fatalf(
   847  			"Finder made fewer Stat calls with a corrupted cache (%v calls) than with no cache (%v calls)",
   848  			numNewStatCalls, numStatCalls)
   849  	}
   850  	finder2.Shutdown()
   851  }
   852  
   853  func TestStatCalls(t *testing.T) {
   854  	// setup filesystem
   855  	filesystem := newFs()
   856  	create(t, "/tmp/a/findme.txt", filesystem)
   857  
   858  	// run finder
   859  	finder := newFinder(
   860  		t,
   861  		filesystem,
   862  		CacheParams{
   863  			RootDirs:     []string{"/tmp"},
   864  			IncludeFiles: []string{"findme.txt"},
   865  		},
   866  	)
   867  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   868  	finder.Shutdown()
   869  
   870  	// check response
   871  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
   872  	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
   873  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
   874  }
   875  
   876  func TestFileAdded(t *testing.T) {
   877  	// setup filesystem
   878  	filesystem := newFs()
   879  	create(t, "/tmp/ignoreme.txt", filesystem)
   880  	create(t, "/tmp/a/findme.txt", filesystem)
   881  	create(t, "/tmp/b/ignore.txt", filesystem)
   882  	create(t, "/tmp/b/c/nope.txt", filesystem)
   883  	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
   884  
   885  	// run the first finder
   886  	finder := newFinder(
   887  		t,
   888  		filesystem,
   889  		CacheParams{
   890  			RootDirs:     []string{"/tmp"},
   891  			IncludeFiles: []string{"findme.txt"},
   892  		},
   893  	)
   894  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   895  	filesystem.Clock.Tick()
   896  	finder.Shutdown()
   897  	// check the response of the first finder
   898  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
   899  
   900  	// modify the filesystem
   901  	filesystem.Clock.Tick()
   902  	create(t, "/tmp/b/c/findme.txt", filesystem)
   903  	filesystem.Clock.Tick()
   904  	filesystem.ClearMetrics()
   905  
   906  	// run the second finder
   907  	finder2 := finderWithSameParams(t, finder)
   908  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
   909  
   910  	// check results
   911  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
   912  	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
   913  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
   914  	finder2.Shutdown()
   915  
   916  }
   917  
   918  func TestDirectoriesAdded(t *testing.T) {
   919  	// setup filesystem
   920  	filesystem := newFs()
   921  	create(t, "/tmp/ignoreme.txt", filesystem)
   922  	create(t, "/tmp/a/findme.txt", filesystem)
   923  	create(t, "/tmp/b/ignore.txt", filesystem)
   924  	create(t, "/tmp/b/c/nope.txt", filesystem)
   925  	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
   926  
   927  	// run the first finder
   928  	finder := newFinder(
   929  		t,
   930  		filesystem,
   931  		CacheParams{
   932  			RootDirs:     []string{"/tmp"},
   933  			IncludeFiles: []string{"findme.txt"},
   934  		},
   935  	)
   936  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
   937  	finder.Shutdown()
   938  	// check the response of the first finder
   939  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
   940  
   941  	// modify the filesystem
   942  	filesystem.Clock.Tick()
   943  	create(t, "/tmp/b/c/new/findme.txt", filesystem)
   944  	create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
   945  	create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
   946  	filesystem.ClearMetrics()
   947  
   948  	// run the second finder
   949  	finder2 := finderWithSameParams(t, finder)
   950  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
   951  
   952  	// check results
   953  	assertSameResponse(t, foundPaths,
   954  		[]string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"})
   955  	assertSameStatCalls(t, filesystem.StatCalls,
   956  		[]string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
   957  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
   958  
   959  	finder2.Shutdown()
   960  }
   961  
   962  func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) {
   963  	// setup filesystem
   964  	filesystem := newFs()
   965  	create(t, "/tmp/hi1.txt", filesystem)
   966  	create(t, "/tmp/a/hi1.txt", filesystem)
   967  
   968  	// run the first finder
   969  	finder := newFinder(
   970  		t,
   971  		filesystem,
   972  		CacheParams{
   973  			RootDirs:     []string{"/tmp"},
   974  			IncludeFiles: []string{"hi1.txt", "hi2.txt"},
   975  		},
   976  	)
   977  	foundPaths := finder.FindNamedAt("/tmp", "hi1.txt")
   978  	finder.Shutdown()
   979  	// check the response of the first finder
   980  	assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
   981  
   982  	// modify the filesystem
   983  	filesystem.Clock.Tick()
   984  	create(t, "/tmp/hi2.txt", filesystem)
   985  	create(t, "/tmp/a/hi2.txt", filesystem)
   986  	filesystem.ClearMetrics()
   987  
   988  	// run the second finder
   989  	finder2 := finderWithSameParams(t, finder)
   990  	foundPaths = finder2.FindAll()
   991  
   992  	// check results
   993  	assertSameResponse(t, foundPaths,
   994  		[]string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"})
   995  	assertSameStatCalls(t, filesystem.StatCalls,
   996  		[]string{"/tmp", "/tmp/a"})
   997  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
   998  
   999  	finder2.Shutdown()
  1000  }
  1001  
  1002  func TestFileDeleted(t *testing.T) {
  1003  	// setup filesystem
  1004  	filesystem := newFs()
  1005  	create(t, "/tmp/ignoreme.txt", filesystem)
  1006  	create(t, "/tmp/a/findme.txt", filesystem)
  1007  	create(t, "/tmp/b/findme.txt", filesystem)
  1008  	create(t, "/tmp/b/c/nope.txt", filesystem)
  1009  	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
  1010  
  1011  	// run the first finder
  1012  	finder := newFinder(
  1013  		t,
  1014  		filesystem,
  1015  		CacheParams{
  1016  			RootDirs:     []string{"/tmp"},
  1017  			IncludeFiles: []string{"findme.txt"},
  1018  		},
  1019  	)
  1020  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1021  	finder.Shutdown()
  1022  	// check the response of the first finder
  1023  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
  1024  
  1025  	// modify the filesystem
  1026  	filesystem.Clock.Tick()
  1027  	delete(t, "/tmp/b/findme.txt", filesystem)
  1028  	filesystem.ClearMetrics()
  1029  
  1030  	// run the second finder
  1031  	finder2 := finderWithSameParams(t, finder)
  1032  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
  1033  
  1034  	// check results
  1035  	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
  1036  	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
  1037  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
  1038  
  1039  	finder2.Shutdown()
  1040  }
  1041  
  1042  func TestDirectoriesDeleted(t *testing.T) {
  1043  	// setup filesystem
  1044  	filesystem := newFs()
  1045  	create(t, "/tmp/findme.txt", filesystem)
  1046  	create(t, "/tmp/a/findme.txt", filesystem)
  1047  	create(t, "/tmp/a/1/findme.txt", filesystem)
  1048  	create(t, "/tmp/a/1/2/findme.txt", filesystem)
  1049  	create(t, "/tmp/b/findme.txt", filesystem)
  1050  
  1051  	// run the first finder
  1052  	finder := newFinder(
  1053  		t,
  1054  		filesystem,
  1055  		CacheParams{
  1056  			RootDirs:     []string{"/tmp"},
  1057  			IncludeFiles: []string{"findme.txt"},
  1058  		},
  1059  	)
  1060  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1061  	finder.Shutdown()
  1062  	// check the response of the first finder
  1063  	assertSameResponse(t, foundPaths,
  1064  		[]string{"/tmp/findme.txt",
  1065  			"/tmp/a/findme.txt",
  1066  			"/tmp/a/1/findme.txt",
  1067  			"/tmp/a/1/2/findme.txt",
  1068  			"/tmp/b/findme.txt"})
  1069  
  1070  	// modify the filesystem
  1071  	filesystem.Clock.Tick()
  1072  	removeAll(t, "/tmp/a/1", filesystem)
  1073  	filesystem.ClearMetrics()
  1074  
  1075  	// run the second finder
  1076  	finder2 := finderWithSameParams(t, finder)
  1077  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
  1078  
  1079  	// check results
  1080  	assertSameResponse(t, foundPaths,
  1081  		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"})
  1082  	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
  1083  	// if the Finder detects the nonexistence of /tmp/a/1
  1084  	// However, when resuming from cache, we don't want the Finder to necessarily wait
  1085  	// to stat a directory until after statting its parent.
  1086  	// So here we just include /tmp/a/1/2 in the list.
  1087  	// The Finder is currently implemented to always restat every dir and
  1088  	// to not short-circuit due to nonexistence of parents (but it will remove
  1089  	// missing dirs from the cache for next time)
  1090  	assertSameStatCalls(t, filesystem.StatCalls,
  1091  		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"})
  1092  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
  1093  
  1094  	finder2.Shutdown()
  1095  }
  1096  
  1097  func TestDirectoriesMoved(t *testing.T) {
  1098  	// setup filesystem
  1099  	filesystem := newFs()
  1100  	create(t, "/tmp/findme.txt", filesystem)
  1101  	create(t, "/tmp/a/findme.txt", filesystem)
  1102  	create(t, "/tmp/a/1/findme.txt", filesystem)
  1103  	create(t, "/tmp/a/1/2/findme.txt", filesystem)
  1104  	create(t, "/tmp/b/findme.txt", filesystem)
  1105  
  1106  	// run the first finder
  1107  	finder := newFinder(
  1108  		t,
  1109  		filesystem,
  1110  		CacheParams{
  1111  			RootDirs:     []string{"/tmp"},
  1112  			IncludeFiles: []string{"findme.txt"},
  1113  		},
  1114  	)
  1115  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1116  	finder.Shutdown()
  1117  	// check the response of the first finder
  1118  	assertSameResponse(t, foundPaths,
  1119  		[]string{"/tmp/findme.txt",
  1120  			"/tmp/a/findme.txt",
  1121  			"/tmp/a/1/findme.txt",
  1122  			"/tmp/a/1/2/findme.txt",
  1123  			"/tmp/b/findme.txt"})
  1124  
  1125  	// modify the filesystem
  1126  	filesystem.Clock.Tick()
  1127  	move(t, "/tmp/a", "/tmp/c", filesystem)
  1128  	filesystem.ClearMetrics()
  1129  
  1130  	// run the second finder
  1131  	finder2 := finderWithSameParams(t, finder)
  1132  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
  1133  
  1134  	// check results
  1135  	assertSameResponse(t, foundPaths,
  1136  		[]string{"/tmp/findme.txt",
  1137  			"/tmp/b/findme.txt",
  1138  			"/tmp/c/findme.txt",
  1139  			"/tmp/c/1/findme.txt",
  1140  			"/tmp/c/1/2/findme.txt"})
  1141  	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
  1142  	// if the Finder detects the nonexistence of /tmp/a/1
  1143  	// However, when resuming from cache, we don't want the Finder to necessarily wait
  1144  	// to stat a directory until after statting its parent.
  1145  	// So here we just include /tmp/a/1/2 in the list.
  1146  	// The Finder is currently implemented to always restat every dir and
  1147  	// to not short-circuit due to nonexistence of parents (but it will remove
  1148  	// missing dirs from the cache for next time)
  1149  	assertSameStatCalls(t, filesystem.StatCalls,
  1150  		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
  1151  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
  1152  	finder2.Shutdown()
  1153  }
  1154  
  1155  func TestDirectoriesSwapped(t *testing.T) {
  1156  	// setup filesystem
  1157  	filesystem := newFs()
  1158  	create(t, "/tmp/findme.txt", filesystem)
  1159  	create(t, "/tmp/a/findme.txt", filesystem)
  1160  	create(t, "/tmp/a/1/findme.txt", filesystem)
  1161  	create(t, "/tmp/a/1/2/findme.txt", filesystem)
  1162  	create(t, "/tmp/b/findme.txt", filesystem)
  1163  
  1164  	// run the first finder
  1165  	finder := newFinder(
  1166  		t,
  1167  		filesystem,
  1168  		CacheParams{
  1169  			RootDirs:     []string{"/tmp"},
  1170  			IncludeFiles: []string{"findme.txt"},
  1171  		},
  1172  	)
  1173  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1174  	finder.Shutdown()
  1175  	// check the response of the first finder
  1176  	assertSameResponse(t, foundPaths,
  1177  		[]string{"/tmp/findme.txt",
  1178  			"/tmp/a/findme.txt",
  1179  			"/tmp/a/1/findme.txt",
  1180  			"/tmp/a/1/2/findme.txt",
  1181  			"/tmp/b/findme.txt"})
  1182  
  1183  	// modify the filesystem
  1184  	filesystem.Clock.Tick()
  1185  	move(t, "/tmp/a", "/tmp/temp", filesystem)
  1186  	move(t, "/tmp/b", "/tmp/a", filesystem)
  1187  	move(t, "/tmp/temp", "/tmp/b", filesystem)
  1188  	filesystem.ClearMetrics()
  1189  
  1190  	// run the second finder
  1191  	finder2 := finderWithSameParams(t, finder)
  1192  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
  1193  
  1194  	// check results
  1195  	assertSameResponse(t, foundPaths,
  1196  		[]string{"/tmp/findme.txt",
  1197  			"/tmp/a/findme.txt",
  1198  			"/tmp/b/findme.txt",
  1199  			"/tmp/b/1/findme.txt",
  1200  			"/tmp/b/1/2/findme.txt"})
  1201  	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
  1202  	// if the Finder detects the nonexistence of /tmp/a/1
  1203  	// However, when resuming from cache, we don't want the Finder to necessarily wait
  1204  	// to stat a directory until after statting its parent.
  1205  	// So here we just include /tmp/a/1/2 in the list.
  1206  	// The Finder is currently implemented to always restat every dir and
  1207  	// to not short-circuit due to nonexistence of parents (but it will remove
  1208  	// missing dirs from the cache for next time)
  1209  	assertSameStatCalls(t, filesystem.StatCalls,
  1210  		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
  1211  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
  1212  	finder2.Shutdown()
  1213  }
  1214  
  1215  // runFsReplacementTest tests a change modifying properties of the filesystem itself:
  1216  // runFsReplacementTest tests changing the user, the hostname, or the device number
  1217  // runFsReplacementTest is a helper method called by other tests
  1218  func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) {
  1219  	// setup fs1
  1220  	create(t, "/tmp/findme.txt", fs1)
  1221  	create(t, "/tmp/a/findme.txt", fs1)
  1222  	create(t, "/tmp/a/a/findme.txt", fs1)
  1223  
  1224  	// setup fs2 to have the same directories but different files
  1225  	create(t, "/tmp/findme.txt", fs2)
  1226  	create(t, "/tmp/a/findme.txt", fs2)
  1227  	create(t, "/tmp/a/a/ignoreme.txt", fs2)
  1228  	create(t, "/tmp/a/b/findme.txt", fs2)
  1229  
  1230  	// run the first finder
  1231  	finder := newFinder(
  1232  		t,
  1233  		fs1,
  1234  		CacheParams{
  1235  			RootDirs:     []string{"/tmp"},
  1236  			IncludeFiles: []string{"findme.txt"},
  1237  		},
  1238  	)
  1239  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1240  	finder.Shutdown()
  1241  	// check the response of the first finder
  1242  	assertSameResponse(t, foundPaths,
  1243  		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"})
  1244  
  1245  	// copy the cache data from the first filesystem to the second
  1246  	cacheContent := read(t, finder.DbPath, fs1)
  1247  	write(t, finder.DbPath, cacheContent, fs2)
  1248  
  1249  	// run the second finder, with the same config and same cache contents but a different filesystem
  1250  	finder2 := newFinder(
  1251  		t,
  1252  		fs2,
  1253  		CacheParams{
  1254  			RootDirs:     []string{"/tmp"},
  1255  			IncludeFiles: []string{"findme.txt"},
  1256  		},
  1257  	)
  1258  	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
  1259  
  1260  	// check results
  1261  	assertSameResponse(t, foundPaths,
  1262  		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"})
  1263  	assertSameStatCalls(t, fs2.StatCalls,
  1264  		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
  1265  	assertSameReadDirCalls(t, fs2.ReadDirCalls,
  1266  		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
  1267  	finder2.Shutdown()
  1268  }
  1269  
  1270  func TestChangeOfDevice(t *testing.T) {
  1271  	fs1 := newFs()
  1272  	// not as fine-grained mounting controls as a real filesystem, but should be adequate
  1273  	fs1.SetDeviceNumber(0)
  1274  
  1275  	fs2 := newFs()
  1276  	fs2.SetDeviceNumber(1)
  1277  
  1278  	runFsReplacementTest(t, fs1, fs2)
  1279  }
  1280  
  1281  func TestChangeOfUserOrHost(t *testing.T) {
  1282  	fs1 := newFs()
  1283  	fs1.SetViewId("me@here")
  1284  
  1285  	fs2 := newFs()
  1286  	fs2.SetViewId("you@there")
  1287  
  1288  	runFsReplacementTest(t, fs1, fs2)
  1289  }
  1290  
  1291  func TestConsistentCacheOrdering(t *testing.T) {
  1292  	// setup filesystem
  1293  	filesystem := newFs()
  1294  	for i := 0; i < 5; i++ {
  1295  		create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
  1296  	}
  1297  
  1298  	// run the first finder
  1299  	finder := newFinder(
  1300  		t,
  1301  		filesystem,
  1302  		CacheParams{
  1303  			RootDirs:     []string{"/tmp"},
  1304  			IncludeFiles: []string{"findme.txt"},
  1305  		},
  1306  	)
  1307  	finder.FindNamedAt("/tmp", "findme.txt")
  1308  	finder.Shutdown()
  1309  
  1310  	// read db file
  1311  	string1 := read(t, finder.DbPath, filesystem)
  1312  
  1313  	err := filesystem.Remove(finder.DbPath)
  1314  	if err != nil {
  1315  		t.Fatal(err)
  1316  	}
  1317  
  1318  	// run another finder
  1319  	finder2 := finderWithSameParams(t, finder)
  1320  	finder2.FindNamedAt("/tmp", "findme.txt")
  1321  	finder2.Shutdown()
  1322  
  1323  	string2 := read(t, finder.DbPath, filesystem)
  1324  
  1325  	if string1 != string2 {
  1326  		t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+
  1327  			"Content of first file:\n"+
  1328  			"\n"+
  1329  			"%v"+
  1330  			"\n"+
  1331  			"\n"+
  1332  			"Content of second file:\n"+
  1333  			"\n"+
  1334  			"%v\n"+
  1335  			"\n",
  1336  			string1,
  1337  			string2,
  1338  		)
  1339  	}
  1340  
  1341  }
  1342  
  1343  func TestNumSyscallsOfSecondFind(t *testing.T) {
  1344  	// setup filesystem
  1345  	filesystem := newFs()
  1346  	create(t, "/tmp/findme.txt", filesystem)
  1347  	create(t, "/tmp/a/findme.txt", filesystem)
  1348  	create(t, "/tmp/a/misc.txt", filesystem)
  1349  
  1350  	// set up the finder and run it once
  1351  	finder := newFinder(
  1352  		t,
  1353  		filesystem,
  1354  		CacheParams{
  1355  			RootDirs:     []string{"/tmp"},
  1356  			IncludeFiles: []string{"findme.txt"},
  1357  		},
  1358  	)
  1359  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1360  	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
  1361  
  1362  	filesystem.ClearMetrics()
  1363  
  1364  	// run the finder again and confirm it doesn't check the filesystem
  1365  	refoundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1366  	assertSameResponse(t, refoundPaths, foundPaths)
  1367  	assertSameStatCalls(t, filesystem.StatCalls, []string{})
  1368  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
  1369  
  1370  	finder.Shutdown()
  1371  }
  1372  
  1373  func TestChangingParamsOfSecondFind(t *testing.T) {
  1374  	// setup filesystem
  1375  	filesystem := newFs()
  1376  	create(t, "/tmp/findme.txt", filesystem)
  1377  	create(t, "/tmp/a/findme.txt", filesystem)
  1378  	create(t, "/tmp/a/metoo.txt", filesystem)
  1379  
  1380  	// set up the finder and run it once
  1381  	finder := newFinder(
  1382  		t,
  1383  		filesystem,
  1384  		CacheParams{
  1385  			RootDirs:     []string{"/tmp"},
  1386  			IncludeFiles: []string{"findme.txt", "metoo.txt"},
  1387  		},
  1388  	)
  1389  	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
  1390  	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
  1391  
  1392  	filesystem.ClearMetrics()
  1393  
  1394  	// run the finder again and confirm it gets the right answer without asking the filesystem
  1395  	refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt")
  1396  	assertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
  1397  	assertSameStatCalls(t, filesystem.StatCalls, []string{})
  1398  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
  1399  
  1400  	finder.Shutdown()
  1401  }
  1402  
  1403  func TestSymlinkPointingToFile(t *testing.T) {
  1404  	// setup filesystem
  1405  	filesystem := newFs()
  1406  	create(t, "/tmp/a/hi.txt", filesystem)
  1407  	create(t, "/tmp/a/ignoreme.txt", filesystem)
  1408  	link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
  1409  	link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
  1410  	link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
  1411  	link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
  1412  	link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
  1413  	link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
  1414  	link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
  1415  
  1416  	// set up the finder and run it once
  1417  	finder := newFinder(
  1418  		t,
  1419  		filesystem,
  1420  		CacheParams{
  1421  			RootDirs:     []string{"/tmp"},
  1422  			IncludeFiles: []string{"hi.txt"},
  1423  		},
  1424  	)
  1425  	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
  1426  	// should search based on the name of the link rather than the destination or validity of the link
  1427  	correctResponse := []string{
  1428  		"/tmp/a/hi.txt",
  1429  		"/tmp/hi.txt",
  1430  		"/tmp/b/hi.txt",
  1431  		"/tmp/c/hi.txt",
  1432  		"/tmp/d/hi.txt",
  1433  		"/tmp/f/hi.txt",
  1434  	}
  1435  	assertSameResponse(t, foundPaths, correctResponse)
  1436  
  1437  }
  1438  
  1439  func TestSymlinkPointingToDirectory(t *testing.T) {
  1440  	// setup filesystem
  1441  	filesystem := newFs()
  1442  	create(t, "/tmp/dir/hi.txt", filesystem)
  1443  	create(t, "/tmp/dir/ignoreme.txt", filesystem)
  1444  
  1445  	link(t, "/tmp/links/dir", "../dir", filesystem)
  1446  	link(t, "/tmp/links/link", "../dir", filesystem)
  1447  	link(t, "/tmp/links/broken", "nothingHere", filesystem)
  1448  	link(t, "/tmp/links/recursive", "recursive", filesystem)
  1449  
  1450  	// set up the finder and run it once
  1451  	finder := newFinder(
  1452  		t,
  1453  		filesystem,
  1454  		CacheParams{
  1455  			RootDirs:     []string{"/tmp"},
  1456  			IncludeFiles: []string{"hi.txt"},
  1457  		},
  1458  	)
  1459  
  1460  	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
  1461  
  1462  	// should completely ignore symlinks that point to directories
  1463  	correctResponse := []string{
  1464  		"/tmp/dir/hi.txt",
  1465  	}
  1466  	assertSameResponse(t, foundPaths, correctResponse)
  1467  
  1468  }
  1469  
  1470  // TestAddPruneFile confirms that adding a prune-file (into a directory for which we
  1471  // already had a cache) causes the directory to be ignored
  1472  func TestAddPruneFile(t *testing.T) {
  1473  	// setup filesystem
  1474  	filesystem := newFs()
  1475  	create(t, "/tmp/out/hi.txt", filesystem)
  1476  	create(t, "/tmp/out/a/hi.txt", filesystem)
  1477  	create(t, "/tmp/hi.txt", filesystem)
  1478  
  1479  	// do find
  1480  	finder := newFinder(
  1481  		t,
  1482  		filesystem,
  1483  		CacheParams{
  1484  			RootDirs:     []string{"/tmp"},
  1485  			PruneFiles:   []string{".ignore-out-dir"},
  1486  			IncludeFiles: []string{"hi.txt"},
  1487  		},
  1488  	)
  1489  
  1490  	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
  1491  
  1492  	// check result
  1493  	assertSameResponse(t, foundPaths,
  1494  		[]string{"/tmp/hi.txt",
  1495  			"/tmp/out/hi.txt",
  1496  			"/tmp/out/a/hi.txt"},
  1497  	)
  1498  	finder.Shutdown()
  1499  
  1500  	// modify filesystem
  1501  	filesystem.Clock.Tick()
  1502  	create(t, "/tmp/out/.ignore-out-dir", filesystem)
  1503  	// run another find and check its result
  1504  	finder2 := finderWithSameParams(t, finder)
  1505  	foundPaths = finder2.FindNamedAt("/tmp", "hi.txt")
  1506  	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
  1507  	finder2.Shutdown()
  1508  }
  1509  
  1510  func TestUpdatingDbIffChanged(t *testing.T) {
  1511  	// setup filesystem
  1512  	filesystem := newFs()
  1513  	create(t, "/tmp/a/hi.txt", filesystem)
  1514  	create(t, "/tmp/b/bye.txt", filesystem)
  1515  
  1516  	// run the first finder
  1517  	finder := newFinder(
  1518  		t,
  1519  		filesystem,
  1520  		CacheParams{
  1521  			RootDirs:     []string{"/tmp"},
  1522  			IncludeFiles: []string{"hi.txt"},
  1523  		},
  1524  	)
  1525  	foundPaths := finder.FindAll()
  1526  	filesystem.Clock.Tick()
  1527  	finder.Shutdown()
  1528  	// check results
  1529  	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
  1530  
  1531  	// modify the filesystem
  1532  	filesystem.Clock.Tick()
  1533  	create(t, "/tmp/b/hi.txt", filesystem)
  1534  	filesystem.Clock.Tick()
  1535  	filesystem.ClearMetrics()
  1536  
  1537  	// run the second finder
  1538  	finder2 := finderWithSameParams(t, finder)
  1539  	foundPaths = finder2.FindAll()
  1540  	finder2.Shutdown()
  1541  	// check results
  1542  	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
  1543  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
  1544  	expectedDbWriteTime := filesystem.Clock.Time()
  1545  	actualDbWriteTime := modTime(t, finder2.DbPath, filesystem)
  1546  	if actualDbWriteTime != expectedDbWriteTime {
  1547  		t.Fatalf("Expected to write db at %v, actually wrote db at %v\n",
  1548  			expectedDbWriteTime, actualDbWriteTime)
  1549  	}
  1550  
  1551  	// reset metrics
  1552  	filesystem.ClearMetrics()
  1553  
  1554  	// run the third finder
  1555  	finder3 := finderWithSameParams(t, finder2)
  1556  	foundPaths = finder3.FindAll()
  1557  
  1558  	// check results
  1559  	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
  1560  	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
  1561  	finder3.Shutdown()
  1562  	actualDbWriteTime = modTime(t, finder3.DbPath, filesystem)
  1563  	if actualDbWriteTime != expectedDbWriteTime {
  1564  		t.Fatalf("Re-wrote db even when contents did not change")
  1565  	}
  1566  
  1567  }
  1568  
  1569  func TestDirectoryNotPermitted(t *testing.T) {
  1570  	// setup filesystem
  1571  	filesystem := newFs()
  1572  	create(t, "/tmp/hi.txt", filesystem)
  1573  	create(t, "/tmp/a/hi.txt", filesystem)
  1574  	create(t, "/tmp/a/a/hi.txt", filesystem)
  1575  	create(t, "/tmp/b/hi.txt", filesystem)
  1576  
  1577  	// run the first finder
  1578  	finder := newFinder(
  1579  		t,
  1580  		filesystem,
  1581  		CacheParams{
  1582  			RootDirs:     []string{"/tmp"},
  1583  			IncludeFiles: []string{"hi.txt"},
  1584  		},
  1585  	)
  1586  	foundPaths := finder.FindAll()
  1587  	filesystem.Clock.Tick()
  1588  	finder.Shutdown()
  1589  	allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"}
  1590  	// check results
  1591  	assertSameResponse(t, foundPaths, allPaths)
  1592  
  1593  	// modify the filesystem
  1594  	filesystem.Clock.Tick()
  1595  
  1596  	setReadable(t, "/tmp/a", false, filesystem)
  1597  	filesystem.Clock.Tick()
  1598  
  1599  	// run the second finder
  1600  	finder2 := finderWithSameParams(t, finder)
  1601  	foundPaths = finder2.FindAll()
  1602  	finder2.Shutdown()
  1603  	// check results
  1604  	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
  1605  
  1606  	// modify the filesystem back
  1607  	setReadable(t, "/tmp/a", true, filesystem)
  1608  
  1609  	// run the third finder
  1610  	finder3 := finderWithSameParams(t, finder2)
  1611  	foundPaths = finder3.FindAll()
  1612  	finder3.Shutdown()
  1613  	// check results
  1614  	assertSameResponse(t, foundPaths, allPaths)
  1615  }
  1616  
  1617  func TestFileNotPermitted(t *testing.T) {
  1618  	// setup filesystem
  1619  	filesystem := newFs()
  1620  	create(t, "/tmp/hi.txt", filesystem)
  1621  	setReadable(t, "/tmp/hi.txt", false, filesystem)
  1622  
  1623  	// run the first finder
  1624  	finder := newFinder(
  1625  		t,
  1626  		filesystem,
  1627  		CacheParams{
  1628  			RootDirs:     []string{"/tmp"},
  1629  			IncludeFiles: []string{"hi.txt"},
  1630  		},
  1631  	)
  1632  	foundPaths := finder.FindAll()
  1633  	filesystem.Clock.Tick()
  1634  	finder.Shutdown()
  1635  	// check results
  1636  	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
  1637  }
  1638  
  1639  func TestCacheEntryPathUnexpectedError(t *testing.T) {
  1640  	// setup filesystem
  1641  	filesystem := newFs()
  1642  	create(t, "/tmp/a/hi.txt", filesystem)
  1643  
  1644  	// run the first finder
  1645  	finder := newFinder(
  1646  		t,
  1647  		filesystem,
  1648  		CacheParams{
  1649  			RootDirs:     []string{"/tmp"},
  1650  			IncludeFiles: []string{"hi.txt"},
  1651  		},
  1652  	)
  1653  	foundPaths := finder.FindAll()
  1654  	filesystem.Clock.Tick()
  1655  	finder.Shutdown()
  1656  	// check results
  1657  	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
  1658  
  1659  	// make the directory not readable
  1660  	setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
  1661  
  1662  	// run the second finder
  1663  	_, err := finderAndErrorWithSameParams(t, finder)
  1664  	if err == nil {
  1665  		fatal(t, "Failed to detect unexpected filesystem error")
  1666  	}
  1667  }