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