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