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