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 "errors" 21 "fmt" 22 "net/url" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "runtime" 27 "sort" 28 "strings" 29 "testing" 30 31 "github.com/go-git/go-git/v5" 32 "github.com/go-git/go-git/v5/plumbing" 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/go-cmp/cmp/cmpopts" 35 "github.com/sourcegraph/zoekt" 36 "github.com/sourcegraph/zoekt/ignore" 37 "github.com/sourcegraph/zoekt/index" 38 "github.com/sourcegraph/zoekt/internal/shards" 39 "github.com/sourcegraph/zoekt/query" 40) 41 42func TestIndexEmptyRepo(t *testing.T) { 43 dir := t.TempDir() 44 45 cmd := exec.Command("git", "init", "-b", "master", "repo") 46 cmd.Dir = dir 47 48 if err := cmd.Run(); err != nil { 49 t.Fatalf("cmd.Run: %v", err) 50 } 51 52 desc := zoekt.Repository{ 53 Name: "repo", 54 } 55 opts := Options{ 56 RepoDir: filepath.Join(dir, "repo", ".git"), 57 BuildOptions: index.Options{ 58 RepositoryDescription: desc, 59 IndexDir: dir, 60 }, 61 } 62 63 if _, err := IndexGitRepo(opts); err != nil { 64 t.Fatalf("IndexGitRepo: %v", err) 65 } 66} 67 68func TestIndexNonexistentRepo(t *testing.T) { 69 dir := t.TempDir() 70 desc := zoekt.Repository{ 71 Name: "nonexistent", 72 } 73 opts := Options{ 74 RepoDir: "does/not/exist", 75 Branches: []string{"main"}, 76 BuildOptions: index.Options{ 77 RepositoryDescription: desc, 78 IndexDir: dir, 79 }, 80 } 81 82 if _, err := IndexGitRepo(opts); err == nil { 83 t.Fatal("expected error, got none") 84 } else if !errors.Is(err, git.ErrRepositoryNotExists) { 85 t.Fatalf("expected git.ErrRepositoryNotExists, got %v", err) 86 } 87} 88 89func TestIndexTinyRepo(t *testing.T) { 90 // Create a repo with one file in it. 91 dir := t.TempDir() 92 executeCommand(t, dir, exec.Command("git", "init", "-b", "main", "repo")) 93 94 repoDir := filepath.Join(dir, "repo") 95 executeCommand(t, repoDir, exec.Command("git", "config", "--local", "user.name", "Thomas")) 96 executeCommand(t, repoDir, exec.Command("git", "config", "--local", "user.email", "thomas@google.com")) 97 98 if err := os.WriteFile(filepath.Join(repoDir, "file1.go"), []byte("package main\n\nfunc main() {}\n"), 0644); err != nil { 99 t.Fatalf("WriteFile: %v", err) 100 } 101 executeCommand(t, repoDir, exec.Command("git", "add", ".")) 102 executeCommand(t, repoDir, exec.Command("git", "commit", "-m", "initial commit")) 103 104 // Test that indexing accepts both the repo directory, and the .git subdirectory. 105 for _, testDir := range []string{"repo", "repo/.git"} { 106 opts := Options{ 107 RepoDir: filepath.Join(dir, testDir), 108 Branches: []string{"main"}, 109 BuildOptions: index.Options{ 110 RepositoryDescription: zoekt.Repository{Name: "repo"}, 111 IndexDir: dir, 112 }, 113 } 114 115 if _, err := IndexGitRepo(opts); err != nil { 116 t.Fatalf("unexpected error %v", err) 117 } 118 119 searcher, err := shards.NewDirectorySearcher(dir) 120 if err != nil { 121 t.Fatal("NewDirectorySearcher", err) 122 } 123 124 results, err := searcher.Search(context.Background(), &query.Const{Value: true}, &zoekt.SearchOptions{}) 125 searcher.Close() 126 127 if err != nil { 128 t.Fatal("search failed", err) 129 } 130 131 if len(results.Files) != 1 { 132 t.Fatalf("got search result %v, want 1 file", results.Files) 133 } 134 } 135} 136 137func executeCommand(t *testing.T, dir string, cmd *exec.Cmd) *exec.Cmd { 138 cmd.Dir = dir 139 if err := cmd.Run(); err != nil { 140 t.Fatalf("cmd.Run: %v", err) 141 } 142 return cmd 143} 144 145func TestIndexDeltaBasic(t *testing.T) { 146 type branchToDocumentMap map[string][]index.Document 147 148 type step struct { 149 name string 150 addedDocuments branchToDocumentMap 151 deletedDocuments branchToDocumentMap 152 optFn func(t *testing.T, options *Options) 153 154 expectedFallbackToNormalBuild bool 155 expectedDocuments []index.Document 156 } 157 158 helloWorld := index.Document{Name: "hello_world.txt", Content: []byte("hello")} 159 160 fruitV1 := index.Document{Name: "best_fruit.txt", Content: []byte("strawberry")} 161 fruitV1InFolder := index.Document{Name: "the_best/best_fruit.txt", Content: fruitV1.Content} 162 fruitV1WithNewName := index.Document{Name: "new_fruit.txt", Content: fruitV1.Content} 163 164 fruitV2 := index.Document{Name: "best_fruit.txt", Content: []byte("grapes")} 165 fruitV2InFolder := index.Document{Name: "the_best/best_fruit.txt", Content: fruitV2.Content} 166 167 fruitV3 := index.Document{Name: "best_fruit.txt", Content: []byte("oranges")} 168 fruitV4 := index.Document{Name: "best_fruit.txt", Content: []byte("apples")} 169 170 foo := index.Document{Name: "foo.txt", Content: []byte("bar")} 171 172 emptySourcegraphIgnore := index.Document{Name: ignore.IgnoreFile} 173 sourcegraphIgnoreWithContent := index.Document{Name: ignore.IgnoreFile, Content: []byte("good_content.txt")} 174 175 for _, test := range []struct { 176 name string 177 branches []string 178 steps []step 179 }{ 180 { 181 name: "modification", 182 branches: []string{"main"}, 183 steps: []step{ 184 { 185 name: "setup", 186 addedDocuments: branchToDocumentMap{ 187 "main": []index.Document{helloWorld, fruitV1}, 188 }, 189 190 expectedDocuments: []index.Document{helloWorld, fruitV1}, 191 }, 192 { 193 name: "add newer version of fruits", 194 addedDocuments: branchToDocumentMap{ 195 "main": []index.Document{fruitV2}, 196 }, 197 optFn: func(t *testing.T, o *Options) { 198 o.BuildOptions.IsDelta = true 199 }, 200 201 expectedDocuments: []index.Document{helloWorld, fruitV2}, 202 }, 203 }, 204 }, 205 { 206 name: "modification only inside nested folder", 207 branches: []string{"main"}, 208 steps: []step{ 209 { 210 name: "setup", 211 addedDocuments: branchToDocumentMap{ 212 "main": []index.Document{foo, fruitV1InFolder}, 213 }, 214 215 expectedDocuments: []index.Document{foo, fruitV1InFolder}, 216 }, 217 { 218 name: "add newer version of fruits inside folder", 219 addedDocuments: branchToDocumentMap{ 220 "main": []index.Document{fruitV2InFolder}, 221 }, 222 optFn: func(t *testing.T, o *Options) { 223 o.BuildOptions.IsDelta = true 224 }, 225 226 expectedDocuments: []index.Document{foo, fruitV2InFolder}, 227 }, 228 }, 229 }, 230 { 231 name: "addition", 232 branches: []string{"main"}, 233 steps: []step{ 234 { 235 name: "setup", 236 addedDocuments: branchToDocumentMap{ 237 "main": []index.Document{helloWorld, fruitV1}, 238 }, 239 240 expectedDocuments: []index.Document{helloWorld, fruitV1}, 241 }, 242 { 243 name: "add new file - foo", 244 addedDocuments: branchToDocumentMap{ 245 "main": []index.Document{foo}, 246 }, 247 optFn: func(t *testing.T, o *Options) { 248 o.BuildOptions.IsDelta = true 249 }, 250 251 expectedDocuments: []index.Document{helloWorld, fruitV1, foo}, 252 }, 253 }, 254 }, 255 { 256 name: "deletion", 257 branches: []string{"main"}, 258 steps: []step{ 259 { 260 name: "setup", 261 addedDocuments: branchToDocumentMap{ 262 "main": []index.Document{helloWorld, fruitV1, foo}, 263 }, 264 265 expectedDocuments: []index.Document{helloWorld, fruitV1, foo}, 266 }, 267 { 268 name: "delete foo file", 269 addedDocuments: nil, 270 deletedDocuments: branchToDocumentMap{ 271 "main": []index.Document{foo}, 272 }, 273 274 optFn: func(t *testing.T, o *Options) { 275 o.BuildOptions.IsDelta = true 276 }, 277 278 expectedDocuments: []index.Document{helloWorld, fruitV1}, 279 }, 280 }, 281 }, 282 { 283 name: "addition and deletion on only one branch", 284 branches: []string{"main", "release", "dev"}, 285 steps: []step{ 286 { 287 name: "setup", 288 addedDocuments: branchToDocumentMap{ 289 "main": []index.Document{fruitV1}, 290 "release": []index.Document{fruitV2}, 291 "dev": []index.Document{fruitV3}, 292 }, 293 294 expectedDocuments: []index.Document{fruitV1, fruitV2, fruitV3}, 295 }, 296 { 297 name: "replace fruits v3 with v4 on 'dev', delete fruits on 'main'", 298 addedDocuments: branchToDocumentMap{ 299 "dev": []index.Document{fruitV4}, 300 }, 301 deletedDocuments: branchToDocumentMap{ 302 "main": []index.Document{fruitV1}, 303 }, 304 305 optFn: func(t *testing.T, o *Options) { 306 o.BuildOptions.IsDelta = true 307 }, 308 309 expectedDocuments: []index.Document{fruitV2, fruitV4}, 310 }, 311 }, 312 }, 313 { 314 name: "rename", 315 branches: []string{"main", "release"}, 316 steps: []step{ 317 { 318 name: "setup", 319 addedDocuments: branchToDocumentMap{ 320 "main": []index.Document{fruitV1}, 321 "release": []index.Document{fruitV2}, 322 }, 323 expectedDocuments: []index.Document{fruitV1, fruitV2}, 324 }, 325 { 326 name: "rename fruits file on 'main' + ensure that unmodified fruits file on 'release' is still searchable", 327 addedDocuments: branchToDocumentMap{ 328 "main": []index.Document{fruitV1WithNewName}, 329 }, 330 deletedDocuments: branchToDocumentMap{ 331 "main": []index.Document{fruitV1}, 332 }, 333 334 optFn: func(t *testing.T, o *Options) { 335 o.BuildOptions.IsDelta = true 336 }, 337 338 expectedDocuments: []index.Document{fruitV1WithNewName, fruitV2}, 339 }, 340 }, 341 }, 342 { 343 name: "modification: update one branch with version of document from another branch (a.k.a. Keegan's test)", 344 branches: []string{"main", "dev"}, 345 steps: []step{ 346 { 347 name: "setup", 348 addedDocuments: branchToDocumentMap{ 349 "main": []index.Document{fruitV1}, 350 "dev": []index.Document{fruitV2}, 351 }, 352 expectedDocuments: []index.Document{fruitV1, fruitV2}, 353 }, 354 { 355 name: "switch main to dev's older version of fruits + bump dev's fruits to new version", 356 addedDocuments: branchToDocumentMap{ 357 "main": []index.Document{fruitV2}, 358 "dev": []index.Document{fruitV3}, 359 }, 360 361 optFn: func(t *testing.T, o *Options) { 362 o.BuildOptions.IsDelta = true 363 }, 364 365 expectedDocuments: []index.Document{fruitV2, fruitV3}, 366 }, 367 }, 368 }, 369 { 370 name: "no-op delta builds (reindexing the same commits)", 371 branches: []string{"main", "dev"}, 372 steps: []step{ 373 { 374 name: "setup", 375 addedDocuments: branchToDocumentMap{ 376 "main": []index.Document{fruitV1, foo}, 377 "dev": []index.Document{helloWorld}, 378 }, 379 expectedDocuments: []index.Document{fruitV1, foo, helloWorld}, 380 }, 381 { 382 name: "first no-op (normal build -> delta build)", 383 optFn: func(t *testing.T, o *Options) { 384 o.BuildOptions.IsDelta = true 385 }, 386 387 expectedDocuments: []index.Document{fruitV1, foo, helloWorld}, 388 }, 389 { 390 name: "second no-op (delta build -> delta build)", 391 optFn: func(t *testing.T, o *Options) { 392 o.BuildOptions.IsDelta = true 393 }, 394 395 expectedDocuments: []index.Document{fruitV1, foo, helloWorld}, 396 }, 397 }, 398 }, 399 { 400 name: "should fallback to normal build if no prior shards exist", 401 branches: []string{"main"}, 402 steps: []step{ 403 { 404 name: "attempt delta build on a repository that hasn't been indexed yet", 405 addedDocuments: branchToDocumentMap{ 406 "main": []index.Document{helloWorld}, 407 }, 408 optFn: func(t *testing.T, o *Options) { 409 o.BuildOptions.IsDelta = true 410 }, 411 412 expectedFallbackToNormalBuild: true, 413 expectedDocuments: []index.Document{helloWorld}, 414 }, 415 }, 416 }, 417 { 418 name: "should fallback to normal build if the set of requested repository branches changes", 419 branches: []string{"main", "release", "dev"}, 420 steps: []step{ 421 { 422 name: "setup", 423 addedDocuments: branchToDocumentMap{ 424 "main": []index.Document{fruitV1}, 425 "release": []index.Document{fruitV2}, 426 "dev": []index.Document{fruitV3}, 427 }, 428 429 expectedDocuments: []index.Document{fruitV1, fruitV2, fruitV3}, 430 }, 431 { 432 name: "try delta build after dropping 'main' branch from index ", 433 addedDocuments: branchToDocumentMap{ 434 "release": []index.Document{fruitV4}, 435 }, 436 optFn: func(t *testing.T, o *Options) { 437 o.Branches = []string{"HEAD", "release", "dev"} // a bit of a hack to override it this way, but it gets the job done 438 o.BuildOptions.IsDelta = true 439 }, 440 441 expectedFallbackToNormalBuild: true, 442 expectedDocuments: []index.Document{fruitV3, fruitV4}, 443 }, 444 }, 445 }, 446 { 447 name: "should fallback to normal build if one or more index options updates requires a full build", 448 branches: []string{"main"}, 449 steps: []step{ 450 { 451 name: "setup", 452 addedDocuments: branchToDocumentMap{ 453 "main": []index.Document{fruitV1}, 454 }, 455 456 expectedDocuments: []index.Document{fruitV1}, 457 }, 458 { 459 name: "try delta build after updating Disable CTags index option", 460 addedDocuments: branchToDocumentMap{ 461 "main": []index.Document{fruitV2}, 462 }, 463 optFn: func(t *testing.T, o *Options) { 464 o.BuildOptions.IsDelta = true 465 o.BuildOptions.DisableCTags = true 466 }, 467 468 expectedFallbackToNormalBuild: true, 469 expectedDocuments: []index.Document{fruitV2}, 470 }, 471 { 472 name: "try delta build after reverting Disable CTags index option", 473 addedDocuments: branchToDocumentMap{ 474 "main": []index.Document{fruitV3}, 475 }, 476 optFn: func(t *testing.T, o *Options) { 477 o.BuildOptions.IsDelta = true 478 o.BuildOptions.DisableCTags = false 479 }, 480 481 expectedFallbackToNormalBuild: true, 482 expectedDocuments: []index.Document{fruitV3}, 483 }, 484 }, 485 }, 486 { 487 name: "should successfully perform multiple delta builds after disabling symbols", 488 branches: []string{"main"}, 489 steps: []step{ 490 { 491 name: "setup", 492 addedDocuments: branchToDocumentMap{ 493 "main": []index.Document{fruitV1}, 494 }, 495 496 expectedDocuments: []index.Document{fruitV1}, 497 }, 498 { 499 name: "try delta build after updating Disable CTags index option", 500 addedDocuments: branchToDocumentMap{ 501 "main": []index.Document{fruitV2}, 502 }, 503 optFn: func(t *testing.T, o *Options) { 504 o.BuildOptions.IsDelta = true 505 o.BuildOptions.DisableCTags = true 506 }, 507 508 expectedFallbackToNormalBuild: true, 509 expectedDocuments: []index.Document{fruitV2}, 510 }, 511 { 512 name: "try another delta build while CTags is still disabled", 513 addedDocuments: branchToDocumentMap{ 514 "main": []index.Document{fruitV3}, 515 }, 516 optFn: func(t *testing.T, o *Options) { 517 o.BuildOptions.IsDelta = true 518 o.BuildOptions.DisableCTags = true 519 }, 520 521 expectedDocuments: []index.Document{fruitV3}, 522 }, 523 }, 524 }, 525 { 526 name: "should fallback to normal build if repository has unsupported Sourcegraph ignore file", 527 branches: []string{"main"}, 528 steps: []step{ 529 { 530 name: "setup", 531 addedDocuments: branchToDocumentMap{ 532 "main": []index.Document{emptySourcegraphIgnore}, 533 }, 534 535 expectedDocuments: []index.Document{emptySourcegraphIgnore}, 536 }, 537 { 538 name: "attempt delta build after modifying ignore file", 539 addedDocuments: branchToDocumentMap{ 540 "main": []index.Document{sourcegraphIgnoreWithContent}, 541 }, 542 optFn: func(t *testing.T, o *Options) { 543 o.BuildOptions.IsDelta = true 544 }, 545 546 expectedFallbackToNormalBuild: true, 547 expectedDocuments: []index.Document{sourcegraphIgnoreWithContent}, 548 }, 549 }, 550 }, 551 { 552 name: "should fallback to a full, normal build if the repository has more than the specified threshold of shards", 553 branches: []string{"main"}, 554 steps: []step{ 555 { 556 name: "setup: first shard", 557 addedDocuments: branchToDocumentMap{ 558 "main": []index.Document{foo}, 559 }, 560 561 expectedDocuments: []index.Document{foo}, 562 }, 563 { 564 name: "setup: second shard (delta)", 565 addedDocuments: branchToDocumentMap{ 566 "main": []index.Document{fruitV1}, 567 }, 568 optFn: func(t *testing.T, o *Options) { 569 o.BuildOptions.IsDelta = true 570 }, 571 572 expectedDocuments: []index.Document{foo, fruitV1}, 573 }, 574 { 575 name: "setup: third shard (delta)", 576 addedDocuments: branchToDocumentMap{ 577 "main": []index.Document{helloWorld}, 578 }, 579 optFn: func(t *testing.T, o *Options) { 580 o.BuildOptions.IsDelta = true 581 }, 582 583 expectedDocuments: []index.Document{foo, fruitV1, helloWorld}, 584 }, 585 { 586 name: "attempt another delta build after we already blew past the shard threshold", 587 addedDocuments: branchToDocumentMap{ 588 "main": []index.Document{fruitV2InFolder}, 589 }, 590 optFn: func(t *testing.T, o *Options) { 591 o.DeltaShardNumberFallbackThreshold = 2 592 o.BuildOptions.IsDelta = true 593 }, 594 595 expectedFallbackToNormalBuild: true, 596 expectedDocuments: []index.Document{foo, fruitV1, helloWorld, fruitV2InFolder}, 597 }, 598 }, 599 }, 600 } { 601 test := test 602 603 t.Run(test.name, func(t *testing.T) { 604 t.Parallel() 605 606 indexDir := t.TempDir() 607 repositoryDir := t.TempDir() 608 609 // setup: initialize the repository and all of its branches 610 runScript(t, repositoryDir, "git init -b master") 611 runScript(t, repositoryDir, fmt.Sprintf("git config user.email %q", "you@example.com")) 612 runScript(t, repositoryDir, fmt.Sprintf("git config user.name %q", "Your Name")) 613 614 for _, b := range test.branches { 615 runScript(t, repositoryDir, fmt.Sprintf("git checkout -b %q", b)) 616 runScript(t, repositoryDir, fmt.Sprintf("git commit --allow-empty -m %q", "empty commit")) 617 } 618 619 for _, step := range test.steps { 620 t.Run(step.name, func(t *testing.T) { 621 for _, b := range test.branches { 622 // setup: for each branch, process any document deletions / additions and commit those changes 623 624 hadChange := false 625 626 runScript(t, repositoryDir, fmt.Sprintf("git checkout %q", b)) 627 628 for _, d := range step.deletedDocuments[b] { 629 hadChange = true 630 631 file := filepath.Join(repositoryDir, d.Name) 632 633 err := os.Remove(file) 634 if err != nil { 635 t.Fatalf("deleting file %q: %s", d.Name, err) 636 } 637 638 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file)) 639 } 640 641 for _, d := range step.addedDocuments[b] { 642 hadChange = true 643 644 file := filepath.Join(repositoryDir, d.Name) 645 646 err := os.MkdirAll(filepath.Dir(file), 0o755) 647 if err != nil { 648 t.Fatalf("ensuring that folders exist for file %q: %s", file, err) 649 } 650 651 err = os.WriteFile(file, d.Content, 0o644) 652 if err != nil { 653 t.Fatalf("writing file %q: %s", d.Name, err) 654 } 655 656 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file)) 657 } 658 659 if !hadChange { 660 continue 661 } 662 663 runScript(t, repositoryDir, fmt.Sprintf("git commit -m %q", step.name)) 664 } 665 666 // setup: prepare indexOptions with given overrides 667 buildOptions := index.Options{ 668 IndexDir: indexDir, 669 RepositoryDescription: zoekt.Repository{ 670 Name: "repository", 671 }, 672 IsDelta: false, 673 } 674 buildOptions.SetDefaults() 675 676 branches := append([]string{"HEAD"}, test.branches...) 677 678 options := Options{ 679 RepoDir: filepath.Join(repositoryDir, ".git"), 680 BuildOptions: buildOptions, 681 Branches: branches, 682 } 683 684 if step.optFn != nil { 685 step.optFn(t, &options) 686 } 687 688 // setup: prepare spy versions of prepare delta / normal build so that we can observe 689 // whether they were called appropriately 690 deltaBuildCalled := false 691 prepareDeltaSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchVersions map[string]map[string]plumbing.Hash, changedOrDeletedPaths []string, err error) { 692 deltaBuildCalled = true 693 return prepareDeltaBuild(options, repository) 694 } 695 696 normalBuildCalled := false 697 prepareNormalSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchVersions map[string]map[string]plumbing.Hash, err error) { 698 normalBuildCalled = true 699 return prepareNormalBuild(options, repository) 700 } 701 702 // run test 703 _, err := indexGitRepo(options, gitIndexConfig{ 704 prepareDeltaBuild: prepareDeltaSpy, 705 prepareNormalBuild: prepareNormalSpy, 706 }) 707 if err != nil { 708 t.Fatalf("IndexGitRepo: %s", err) 709 } 710 711 if options.BuildOptions.IsDelta != deltaBuildCalled { 712 // We should always try a delta build if we request it in the options. 713 t.Fatalf("expected deltaBuildCalled to be %t, got %t", options.BuildOptions.IsDelta, deltaBuildCalled) 714 } 715 716 if options.BuildOptions.IsDelta && (step.expectedFallbackToNormalBuild != normalBuildCalled) { 717 // We only check the normal spy on delta builds because it's only considered a "fallback" if we 718 // asked for a delta build in the first place. 719 t.Fatalf("expected normalBuildCalled to be %t, got %t", step.expectedFallbackToNormalBuild, normalBuildCalled) 720 } 721 722 // examine outcome: load shards into a searcher instance and run a dummy search query 723 // that returns every document contained in the shards 724 // 725 // then, compare returned set of documents with the expected set for the step and see if they agree 726 727 ss, err := shards.NewDirectorySearcher(indexDir) 728 if err != nil { 729 t.Fatalf("NewDirectorySearcher(%s): %s", indexDir, err) 730 } 731 defer ss.Close() 732 733 searchOpts := &zoekt.SearchOptions{Whole: true} 734 result, err := ss.Search(context.Background(), &query.Const{Value: true}, searchOpts) 735 if err != nil { 736 t.Fatalf("Search: %s", err) 737 } 738 739 var receivedDocuments []index.Document 740 for _, f := range result.Files { 741 receivedDocuments = append(receivedDocuments, index.Document{ 742 Name: f.FileName, 743 Content: f.Content, 744 }) 745 } 746 747 for _, docs := range [][]index.Document{step.expectedDocuments, receivedDocuments} { 748 sort.Slice(docs, func(i, j int) bool { 749 a, b := docs[i], docs[j] 750 751 // first compare names, then fallback to contents if the names are equal 752 753 if a.Name < b.Name { 754 return true 755 } 756 757 if a.Name > b.Name { 758 return false 759 } 760 761 return bytes.Compare(a.Content, b.Content) < 0 762 }) 763 } 764 765 compareOptions := []cmp.Option{ 766 cmpopts.IgnoreFields(index.Document{}, "Branches"), 767 cmpopts.EquateEmpty(), 768 } 769 770 if diff := cmp.Diff(step.expectedDocuments, receivedDocuments, compareOptions...); diff != "" { 771 t.Errorf("diff in received documents (-want +got):%s\n:", diff) 772 } 773 }) 774 } 775 }) 776 } 777} 778 779func runScript(t *testing.T, cwd string, script string) { 780 t.Helper() 781 782 err := os.MkdirAll(cwd, 0o755) 783 if err != nil { 784 t.Fatalf("ensuring path %q exists: %s", cwd, err) 785 } 786 787 cmd := exec.Command("sh", "-euxc", script) 788 cmd.Dir = cwd 789 cmd.Env = append([]string{"GIT_CONFIG_GLOBAL=", "GIT_CONFIG_SYSTEM="}, os.Environ()...) 790 791 if out, err := cmd.CombinedOutput(); err != nil { 792 t.Fatalf("execution error: %v, output %s", err, out) 793 } 794} 795 796func TestSetTemplates_e2e(t *testing.T) { 797 repositoryDir := t.TempDir() 798 799 // setup: initialize the repository and all of its branches 800 runScript(t, repositoryDir, "git init -b master") 801 runScript(t, repositoryDir, "git config remote.origin.url git@github.com:sourcegraph/zoekt.git") 802 desc := zoekt.Repository{} 803 if err := setTemplatesFromConfig(&desc, repositoryDir); err != nil { 804 t.Fatalf("setTemplatesFromConfig: %v", err) 805 } 806 807 if got, want := desc.FileURLTemplate, `{{URLJoinPath "https://github.com/sourcegraph/zoekt" "blob" .Version .Path}}`; got != want { 808 t.Errorf("got %q, want %q", got, want) 809 } 810} 811 812func TestSetTemplates(t *testing.T) { 813 base := "https://example.com/repo/name" 814 version := "VERSION" 815 path := "dir/name.txt" 816 lineNumber := 10 817 cases := []struct { 818 typ string 819 commit string 820 file string 821 line string 822 }{{ 823 typ: "gitiles", 824 commit: "https://example.com/repo/name/%2B/VERSION", 825 file: "https://example.com/repo/name/%2B/VERSION/dir/name.txt", 826 line: "#10", 827 }, { 828 typ: "github", 829 commit: "https://example.com/repo/name/commit/VERSION", 830 file: "https://example.com/repo/name/blob/VERSION/dir/name.txt", 831 line: "#L10", 832 }, { 833 typ: "cgit", 834 commit: "https://example.com/repo/name/commit/?id=VERSION", 835 file: "https://example.com/repo/name/tree/dir/name.txt/?id=VERSION", 836 line: "#n10", 837 }, { 838 typ: "gitweb", 839 commit: "https://example.com/repo/name;a=commit;h=VERSION", 840 file: "https://example.com/repo/name;a=blob;f=dir/name.txt;hb=VERSION", 841 line: "#l10", 842 }, { 843 typ: "source.bazel.build", 844 commit: "https://example.com/repo/name/%2B/VERSION", 845 file: "https://example.com/repo/name/%2B/VERSION:dir/name.txt", 846 line: ";l=10", 847 }, { 848 typ: "bitbucket-server", 849 commit: "https://example.com/repo/name/commits/VERSION", 850 file: "https://example.com/repo/name/dir/name.txt?at=VERSION", 851 line: "#10", 852 }, { 853 typ: "gitlab", 854 commit: "https://example.com/repo/name/-/commit/VERSION", 855 file: "https://example.com/repo/name/-/blob/VERSION/dir/name.txt", 856 line: "#L10", 857 }, { 858 typ: "gitea", 859 commit: "https://example.com/repo/name/commit/VERSION", 860 file: "https://example.com/repo/name/src/commit/VERSION/dir/name.txt?display=source", 861 line: "#L10", 862 }} 863 864 for _, tc := range cases { 865 t.Run(tc.typ, func(t *testing.T) { 866 assertOutput := func(templateText string, want string) { 867 t.Helper() 868 869 tt, err := index.ParseTemplate(templateText) 870 if err != nil { 871 t.Fatal(err) 872 } 873 874 var sb strings.Builder 875 err = tt.Execute(&sb, map[string]any{ 876 "Version": version, 877 "Path": path, 878 "LineNumber": lineNumber, 879 }) 880 if err != nil { 881 t.Fatal(err) 882 } 883 if got := sb.String(); got != want { 884 t.Fatalf("want: %q\ngot: %q", want, got) 885 } 886 } 887 888 var repo zoekt.Repository 889 u, _ := url.Parse(base) 890 err := setTemplates(&repo, u, tc.typ) 891 if err != nil { 892 t.Fatal(err) 893 } 894 assertOutput(repo.CommitURLTemplate, tc.commit) 895 assertOutput(repo.FileURLTemplate, tc.file) 896 assertOutput(repo.LineFragmentTemplate, tc.line) 897 }) 898 } 899} 900 901func BenchmarkPrepareNormalBuild(b *testing.B) { 902 // NOTE: To run the benchmark, download a large repo (like github.com/chromium/chromium/) and change this to its path. 903 repoDir := "/path/to/your/repo" 904 repo, err := git.PlainOpen(repoDir) 905 if err != nil { 906 b.Fatalf("Failed to open test repository: %v", err) 907 } 908 909 opts := Options{ 910 RepoDir: repoDir, 911 Submodules: false, 912 BranchPrefix: "refs/heads/", 913 Branches: []string{"main"}, 914 BuildOptions: index.Options{ 915 RepositoryDescription: zoekt.Repository{ 916 Name: "test-repo", 917 URL: "https://github.com/example/test-repo", 918 }, 919 }, 920 } 921 922 b.ReportAllocs() 923 924 repos, branchVersions, err := prepareNormalBuild(opts, repo) 925 if err != nil { 926 b.Fatalf("prepareNormalBuild failed: %v", err) 927 } 928 929 runtime.GC() 930 931 var m runtime.MemStats 932 runtime.ReadMemStats(&m) 933 b.ReportMetric(float64(m.HeapInuse), "heap-used-bytes") 934 b.ReportMetric(float64(m.HeapInuse), "heap-allocated-bytes") 935 936 if len(repos) == 0 || len(branchVersions) == 0 { 937 b.Fatalf("Unexpected empty results") 938 } 939}