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