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