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