fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

1// Copyright 2021 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 gitindex 16 17import ( 18 "bytes" 19 "context" 20 "fmt" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "sort" 25 "testing" 26 27 "github.com/go-git/go-git/v5" 28 "github.com/go-git/go-git/v5/plumbing" 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "github.com/sourcegraph/zoekt" 32 "github.com/sourcegraph/zoekt/build" 33 "github.com/sourcegraph/zoekt/ignore" 34 "github.com/sourcegraph/zoekt/query" 35 "github.com/sourcegraph/zoekt/shards" 36) 37 38func TestIndexEmptyRepo(t *testing.T) { 39 dir := t.TempDir() 40 41 cmd := exec.Command("git", "init", "-b", "master", "repo") 42 cmd.Dir = dir 43 44 if err := cmd.Run(); err != nil { 45 t.Fatalf("cmd.Run: %v", err) 46 } 47 48 desc := zoekt.Repository{ 49 Name: "repo", 50 } 51 opts := Options{ 52 RepoDir: filepath.Join(dir, "repo", ".git"), 53 BuildOptions: build.Options{ 54 RepositoryDescription: desc, 55 IndexDir: dir, 56 }, 57 } 58 59 if err := IndexGitRepo(opts); err != nil { 60 t.Fatalf("IndexGitRepo: %v", err) 61 } 62} 63 64func TestIndexDeltaBasic(t *testing.T) { 65 type branchToDocumentMap map[string][]zoekt.Document 66 67 type step struct { 68 name string 69 addedDocuments branchToDocumentMap 70 deletedDocuments branchToDocumentMap 71 optFn func(t *testing.T, options *Options) 72 73 expectedFallbackToNormalBuild bool 74 expectedDocuments []zoekt.Document 75 } 76 77 helloWorld := zoekt.Document{Name: "hello_world.txt", Content: []byte("hello")} 78 79 fruitV1 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("strawberry")} 80 fruitV1InFolder := zoekt.Document{Name: "the_best/best_fruit.txt", Content: fruitV1.Content} 81 fruitV1WithNewName := zoekt.Document{Name: "new_fruit.txt", Content: fruitV1.Content} 82 83 fruitV2 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("grapes")} 84 fruitV2InFolder := zoekt.Document{Name: "the_best/best_fruit.txt", Content: fruitV2.Content} 85 86 fruitV3 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("oranges")} 87 fruitV4 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("apples")} 88 89 foo := zoekt.Document{Name: "foo.txt", Content: []byte("bar")} 90 91 emptySourcegraphIgnore := zoekt.Document{Name: ignore.IgnoreFile} 92 sourcegraphIgnoreWithContent := zoekt.Document{Name: ignore.IgnoreFile, Content: []byte("good_content.txt")} 93 94 for _, test := range []struct { 95 name string 96 branches []string 97 steps []step 98 }{ 99 { 100 name: "modification", 101 branches: []string{"main"}, 102 steps: []step{ 103 { 104 name: "setup", 105 addedDocuments: branchToDocumentMap{ 106 "main": []zoekt.Document{helloWorld, fruitV1}, 107 }, 108 109 expectedDocuments: []zoekt.Document{helloWorld, fruitV1}, 110 }, 111 { 112 name: "add newer version of fruits", 113 addedDocuments: branchToDocumentMap{ 114 "main": []zoekt.Document{fruitV2}, 115 }, 116 optFn: func(t *testing.T, o *Options) { 117 o.BuildOptions.IsDelta = true 118 }, 119 120 expectedDocuments: []zoekt.Document{helloWorld, fruitV2}, 121 }, 122 }, 123 }, 124 { 125 name: "modification only inside nested folder", 126 branches: []string{"main"}, 127 steps: []step{ 128 { 129 name: "setup", 130 addedDocuments: branchToDocumentMap{ 131 "main": []zoekt.Document{foo, fruitV1InFolder}, 132 }, 133 134 expectedDocuments: []zoekt.Document{foo, fruitV1InFolder}, 135 }, 136 { 137 name: "add newer version of fruits inside folder", 138 addedDocuments: branchToDocumentMap{ 139 "main": []zoekt.Document{fruitV2InFolder}, 140 }, 141 optFn: func(t *testing.T, o *Options) { 142 o.BuildOptions.IsDelta = true 143 }, 144 145 expectedDocuments: []zoekt.Document{foo, fruitV2InFolder}, 146 }, 147 }, 148 }, 149 { 150 name: "addition", 151 branches: []string{"main"}, 152 steps: []step{ 153 { 154 name: "setup", 155 addedDocuments: branchToDocumentMap{ 156 "main": []zoekt.Document{helloWorld, fruitV1}, 157 }, 158 159 expectedDocuments: []zoekt.Document{helloWorld, fruitV1}, 160 }, 161 { 162 name: "add new file - foo", 163 addedDocuments: branchToDocumentMap{ 164 "main": []zoekt.Document{foo}, 165 }, 166 optFn: func(t *testing.T, o *Options) { 167 o.BuildOptions.IsDelta = true 168 }, 169 170 expectedDocuments: []zoekt.Document{helloWorld, fruitV1, foo}, 171 }, 172 }, 173 }, 174 { 175 name: "deletion", 176 branches: []string{"main"}, 177 steps: []step{ 178 { 179 name: "setup", 180 addedDocuments: branchToDocumentMap{ 181 "main": []zoekt.Document{helloWorld, fruitV1, foo}, 182 }, 183 184 expectedDocuments: []zoekt.Document{helloWorld, fruitV1, foo}, 185 }, 186 { 187 name: "delete foo file", 188 addedDocuments: nil, 189 deletedDocuments: branchToDocumentMap{ 190 "main": []zoekt.Document{foo}, 191 }, 192 193 optFn: func(t *testing.T, o *Options) { 194 o.BuildOptions.IsDelta = true 195 }, 196 197 expectedDocuments: []zoekt.Document{helloWorld, fruitV1}, 198 }, 199 }, 200 }, 201 { 202 name: "addition and deletion on only one branch", 203 branches: []string{"main", "release", "dev"}, 204 steps: []step{ 205 { 206 name: "setup", 207 addedDocuments: branchToDocumentMap{ 208 "main": []zoekt.Document{fruitV1}, 209 "release": []zoekt.Document{fruitV2}, 210 "dev": []zoekt.Document{fruitV3}, 211 }, 212 213 expectedDocuments: []zoekt.Document{fruitV1, fruitV2, fruitV3}, 214 }, 215 { 216 name: "replace fruits v3 with v4 on 'dev', delete fruits on 'main'", 217 addedDocuments: branchToDocumentMap{ 218 "dev": []zoekt.Document{fruitV4}, 219 }, 220 deletedDocuments: branchToDocumentMap{ 221 "main": []zoekt.Document{fruitV1}, 222 }, 223 224 optFn: func(t *testing.T, o *Options) { 225 o.BuildOptions.IsDelta = true 226 }, 227 228 expectedDocuments: []zoekt.Document{fruitV2, fruitV4}, 229 }, 230 }, 231 }, 232 { 233 name: "rename", 234 branches: []string{"main", "release"}, 235 steps: []step{ 236 { 237 name: "setup", 238 addedDocuments: branchToDocumentMap{ 239 "main": []zoekt.Document{fruitV1}, 240 "release": []zoekt.Document{fruitV2}, 241 }, 242 expectedDocuments: []zoekt.Document{fruitV1, fruitV2}, 243 }, 244 { 245 name: "rename fruits file on 'main' + ensure that unmodified fruits file on 'release' is still searchable", 246 addedDocuments: branchToDocumentMap{ 247 "main": []zoekt.Document{fruitV1WithNewName}, 248 }, 249 deletedDocuments: branchToDocumentMap{ 250 "main": []zoekt.Document{fruitV1}, 251 }, 252 253 optFn: func(t *testing.T, o *Options) { 254 o.BuildOptions.IsDelta = true 255 }, 256 257 expectedDocuments: []zoekt.Document{fruitV1WithNewName, fruitV2}, 258 }, 259 }, 260 }, 261 { 262 name: "modification: update one branch with version of document from another branch (a.k.a. Keegan's test)", 263 branches: []string{"main", "dev"}, 264 steps: []step{ 265 { 266 name: "setup", 267 addedDocuments: branchToDocumentMap{ 268 "main": []zoekt.Document{fruitV1}, 269 "dev": []zoekt.Document{fruitV2}, 270 }, 271 expectedDocuments: []zoekt.Document{fruitV1, fruitV2}, 272 }, 273 { 274 name: "switch main to dev's older version of fruits + bump dev's fruits to new version", 275 addedDocuments: branchToDocumentMap{ 276 "main": []zoekt.Document{fruitV2}, 277 "dev": []zoekt.Document{fruitV3}, 278 }, 279 280 optFn: func(t *testing.T, o *Options) { 281 o.BuildOptions.IsDelta = true 282 }, 283 284 expectedDocuments: []zoekt.Document{fruitV2, fruitV3}, 285 }, 286 }, 287 }, 288 { 289 name: "no-op delta builds (reindexing the same commits)", 290 branches: []string{"main", "dev"}, 291 steps: []step{ 292 { 293 name: "setup", 294 addedDocuments: branchToDocumentMap{ 295 "main": []zoekt.Document{fruitV1, foo}, 296 "dev": []zoekt.Document{helloWorld}, 297 }, 298 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld}, 299 }, 300 { 301 name: "first no-op (normal build -> delta build)", 302 optFn: func(t *testing.T, o *Options) { 303 o.BuildOptions.IsDelta = true 304 }, 305 306 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld}, 307 }, 308 { 309 name: "second no-op (delta build -> delta build)", 310 optFn: func(t *testing.T, o *Options) { 311 o.BuildOptions.IsDelta = true 312 }, 313 314 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld}, 315 }, 316 }, 317 }, 318 { 319 name: "should fallback to normal build if no prior shards exist", 320 branches: []string{"main"}, 321 steps: []step{ 322 { 323 name: "attempt delta build on a repository that hasn't been indexed yet", 324 addedDocuments: branchToDocumentMap{ 325 "main": []zoekt.Document{helloWorld}, 326 }, 327 optFn: func(t *testing.T, o *Options) { 328 o.BuildOptions.IsDelta = true 329 }, 330 331 expectedFallbackToNormalBuild: true, 332 expectedDocuments: []zoekt.Document{helloWorld}, 333 }, 334 }, 335 }, 336 { 337 name: "should fallback to normal build if the set of requested repository branches changes", 338 branches: []string{"main", "release", "dev"}, 339 steps: []step{ 340 { 341 name: "setup", 342 addedDocuments: branchToDocumentMap{ 343 "main": []zoekt.Document{fruitV1}, 344 "release": []zoekt.Document{fruitV2}, 345 "dev": []zoekt.Document{fruitV3}, 346 }, 347 348 expectedDocuments: []zoekt.Document{fruitV1, fruitV2, fruitV3}, 349 }, 350 { 351 name: "try delta build after dropping 'main' branch from index ", 352 addedDocuments: branchToDocumentMap{ 353 "release": []zoekt.Document{fruitV4}, 354 }, 355 optFn: func(t *testing.T, o *Options) { 356 o.Branches = []string{"HEAD", "release", "dev"} // a bit of a hack to override it this way, but it gets the job done 357 o.BuildOptions.IsDelta = true 358 }, 359 360 expectedFallbackToNormalBuild: true, 361 expectedDocuments: []zoekt.Document{fruitV3, fruitV4}, 362 }, 363 }, 364 }, 365 { 366 name: "should fallback to normal build if one or more index options updates requires a full build", 367 branches: []string{"main"}, 368 steps: []step{ 369 { 370 name: "setup", 371 addedDocuments: branchToDocumentMap{ 372 "main": []zoekt.Document{fruitV1}, 373 }, 374 375 expectedDocuments: []zoekt.Document{fruitV1}, 376 }, 377 { 378 name: "try delta build after updating Disable CTags index option", 379 addedDocuments: branchToDocumentMap{ 380 "main": []zoekt.Document{fruitV2}, 381 }, 382 optFn: func(t *testing.T, o *Options) { 383 o.BuildOptions.IsDelta = true 384 o.BuildOptions.DisableCTags = true 385 }, 386 387 expectedFallbackToNormalBuild: true, 388 expectedDocuments: []zoekt.Document{fruitV2}, 389 }, 390 { 391 name: "try delta build after reverting Disable CTags index option", 392 addedDocuments: branchToDocumentMap{ 393 "main": []zoekt.Document{fruitV3}, 394 }, 395 optFn: func(t *testing.T, o *Options) { 396 o.BuildOptions.IsDelta = true 397 o.BuildOptions.DisableCTags = false 398 }, 399 400 expectedFallbackToNormalBuild: true, 401 expectedDocuments: []zoekt.Document{fruitV3}, 402 }, 403 }, 404 }, 405 { 406 name: "should successfully perform multiple delta builds after disabling symbols", 407 branches: []string{"main"}, 408 steps: []step{ 409 { 410 name: "setup", 411 addedDocuments: branchToDocumentMap{ 412 "main": []zoekt.Document{fruitV1}, 413 }, 414 415 expectedDocuments: []zoekt.Document{fruitV1}, 416 }, 417 { 418 name: "try delta build after updating Disable CTags index option", 419 addedDocuments: branchToDocumentMap{ 420 "main": []zoekt.Document{fruitV2}, 421 }, 422 optFn: func(t *testing.T, o *Options) { 423 o.BuildOptions.IsDelta = true 424 o.BuildOptions.DisableCTags = true 425 }, 426 427 expectedFallbackToNormalBuild: true, 428 expectedDocuments: []zoekt.Document{fruitV2}, 429 }, 430 { 431 name: "try another delta build while CTags is still disabled", 432 addedDocuments: branchToDocumentMap{ 433 "main": []zoekt.Document{fruitV3}, 434 }, 435 optFn: func(t *testing.T, o *Options) { 436 o.BuildOptions.IsDelta = true 437 o.BuildOptions.DisableCTags = true 438 }, 439 440 expectedDocuments: []zoekt.Document{fruitV3}, 441 }, 442 }, 443 }, 444 { 445 name: "should fallback to normal build if repository has unsupported Sourcegraph ignore file", 446 branches: []string{"main"}, 447 steps: []step{ 448 { 449 name: "setup", 450 addedDocuments: branchToDocumentMap{ 451 "main": []zoekt.Document{emptySourcegraphIgnore}, 452 }, 453 454 expectedDocuments: []zoekt.Document{emptySourcegraphIgnore}, 455 }, 456 { 457 name: "attempt delta build after modifying ignore file", 458 addedDocuments: branchToDocumentMap{ 459 "main": []zoekt.Document{sourcegraphIgnoreWithContent}, 460 }, 461 optFn: func(t *testing.T, o *Options) { 462 o.BuildOptions.IsDelta = true 463 }, 464 465 expectedFallbackToNormalBuild: true, 466 expectedDocuments: []zoekt.Document{sourcegraphIgnoreWithContent}, 467 }, 468 }, 469 }, 470 { 471 name: "should fallback to a full, normal build if the repository has more than the specified threshold of shards", 472 branches: []string{"main"}, 473 steps: []step{ 474 { 475 name: "setup: first shard", 476 addedDocuments: branchToDocumentMap{ 477 "main": []zoekt.Document{foo}, 478 }, 479 480 expectedDocuments: []zoekt.Document{foo}, 481 }, 482 { 483 name: "setup: second shard (delta)", 484 addedDocuments: branchToDocumentMap{ 485 "main": []zoekt.Document{fruitV1}, 486 }, 487 optFn: func(t *testing.T, o *Options) { 488 o.BuildOptions.IsDelta = true 489 }, 490 491 expectedDocuments: []zoekt.Document{foo, fruitV1}, 492 }, 493 { 494 name: "setup: third shard (delta)", 495 addedDocuments: branchToDocumentMap{ 496 "main": []zoekt.Document{helloWorld}, 497 }, 498 optFn: func(t *testing.T, o *Options) { 499 o.BuildOptions.IsDelta = true 500 }, 501 502 expectedDocuments: []zoekt.Document{foo, fruitV1, helloWorld}, 503 }, 504 { 505 name: "attempt another delta build after we already blew past the shard threshold", 506 addedDocuments: branchToDocumentMap{ 507 "main": []zoekt.Document{fruitV2InFolder}, 508 }, 509 optFn: func(t *testing.T, o *Options) { 510 o.DeltaShardNumberFallbackThreshold = 2 511 o.BuildOptions.IsDelta = true 512 }, 513 514 expectedFallbackToNormalBuild: true, 515 expectedDocuments: []zoekt.Document{foo, fruitV1, helloWorld, fruitV2InFolder}, 516 }, 517 }, 518 }, 519 } { 520 test := test 521 522 t.Run(test.name, func(t *testing.T) { 523 t.Parallel() 524 525 indexDir := t.TempDir() 526 repositoryDir := t.TempDir() 527 528 // setup: initialize the repository and all of its branches 529 runScript(t, repositoryDir, "git init -b master") 530 runScript(t, repositoryDir, fmt.Sprintf("git config user.email %q", "you@example.com")) 531 runScript(t, repositoryDir, fmt.Sprintf("git config user.name %q", "Your Name")) 532 533 for _, b := range test.branches { 534 runScript(t, repositoryDir, fmt.Sprintf("git checkout -b %q", b)) 535 runScript(t, repositoryDir, fmt.Sprintf("git commit --allow-empty -m %q", "empty commit")) 536 } 537 538 for _, step := range test.steps { 539 t.Run(step.name, func(t *testing.T) { 540 for _, b := range test.branches { 541 // setup: for each branch, process any document deletions / additions and commit those changes 542 543 hadChange := false 544 545 runScript(t, repositoryDir, fmt.Sprintf("git checkout %q", b)) 546 547 for _, d := range step.deletedDocuments[b] { 548 hadChange = true 549 550 file := filepath.Join(repositoryDir, d.Name) 551 552 err := os.Remove(file) 553 if err != nil { 554 t.Fatalf("deleting file %q: %s", d.Name, err) 555 } 556 557 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file)) 558 } 559 560 for _, d := range step.addedDocuments[b] { 561 hadChange = true 562 563 file := filepath.Join(repositoryDir, d.Name) 564 565 err := os.MkdirAll(filepath.Dir(file), 0o755) 566 if err != nil { 567 t.Fatalf("ensuring that folders exist for file %q: %s", file, err) 568 } 569 570 err = os.WriteFile(file, d.Content, 0o644) 571 if err != nil { 572 t.Fatalf("writing file %q: %s", d.Name, err) 573 } 574 575 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file)) 576 } 577 578 if !hadChange { 579 continue 580 } 581 582 runScript(t, repositoryDir, fmt.Sprintf("git commit -m %q", step.name)) 583 } 584 585 // setup: prepare indexOptions with given overrides 586 buildOptions := build.Options{ 587 IndexDir: indexDir, 588 RepositoryDescription: zoekt.Repository{ 589 Name: "repository", 590 }, 591 IsDelta: false, 592 } 593 buildOptions.SetDefaults() 594 595 branches := append([]string{"HEAD"}, test.branches...) 596 597 options := Options{ 598 RepoDir: filepath.Join(repositoryDir, ".git"), 599 BuildOptions: buildOptions, 600 Branches: branches, 601 } 602 603 if step.optFn != nil { 604 step.optFn(t, &options) 605 } 606 607 // setup: prepare spy versions of prepare delta / normal build so that we can observe 608 // whether they were called appropriately 609 deltaBuildCalled := false 610 prepareDeltaSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchMap map[fileKey][]string, branchVersions map[string]map[string]plumbing.Hash, changedOrDeletedPaths []string, err error) { 611 deltaBuildCalled = true 612 return prepareDeltaBuild(options, repository) 613 } 614 615 normalBuildCalled := false 616 prepareNormalSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchMap map[fileKey][]string, branchVersions map[string]map[string]plumbing.Hash, err error) { 617 normalBuildCalled = true 618 return prepareNormalBuild(options, repository) 619 } 620 621 // run test 622 err := indexGitRepo(options, gitIndexConfig{ 623 prepareDeltaBuild: prepareDeltaSpy, 624 prepareNormalBuild: prepareNormalSpy, 625 }) 626 if err != nil { 627 t.Fatalf("IndexGitRepo: %s", err) 628 } 629 630 if options.BuildOptions.IsDelta != deltaBuildCalled { 631 // We should always try a delta build if we request it in the options. 632 t.Fatalf("expected deltaBuildCalled to be %t, got %t", options.BuildOptions.IsDelta, deltaBuildCalled) 633 } 634 635 if options.BuildOptions.IsDelta && (step.expectedFallbackToNormalBuild != normalBuildCalled) { 636 // We only check the normal spy on delta builds because it's only considered a "fallback" if we 637 // asked for a delta build in the first place. 638 t.Fatalf("expected normalBuildCalled to be %t, got %t", step.expectedFallbackToNormalBuild, normalBuildCalled) 639 } 640 641 // examine outcome: load shards into a searcher instance and run a dummy search query 642 // that returns every document contained in the shards 643 // 644 // then, compare returned set of documents with the expected set for the step and see if they agree 645 646 ss, err := shards.NewDirectorySearcher(indexDir) 647 if err != nil { 648 t.Fatalf("NewDirectorySearcher(%s): %s", indexDir, err) 649 } 650 defer ss.Close() 651 652 searchOpts := &zoekt.SearchOptions{Whole: true} 653 result, err := ss.Search(context.Background(), &query.Const{Value: true}, searchOpts) 654 if err != nil { 655 t.Fatalf("Search: %s", err) 656 } 657 658 var receivedDocuments []zoekt.Document 659 for _, f := range result.Files { 660 receivedDocuments = append(receivedDocuments, zoekt.Document{ 661 Name: f.FileName, 662 Content: f.Content, 663 }) 664 } 665 666 for _, docs := range [][]zoekt.Document{step.expectedDocuments, receivedDocuments} { 667 sort.Slice(docs, func(i, j int) bool { 668 a, b := docs[i], docs[j] 669 670 // first compare names, then fallback to contents if the names are equal 671 672 if a.Name < b.Name { 673 return true 674 } 675 676 if a.Name > b.Name { 677 return false 678 } 679 680 return bytes.Compare(a.Content, b.Content) < 0 681 }) 682 } 683 684 compareOptions := []cmp.Option{ 685 cmpopts.IgnoreFields(zoekt.Document{}, "Branches"), 686 cmpopts.EquateEmpty(), 687 } 688 689 if diff := cmp.Diff(step.expectedDocuments, receivedDocuments, compareOptions...); diff != "" { 690 t.Errorf("diff in received documents (-want +got):%s\n:", diff) 691 } 692 }) 693 } 694 }) 695 } 696} 697 698func TestRepoPathRanks(t *testing.T) { 699 pathRanks := repoPathRanks{ 700 Paths: map[string]float64{ 701 "search.go": 10.23, 702 "internal/index.go": 5.5, 703 "internal/scratch.go": 0.0, 704 "backend/search_test.go": 2.1, 705 }, 706 MeanRank: 3.3, 707 } 708 cases := []struct { 709 name string 710 path string 711 rank float64 712 }{ 713 { 714 name: "rank for standard file", 715 path: "search.go", 716 rank: 10.23, 717 }, 718 { 719 name: "file with rank 0", 720 path: "internal/scratch.go", 721 rank: 0.0, 722 }, 723 { 724 name: "rank for test file", 725 path: "backend/search_test.go", 726 rank: 2.1, 727 }, 728 { 729 name: "file with missing rank", 730 path: "internal/docs.md", 731 rank: 3.3, 732 }, 733 { 734 name: "test file with missing rank", 735 path: "backend/index_test.go", 736 rank: 0.0, 737 }, 738 { 739 name: "third-party file with missing rank", 740 path: "node_modules/search/index.js", 741 rank: 0.0, 742 }, 743 } 744 745 for _, tt := range cases { 746 t.Run(tt.name, func(t *testing.T) { 747 got := pathRanks.rank(tt.path) 748 if got != tt.rank { 749 t.Errorf("expected file '%s' to have rank %f, but got %f", tt.path, tt.rank, got) 750 } 751 }) 752 } 753} 754 755func runScript(t *testing.T, cwd string, script string) { 756 err := os.MkdirAll(cwd, 0o755) 757 if err != nil { 758 t.Fatalf("ensuring path %q exists: %s", cwd, err) 759 } 760 761 cmd := exec.Command("sh", "-euxc", script) 762 cmd.Dir = cwd 763 764 if out, err := cmd.CombinedOutput(); err != nil { 765 t.Fatalf("execution error: %v, output %s", err, out) 766 } 767}