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