• 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	"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	filesystem.Clock.Tick()
817	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
818	finder.Shutdown()
819	// check the response of the first finder
820	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
821
822	// modify the filesystem
823	filesystem.Clock.Tick()
824	fs.Create(t, "/tmp/b/c/findme.txt", filesystem)
825	filesystem.Clock.Tick()
826	filesystem.ClearMetrics()
827
828	// run the second finder
829	finder2 := finderWithSameParams(t, finder)
830	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
831
832	// check results
833	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
834	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
835	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
836	finder2.Shutdown()
837
838}
839
840func TestDirectoriesAdded(t *testing.T) {
841	// setup filesystem
842	filesystem := newFs()
843	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
844	fs.Create(t, "/tmp/a/findme.txt", filesystem)
845	fs.Create(t, "/tmp/b/ignore.txt", filesystem)
846	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
847	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
848
849	// run the first finder
850	finder := newFinder(
851		t,
852		filesystem,
853		CacheParams{
854			RootDirs:     []string{"/tmp"},
855			IncludeFiles: []string{"findme.txt"},
856		},
857	)
858	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
859	finder.Shutdown()
860	// check the response of the first finder
861	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
862
863	// modify the filesystem
864	filesystem.Clock.Tick()
865	fs.Create(t, "/tmp/b/c/new/findme.txt", filesystem)
866	fs.Create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
867	fs.Create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
868	filesystem.ClearMetrics()
869
870	// run the second finder
871	finder2 := finderWithSameParams(t, finder)
872	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
873
874	// check results
875	fs.AssertSameResponse(t, foundPaths,
876		[]string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"})
877	fs.AssertSameStatCalls(t, filesystem.StatCalls,
878		[]string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
879	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
880
881	finder2.Shutdown()
882}
883
884func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) {
885	// setup filesystem
886	filesystem := newFs()
887	fs.Create(t, "/tmp/hi1.txt", filesystem)
888	fs.Create(t, "/tmp/a/hi1.txt", filesystem)
889
890	// run the first finder
891	finder := newFinder(
892		t,
893		filesystem,
894		CacheParams{
895			RootDirs:     []string{"/tmp"},
896			IncludeFiles: []string{"hi1.txt", "hi2.txt"},
897		},
898	)
899	foundPaths := finder.FindNamedAt("/tmp", "hi1.txt")
900	finder.Shutdown()
901	// check the response of the first finder
902	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
903
904	// modify the filesystem
905	filesystem.Clock.Tick()
906	fs.Create(t, "/tmp/hi2.txt", filesystem)
907	fs.Create(t, "/tmp/a/hi2.txt", filesystem)
908	filesystem.ClearMetrics()
909
910	// run the second finder
911	finder2 := finderWithSameParams(t, finder)
912	foundPaths = finder2.FindAll()
913
914	// check results
915	fs.AssertSameResponse(t, foundPaths,
916		[]string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"})
917	fs.AssertSameStatCalls(t, filesystem.StatCalls,
918		[]string{"/tmp", "/tmp/a"})
919	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
920
921	finder2.Shutdown()
922}
923
924func TestFileDeleted(t *testing.T) {
925	// setup filesystem
926	filesystem := newFs()
927	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
928	fs.Create(t, "/tmp/a/findme.txt", filesystem)
929	fs.Create(t, "/tmp/b/findme.txt", filesystem)
930	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
931	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
932
933	// run the first finder
934	finder := newFinder(
935		t,
936		filesystem,
937		CacheParams{
938			RootDirs:     []string{"/tmp"},
939			IncludeFiles: []string{"findme.txt"},
940		},
941	)
942	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
943	finder.Shutdown()
944	// check the response of the first finder
945	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
946
947	// modify the filesystem
948	filesystem.Clock.Tick()
949	fs.Delete(t, "/tmp/b/findme.txt", filesystem)
950	filesystem.ClearMetrics()
951
952	// run the second finder
953	finder2 := finderWithSameParams(t, finder)
954	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
955
956	// check results
957	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
958	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
959	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
960
961	finder2.Shutdown()
962}
963
964func TestDirectoriesDeleted(t *testing.T) {
965	// setup filesystem
966	filesystem := newFs()
967	fs.Create(t, "/tmp/findme.txt", filesystem)
968	fs.Create(t, "/tmp/a/findme.txt", filesystem)
969	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
970	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
971	fs.Create(t, "/tmp/b/findme.txt", filesystem)
972
973	// run the first finder
974	finder := newFinder(
975		t,
976		filesystem,
977		CacheParams{
978			RootDirs:     []string{"/tmp"},
979			IncludeFiles: []string{"findme.txt"},
980		},
981	)
982	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
983	finder.Shutdown()
984	// check the response of the first finder
985	fs.AssertSameResponse(t, foundPaths,
986		[]string{"/tmp/findme.txt",
987			"/tmp/a/findme.txt",
988			"/tmp/a/1/findme.txt",
989			"/tmp/a/1/2/findme.txt",
990			"/tmp/b/findme.txt"})
991
992	// modify the filesystem
993	filesystem.Clock.Tick()
994	fs.RemoveAll(t, "/tmp/a/1", filesystem)
995	filesystem.ClearMetrics()
996
997	// run the second finder
998	finder2 := finderWithSameParams(t, finder)
999	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1000
1001	// check results
1002	fs.AssertSameResponse(t, foundPaths,
1003		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"})
1004	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1005	// if the Finder detects the nonexistence of /tmp/a/1
1006	// However, when resuming from cache, we don't want the Finder to necessarily wait
1007	// to stat a directory until after statting its parent.
1008	// So here we just include /tmp/a/1/2 in the list.
1009	// The Finder is currently implemented to always restat every dir and
1010	// to not short-circuit due to nonexistence of parents (but it will remove
1011	// missing dirs from the cache for next time)
1012	fs.AssertSameStatCalls(t, filesystem.StatCalls,
1013		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"})
1014	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
1015
1016	finder2.Shutdown()
1017}
1018
1019func TestDirectoriesMoved(t *testing.T) {
1020	// setup filesystem
1021	filesystem := newFs()
1022	fs.Create(t, "/tmp/findme.txt", filesystem)
1023	fs.Create(t, "/tmp/a/findme.txt", filesystem)
1024	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
1025	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
1026	fs.Create(t, "/tmp/b/findme.txt", filesystem)
1027
1028	// run the first finder
1029	finder := newFinder(
1030		t,
1031		filesystem,
1032		CacheParams{
1033			RootDirs:     []string{"/tmp"},
1034			IncludeFiles: []string{"findme.txt"},
1035		},
1036	)
1037	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1038	finder.Shutdown()
1039	// check the response of the first finder
1040	fs.AssertSameResponse(t, foundPaths,
1041		[]string{"/tmp/findme.txt",
1042			"/tmp/a/findme.txt",
1043			"/tmp/a/1/findme.txt",
1044			"/tmp/a/1/2/findme.txt",
1045			"/tmp/b/findme.txt"})
1046
1047	// modify the filesystem
1048	filesystem.Clock.Tick()
1049	fs.Move(t, "/tmp/a", "/tmp/c", filesystem)
1050	filesystem.ClearMetrics()
1051
1052	// run the second finder
1053	finder2 := finderWithSameParams(t, finder)
1054	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1055
1056	// check results
1057	fs.AssertSameResponse(t, foundPaths,
1058		[]string{"/tmp/findme.txt",
1059			"/tmp/b/findme.txt",
1060			"/tmp/c/findme.txt",
1061			"/tmp/c/1/findme.txt",
1062			"/tmp/c/1/2/findme.txt"})
1063	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1064	// if the Finder detects the nonexistence of /tmp/a/1
1065	// However, when resuming from cache, we don't want the Finder to necessarily wait
1066	// to stat a directory until after statting its parent.
1067	// So here we just include /tmp/a/1/2 in the list.
1068	// The Finder is currently implemented to always restat every dir and
1069	// to not short-circuit due to nonexistence of parents (but it will remove
1070	// missing dirs from the cache for next time)
1071	fs.AssertSameStatCalls(t, filesystem.StatCalls,
1072		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
1073	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
1074	finder2.Shutdown()
1075}
1076
1077func TestDirectoriesSwapped(t *testing.T) {
1078	// setup filesystem
1079	filesystem := newFs()
1080	fs.Create(t, "/tmp/findme.txt", filesystem)
1081	fs.Create(t, "/tmp/a/findme.txt", filesystem)
1082	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
1083	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
1084	fs.Create(t, "/tmp/b/findme.txt", filesystem)
1085
1086	// run the first finder
1087	finder := newFinder(
1088		t,
1089		filesystem,
1090		CacheParams{
1091			RootDirs:     []string{"/tmp"},
1092			IncludeFiles: []string{"findme.txt"},
1093		},
1094	)
1095	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1096	finder.Shutdown()
1097	// check the response of the first finder
1098	fs.AssertSameResponse(t, foundPaths,
1099		[]string{"/tmp/findme.txt",
1100			"/tmp/a/findme.txt",
1101			"/tmp/a/1/findme.txt",
1102			"/tmp/a/1/2/findme.txt",
1103			"/tmp/b/findme.txt"})
1104
1105	// modify the filesystem
1106	filesystem.Clock.Tick()
1107	fs.Move(t, "/tmp/a", "/tmp/temp", filesystem)
1108	fs.Move(t, "/tmp/b", "/tmp/a", filesystem)
1109	fs.Move(t, "/tmp/temp", "/tmp/b", filesystem)
1110	filesystem.ClearMetrics()
1111
1112	// run the second finder
1113	finder2 := finderWithSameParams(t, finder)
1114	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1115
1116	// check results
1117	fs.AssertSameResponse(t, foundPaths,
1118		[]string{"/tmp/findme.txt",
1119			"/tmp/a/findme.txt",
1120			"/tmp/b/findme.txt",
1121			"/tmp/b/1/findme.txt",
1122			"/tmp/b/1/2/findme.txt"})
1123	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
1124	// if the Finder detects the nonexistence of /tmp/a/1
1125	// However, when resuming from cache, we don't want the Finder to necessarily wait
1126	// to stat a directory until after statting its parent.
1127	// So here we just include /tmp/a/1/2 in the list.
1128	// The Finder is currently implemented to always restat every dir and
1129	// to not short-circuit due to nonexistence of parents (but it will remove
1130	// missing dirs from the cache for next time)
1131	fs.AssertSameStatCalls(t, filesystem.StatCalls,
1132		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
1133	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
1134	finder2.Shutdown()
1135}
1136
1137// runFsReplacementTest tests a change modifying properties of the filesystem itself:
1138// runFsReplacementTest tests changing the user, the hostname, or the device number
1139// runFsReplacementTest is a helper method called by other tests
1140func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) {
1141	// setup fs1
1142	fs.Create(t, "/tmp/findme.txt", fs1)
1143	fs.Create(t, "/tmp/a/findme.txt", fs1)
1144	fs.Create(t, "/tmp/a/a/findme.txt", fs1)
1145
1146	// setup fs2 to have the same directories but different files
1147	fs.Create(t, "/tmp/findme.txt", fs2)
1148	fs.Create(t, "/tmp/a/findme.txt", fs2)
1149	fs.Create(t, "/tmp/a/a/ignoreme.txt", fs2)
1150	fs.Create(t, "/tmp/a/b/findme.txt", fs2)
1151
1152	// run the first finder
1153	finder := newFinder(
1154		t,
1155		fs1,
1156		CacheParams{
1157			RootDirs:     []string{"/tmp"},
1158			IncludeFiles: []string{"findme.txt"},
1159		},
1160	)
1161	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1162	finder.Shutdown()
1163	// check the response of the first finder
1164	fs.AssertSameResponse(t, foundPaths,
1165		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"})
1166
1167	// copy the cache data from the first filesystem to the second
1168	cacheContent := fs.Read(t, finder.DbPath, fs1)
1169	fs.Write(t, finder.DbPath, cacheContent, fs2)
1170
1171	// run the second finder, with the same config and same cache contents but a different filesystem
1172	finder2 := newFinder(
1173		t,
1174		fs2,
1175		CacheParams{
1176			RootDirs:     []string{"/tmp"},
1177			IncludeFiles: []string{"findme.txt"},
1178		},
1179	)
1180	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
1181
1182	// check results
1183	fs.AssertSameResponse(t, foundPaths,
1184		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"})
1185	fs.AssertSameStatCalls(t, fs2.StatCalls,
1186		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
1187	fs.AssertSameReadDirCalls(t, fs2.ReadDirCalls,
1188		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
1189	finder2.Shutdown()
1190}
1191
1192func TestChangeOfDevice(t *testing.T) {
1193	fs1 := newFs()
1194	// not as fine-grained mounting controls as a real filesystem, but should be adequate
1195	fs1.SetDeviceNumber(0)
1196
1197	fs2 := newFs()
1198	fs2.SetDeviceNumber(1)
1199
1200	runFsReplacementTest(t, fs1, fs2)
1201}
1202
1203func TestChangeOfUserOrHost(t *testing.T) {
1204	fs1 := newFs()
1205	fs1.SetViewId("me@here")
1206
1207	fs2 := newFs()
1208	fs2.SetViewId("you@there")
1209
1210	runFsReplacementTest(t, fs1, fs2)
1211}
1212
1213func TestConsistentCacheOrdering(t *testing.T) {
1214	// setup filesystem
1215	filesystem := newFs()
1216	for i := 0; i < 5; i++ {
1217		fs.Create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
1218	}
1219
1220	// run the first finder
1221	finder := newFinder(
1222		t,
1223		filesystem,
1224		CacheParams{
1225			RootDirs:     []string{"/tmp"},
1226			IncludeFiles: []string{"findme.txt"},
1227		},
1228	)
1229	finder.FindNamedAt("/tmp", "findme.txt")
1230	finder.Shutdown()
1231
1232	// read db file
1233	string1 := fs.Read(t, finder.DbPath, filesystem)
1234
1235	err := filesystem.Remove(finder.DbPath)
1236	if err != nil {
1237		t.Fatal(err)
1238	}
1239
1240	// run another finder
1241	finder2 := finderWithSameParams(t, finder)
1242	finder2.FindNamedAt("/tmp", "findme.txt")
1243	finder2.Shutdown()
1244
1245	string2 := fs.Read(t, finder.DbPath, filesystem)
1246
1247	if string1 != string2 {
1248		t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+
1249			"Content of first file:\n"+
1250			"\n"+
1251			"%v"+
1252			"\n"+
1253			"\n"+
1254			"Content of second file:\n"+
1255			"\n"+
1256			"%v\n"+
1257			"\n",
1258			string1,
1259			string2,
1260		)
1261	}
1262
1263}
1264
1265func TestNumSyscallsOfSecondFind(t *testing.T) {
1266	// setup filesystem
1267	filesystem := newFs()
1268	fs.Create(t, "/tmp/findme.txt", filesystem)
1269	fs.Create(t, "/tmp/a/findme.txt", filesystem)
1270	fs.Create(t, "/tmp/a/misc.txt", filesystem)
1271
1272	// set up the finder and run it once
1273	finder := newFinder(
1274		t,
1275		filesystem,
1276		CacheParams{
1277			RootDirs:     []string{"/tmp"},
1278			IncludeFiles: []string{"findme.txt"},
1279		},
1280	)
1281	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1282	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
1283
1284	filesystem.ClearMetrics()
1285
1286	// run the finder again and confirm it doesn't check the filesystem
1287	refoundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1288	fs.AssertSameResponse(t, refoundPaths, foundPaths)
1289	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
1290	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1291
1292	finder.Shutdown()
1293}
1294
1295func TestChangingParamsOfSecondFind(t *testing.T) {
1296	// setup filesystem
1297	filesystem := newFs()
1298	fs.Create(t, "/tmp/findme.txt", filesystem)
1299	fs.Create(t, "/tmp/a/findme.txt", filesystem)
1300	fs.Create(t, "/tmp/a/metoo.txt", filesystem)
1301
1302	// set up the finder and run it once
1303	finder := newFinder(
1304		t,
1305		filesystem,
1306		CacheParams{
1307			RootDirs:     []string{"/tmp"},
1308			IncludeFiles: []string{"findme.txt", "metoo.txt"},
1309		},
1310	)
1311	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
1312	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
1313
1314	filesystem.ClearMetrics()
1315
1316	// run the finder again and confirm it gets the right answer without asking the filesystem
1317	refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt")
1318	fs.AssertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
1319	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
1320	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1321
1322	finder.Shutdown()
1323}
1324
1325func TestSymlinkPointingToFile(t *testing.T) {
1326	// setup filesystem
1327	filesystem := newFs()
1328	fs.Create(t, "/tmp/a/hi.txt", filesystem)
1329	fs.Create(t, "/tmp/a/ignoreme.txt", filesystem)
1330	fs.Link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
1331	fs.Link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
1332	fs.Link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
1333	fs.Link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
1334	fs.Link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
1335	fs.Link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
1336	fs.Link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
1337
1338	// set up the finder and run it once
1339	finder := newFinder(
1340		t,
1341		filesystem,
1342		CacheParams{
1343			RootDirs:     []string{"/tmp"},
1344			IncludeFiles: []string{"hi.txt"},
1345		},
1346	)
1347	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1348	// should search based on the name of the link rather than the destination or validity of the link
1349	correctResponse := []string{
1350		"/tmp/a/hi.txt",
1351		"/tmp/hi.txt",
1352		"/tmp/b/hi.txt",
1353		"/tmp/c/hi.txt",
1354		"/tmp/d/hi.txt",
1355		"/tmp/f/hi.txt",
1356	}
1357	fs.AssertSameResponse(t, foundPaths, correctResponse)
1358
1359}
1360
1361func TestSymlinkPointingToDirectory(t *testing.T) {
1362	// setup filesystem
1363	filesystem := newFs()
1364	fs.Create(t, "/tmp/dir/hi.txt", filesystem)
1365	fs.Create(t, "/tmp/dir/ignoreme.txt", filesystem)
1366
1367	fs.Link(t, "/tmp/links/dir", "../dir", filesystem)
1368	fs.Link(t, "/tmp/links/link", "../dir", filesystem)
1369	fs.Link(t, "/tmp/links/hi.txt", "../dir", filesystem)
1370	fs.Link(t, "/tmp/links/broken", "nothingHere", filesystem)
1371	fs.Link(t, "/tmp/links/recursive", "recursive", filesystem)
1372
1373	// set up the finder and run it once
1374	finder := newFinder(
1375		t,
1376		filesystem,
1377		CacheParams{
1378			RootDirs:     []string{"/tmp"},
1379			IncludeFiles: []string{"hi.txt"},
1380		},
1381	)
1382
1383	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1384
1385	// should completely ignore symlinks that point to directories
1386	correctResponse := []string{
1387		"/tmp/dir/hi.txt",
1388	}
1389	fs.AssertSameResponse(t, foundPaths, correctResponse)
1390
1391}
1392
1393// TestAddPruneFile confirms that adding a prune-file (into a directory for which we
1394// already had a cache) causes the directory to be ignored
1395func TestAddPruneFile(t *testing.T) {
1396	// setup filesystem
1397	filesystem := newFs()
1398	fs.Create(t, "/tmp/out/hi.txt", filesystem)
1399	fs.Create(t, "/tmp/out/a/hi.txt", filesystem)
1400	fs.Create(t, "/tmp/hi.txt", filesystem)
1401
1402	// do find
1403	finder := newFinder(
1404		t,
1405		filesystem,
1406		CacheParams{
1407			RootDirs:     []string{"/tmp"},
1408			PruneFiles:   []string{".ignore-out-dir"},
1409			IncludeFiles: []string{"hi.txt"},
1410		},
1411	)
1412
1413	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
1414
1415	// check result
1416	fs.AssertSameResponse(t, foundPaths,
1417		[]string{"/tmp/hi.txt",
1418			"/tmp/out/hi.txt",
1419			"/tmp/out/a/hi.txt"},
1420	)
1421	finder.Shutdown()
1422
1423	// modify filesystem
1424	filesystem.Clock.Tick()
1425	fs.Create(t, "/tmp/out/.ignore-out-dir", filesystem)
1426	// run another find and check its result
1427	finder2 := finderWithSameParams(t, finder)
1428	foundPaths = finder2.FindNamedAt("/tmp", "hi.txt")
1429	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
1430	finder2.Shutdown()
1431}
1432
1433func TestUpdatingDbIffChanged(t *testing.T) {
1434	// setup filesystem
1435	filesystem := newFs()
1436	fs.Create(t, "/tmp/a/hi.txt", filesystem)
1437	fs.Create(t, "/tmp/b/bye.txt", filesystem)
1438
1439	// run the first finder
1440	finder := newFinder(
1441		t,
1442		filesystem,
1443		CacheParams{
1444			RootDirs:     []string{"/tmp"},
1445			IncludeFiles: []string{"hi.txt"},
1446		},
1447	)
1448	filesystem.Clock.Tick()
1449	foundPaths := finder.FindAll()
1450	finder.Shutdown()
1451	// check results
1452	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
1453
1454	// modify the filesystem
1455	filesystem.Clock.Tick()
1456	fs.Create(t, "/tmp/b/hi.txt", filesystem)
1457	filesystem.Clock.Tick()
1458	filesystem.ClearMetrics()
1459
1460	// run the second finder
1461	finder2 := finderWithSameParams(t, finder)
1462	foundPaths = finder2.FindAll()
1463	finder2.Shutdown()
1464	// check results
1465	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
1466	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
1467	expectedDbWriteTime := filesystem.Clock.Time()
1468	actualDbWriteTime := fs.ModTime(t, finder2.DbPath, filesystem)
1469	if actualDbWriteTime != expectedDbWriteTime {
1470		t.Fatalf("Expected to write db at %v, actually wrote db at %v\n",
1471			expectedDbWriteTime, actualDbWriteTime)
1472	}
1473
1474	// reset metrics
1475	filesystem.ClearMetrics()
1476
1477	// run the third finder
1478	finder3 := finderWithSameParams(t, finder2)
1479	foundPaths = finder3.FindAll()
1480
1481	// check results
1482	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
1483	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
1484	finder3.Shutdown()
1485	actualDbWriteTime = fs.ModTime(t, finder3.DbPath, filesystem)
1486	if actualDbWriteTime != expectedDbWriteTime {
1487		t.Fatalf("Re-wrote db even when contents did not change")
1488	}
1489
1490}
1491
1492func TestDirectoryNotPermitted(t *testing.T) {
1493	// setup filesystem
1494	filesystem := newFs()
1495	fs.Create(t, "/tmp/hi.txt", filesystem)
1496	fs.Create(t, "/tmp/a/hi.txt", filesystem)
1497	fs.Create(t, "/tmp/a/a/hi.txt", filesystem)
1498	fs.Create(t, "/tmp/b/hi.txt", filesystem)
1499
1500	// run the first finder
1501	finder := newFinder(
1502		t,
1503		filesystem,
1504		CacheParams{
1505			RootDirs:     []string{"/tmp"},
1506			IncludeFiles: []string{"hi.txt"},
1507		},
1508	)
1509	filesystem.Clock.Tick()
1510	foundPaths := finder.FindAll()
1511	finder.Shutdown()
1512	allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"}
1513	// check results
1514	fs.AssertSameResponse(t, foundPaths, allPaths)
1515
1516	// modify the filesystem
1517	filesystem.Clock.Tick()
1518
1519	fs.SetReadable(t, "/tmp/a", false, filesystem)
1520	filesystem.Clock.Tick()
1521
1522	// run the second finder
1523	finder2 := finderWithSameParams(t, finder)
1524	foundPaths = finder2.FindAll()
1525	finder2.Shutdown()
1526	// check results
1527	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
1528
1529	// modify the filesystem back
1530	fs.SetReadable(t, "/tmp/a", true, filesystem)
1531
1532	// run the third finder
1533	finder3 := finderWithSameParams(t, finder2)
1534	foundPaths = finder3.FindAll()
1535	finder3.Shutdown()
1536	// check results
1537	fs.AssertSameResponse(t, foundPaths, allPaths)
1538}
1539
1540func TestFileNotPermitted(t *testing.T) {
1541	// setup filesystem
1542	filesystem := newFs()
1543	fs.Create(t, "/tmp/hi.txt", filesystem)
1544	fs.SetReadable(t, "/tmp/hi.txt", false, filesystem)
1545
1546	// run the first finder
1547	finder := newFinder(
1548		t,
1549		filesystem,
1550		CacheParams{
1551			RootDirs:     []string{"/tmp"},
1552			IncludeFiles: []string{"hi.txt"},
1553		},
1554	)
1555	filesystem.Clock.Tick()
1556	foundPaths := finder.FindAll()
1557	finder.Shutdown()
1558	// check results
1559	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
1560}
1561
1562func TestCacheEntryPathUnexpectedError(t *testing.T) {
1563	// setup filesystem
1564	filesystem := newFs()
1565	fs.Create(t, "/tmp/a/hi.txt", filesystem)
1566
1567	// run the first finder
1568	finder := newFinder(
1569		t,
1570		filesystem,
1571		CacheParams{
1572			RootDirs:     []string{"/tmp"},
1573			IncludeFiles: []string{"hi.txt"},
1574		},
1575	)
1576	filesystem.Clock.Tick()
1577	foundPaths := finder.FindAll()
1578	finder.Shutdown()
1579	// check results
1580	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
1581
1582	// make the directory not readable
1583	fs.SetReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
1584
1585	// run the second finder
1586	_, err := finderAndErrorWithSameParams(t, finder)
1587	if err == nil {
1588		t.Fatal("Failed to detect unexpected filesystem error")
1589	}
1590}
1591