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

Configure Feed

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

1package main 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "reflect" 15 "sort" 16 "strconv" 17 "strings" 18 "testing" 19 "time" 20 21 "github.com/sourcegraph/log/logtest" 22 23 "google.golang.org/grpc" 24 "google.golang.org/protobuf/testing/protocmp" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 30 "github.com/sourcegraph/zoekt" 31 proto "github.com/sourcegraph/zoekt/cmd/zoekt-sourcegraph-indexserver/protos/sourcegraph/zoekt/configuration/v1" 32) 33 34func TestIterateIndexOptions_Fingerprint(t *testing.T) { 35 t.Run("gRPC", func(t *testing.T) { 36 fingerprintV0 := &proto.Fingerprint{ 37 Identifier: 100, 38 GeneratedAt: timestamppb.New(time.Unix(100, 0)), 39 } 40 41 fingerprintV1 := &proto.Fingerprint{ 42 Identifier: 101, 43 GeneratedAt: timestamppb.New(time.Unix(101, 0)), 44 } 45 46 fingerprintV2 := &proto.Fingerprint{ 47 Identifier: 102, 48 GeneratedAt: timestamppb.New(time.Unix(102, 0)), 49 } 50 51 mkSearchConfigurationResponse := func(fingerprint *proto.Fingerprint, repoIDs ...int32) *proto.SearchConfigurationResponse { 52 repositories := make([]*proto.ZoektIndexOptions, 0, len(repoIDs)) 53 for _, repoID := range repoIDs { 54 repositories = append(repositories, &proto.ZoektIndexOptions{ 55 RepoId: repoID, 56 }) 57 } 58 59 return &proto.SearchConfigurationResponse{ 60 UpdatedOptions: repositories, 61 Fingerprint: fingerprint, 62 } 63 } 64 65 grpcClient := &mockGRPCClient{ 66 mockList: func(_ context.Context, in *proto.ListRequest, opts ...grpc.CallOption) (*proto.ListResponse, error) { 67 return &proto.ListResponse{ 68 RepoIds: []int32{1, 2, 3}, 69 }, nil 70 }, 71 } 72 73 clientOpts := []SourcegraphClientOption{ 74 WithBatchSize(1), 75 WithShouldUseGRPC(true), 76 WithGRPCClient(grpcClient), 77 } 78 79 testURL := url.URL{Scheme: "http", Host: "does.not.matter", Path: "/"} 80 sg := newSourcegraphClient(&testURL, "", clientOpts...) 81 82 type step struct { 83 name string 84 85 wantFingerprint *proto.Fingerprint 86 returnFingerprint *proto.Fingerprint 87 returnErr error 88 skipCheckingRepoIDs bool 89 } 90 91 for _, step := range []step{ 92 { 93 name: "first call", 94 wantFingerprint: nil, 95 returnFingerprint: fingerprintV0, 96 }, 97 { 98 name: "second call (should provide fingerprint from last time)", 99 wantFingerprint: fingerprintV0, 100 returnFingerprint: fingerprintV1, 101 }, 102 { 103 name: "error", 104 wantFingerprint: fingerprintV1, 105 returnFingerprint: fingerprintV2, 106 107 returnErr: fmt.Errorf("boom"), 108 skipCheckingRepoIDs: true, // don't bother checking repoIDs if we expect an error 109 }, 110 { 111 name: "call after error (should ignore fingerprint from last time, and provide the older one)", 112 wantFingerprint: fingerprintV1, 113 returnFingerprint: fingerprintV2, 114 }, 115 } { 116 t.Run(step.name, func(t *testing.T) { 117 called := false 118 grpcClient.mockSearchConfiguration = func(_ context.Context, in *proto.SearchConfigurationRequest, opts ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) { 119 called = true 120 121 diff := cmp.Diff(step.wantFingerprint, in.GetFingerprint(), protocmp.Transform()) 122 if diff != "" { 123 t.Fatalf("unexpected fingerprint (-want +got):\n%s", diff) 124 } 125 126 return mkSearchConfigurationResponse(step.returnFingerprint, in.RepoIds...), step.returnErr 127 } 128 129 result, err := sg.List(context.Background(), nil) 130 if err != nil { 131 t.Fatalf("unexpected error from List: %v", err) 132 } 133 134 var iteratedIDs []uint32 135 result.IterateIndexOptions(func(options IndexOptions) { 136 iteratedIDs = append(iteratedIDs, options.RepoID) 137 }) 138 139 if !called { 140 t.Fatal("expected SearchConfiguration to be called") 141 } 142 143 if step.skipCheckingRepoIDs { 144 return 145 } 146 147 sort.Slice(iteratedIDs, func(i, j int) bool { 148 return iteratedIDs[i] < iteratedIDs[j] 149 }) 150 151 expectedIDs := []uint32{1, 2, 3} 152 sort.Slice(expectedIDs, func(i, j int) bool { 153 return expectedIDs[i] < expectedIDs[j] 154 }) 155 156 if diff := cmp.Diff(expectedIDs, iteratedIDs); diff != "" { 157 t.Fatalf("unexpected repo ids (-want +got):\n%s", diff) 158 } 159 }) 160 } 161 162 }) 163 164 t.Run("REST", func(t *testing.T) { 165 fingerprintV0 := "v0" 166 fingerprintV1 := "v1" 167 fingerprintV2 := "v2" 168 169 handleList := func(w http.ResponseWriter, _ *http.Request) { 170 data := struct { 171 RepoIDs []uint32 172 }{ 173 RepoIDs: []uint32{1, 2, 3}, 174 } 175 176 json.NewEncoder(w).Encode(data) 177 } 178 179 searchConfigurationHandler := func(w http.ResponseWriter, r *http.Request) { 180 http.Error(w, "this search configuration handler hasn't been overridden", http.StatusForbidden) 181 } 182 183 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 184 switch r.URL.Path { 185 case "/.internal/search/configuration": 186 searchConfigurationHandler(w, r) 187 case "/.internal/repos/index": 188 handleList(w, r) 189 default: 190 t.Fatalf("unexpected path: %s", r.URL.Path) 191 } 192 })) 193 defer server.Close() 194 195 clientOpts := []SourcegraphClientOption{ 196 WithBatchSize(1), 197 WithShouldUseGRPC(false), 198 } 199 200 testURL, err := url.Parse(server.URL) 201 if err != nil { 202 t.Fatalf("unexpected error parsing URL: %v", err) 203 } 204 205 sg := newSourcegraphClient(testURL, "", clientOpts...) 206 207 type step struct { 208 name string 209 210 wantFingerprint string 211 returnFingerprint string 212 returnErr error 213 skipCheckingRepoIDs bool 214 } 215 216 for _, step := range []step{ 217 { 218 name: "first call", 219 wantFingerprint: "", 220 returnFingerprint: fingerprintV0, 221 }, 222 { 223 name: "second call (should provide fingerprint from last time)", 224 wantFingerprint: fingerprintV0, 225 returnFingerprint: fingerprintV1, 226 }, 227 { 228 name: "error", 229 wantFingerprint: fingerprintV1, 230 returnFingerprint: fingerprintV2, 231 232 returnErr: fmt.Errorf("boom"), 233 skipCheckingRepoIDs: true, // don't bother checking repoIDs if we expect an error 234 }, 235 { 236 name: "call after error (should ignore fingerprint from last time, and provide the older one)", 237 wantFingerprint: fingerprintV1, 238 returnFingerprint: fingerprintV2, 239 }, 240 } { 241 t.Run(step.name, func(t *testing.T) { 242 called := false 243 244 searchConfigurationHandler = func(w http.ResponseWriter, r *http.Request) { 245 called = true 246 247 fingerprint := r.Header.Get(fingerprintHeader) 248 if diff := cmp.Diff(step.wantFingerprint, fingerprint); diff != "" { 249 t.Fatalf("unexpected fingerprint (-want +got):\n%s", diff) 250 } 251 252 w.Header().Set(fingerprintHeader, step.returnFingerprint) 253 254 if step.returnErr != nil { 255 // The status code is a bit of a hack, but it prevents 256 // the retry logic from kicking in and stalling the test for 45 seconds. 257 http.Error(w, step.returnErr.Error(), http.StatusBadRequest) 258 return 259 } 260 261 if err := r.ParseForm(); err != nil { 262 http.Error(w, fmt.Sprintf("unexpected error parsing form for repoIDs: %v", err.Error()), http.StatusBadRequest) 263 return 264 } 265 266 repoIDs := make([]uint32, 0, len(r.Form["repoID"])) 267 for _, idStr := range r.Form["repoID"] { 268 id, err := strconv.Atoi(idStr) 269 if err != nil { 270 http.Error(w, fmt.Sprintf("invalid repo id %s: %s", idStr, err), http.StatusBadRequest) 271 return 272 } 273 repoIDs = append(repoIDs, uint32(id)) 274 } 275 276 optionJSONSlice := make([][]byte, 0, len(repoIDs)) 277 for _, repoID := range repoIDs { 278 option := IndexOptions{ 279 RepoID: repoID, 280 } 281 282 optionJSON, err := json.Marshal(option) 283 if err != nil { 284 t.Fatalf("unexpected error marshalling JSON: %v", err) 285 } 286 287 optionJSONSlice = append(optionJSONSlice, optionJSON) 288 } 289 290 w.Write(bytes.Join(optionJSONSlice, []byte("\n"))) 291 } 292 293 result, err := sg.List(context.Background(), nil) 294 if err != nil { 295 t.Fatalf("unexpected error from List: %v", err) 296 } 297 298 var iteratedIDs []uint32 299 result.IterateIndexOptions(func(options IndexOptions) { 300 iteratedIDs = append(iteratedIDs, options.RepoID) 301 }) 302 303 if !called { 304 t.Fatal("expected SearchConfiguration to be called") 305 } 306 307 if step.skipCheckingRepoIDs { 308 return 309 } 310 311 sort.Slice(iteratedIDs, func(i, j int) bool { 312 return iteratedIDs[i] < iteratedIDs[j] 313 }) 314 315 expectedIDs := []uint32{1, 2, 3} 316 sort.Slice(expectedIDs, func(i, j int) bool { 317 return expectedIDs[i] < expectedIDs[j] 318 }) 319 320 if diff := cmp.Diff(expectedIDs, iteratedIDs); diff != "" { 321 t.Fatalf("unexpected repo ids (-want +got):\n%s", diff) 322 } 323 }) 324 } 325 326 }) 327} 328 329func TestGetIndexOptions(t *testing.T) { 330 t.Run("gRPC", func(t *testing.T) { 331 332 type testCase struct { 333 name string 334 response *proto.SearchConfigurationResponse 335 want *IndexOptions 336 wantErr string 337 } 338 339 for _, tc := range []testCase{ 340 { 341 name: "symbols, large files", 342 response: &proto.SearchConfigurationResponse{ 343 UpdatedOptions: []*proto.ZoektIndexOptions{ 344 { 345 Symbols: true, 346 LargeFiles: []string{"foo", "bar"}, 347 }, 348 }, 349 }, 350 want: &IndexOptions{ 351 Symbols: true, 352 LargeFiles: []string{"foo", "bar"}, 353 }, 354 }, 355 { 356 name: "no symbols , large files", 357 response: &proto.SearchConfigurationResponse{ 358 UpdatedOptions: []*proto.ZoektIndexOptions{ 359 { 360 Symbols: true, 361 LargeFiles: []string{"foo", "bar"}, 362 }, 363 }, 364 }, 365 want: &IndexOptions{ 366 Symbols: true, 367 LargeFiles: []string{"foo", "bar"}, 368 }, 369 }, 370 371 { 372 name: "empty", 373 response: nil, 374 want: nil, 375 }, 376 377 { 378 name: "symbols", 379 response: &proto.SearchConfigurationResponse{ 380 UpdatedOptions: []*proto.ZoektIndexOptions{ 381 { 382 Symbols: true, 383 }, 384 }, 385 }, 386 want: &IndexOptions{ 387 Symbols: true, 388 }, 389 }, 390 { 391 name: "repoID", 392 response: &proto.SearchConfigurationResponse{ 393 UpdatedOptions: []*proto.ZoektIndexOptions{ 394 { 395 RepoId: 123, 396 }, 397 }, 398 }, 399 want: &IndexOptions{ 400 RepoID: 123, 401 }, 402 }, 403 { 404 name: "error", 405 response: &proto.SearchConfigurationResponse{ 406 UpdatedOptions: []*proto.ZoektIndexOptions{ 407 { 408 Error: "boom", 409 }, 410 }, 411 }, 412 want: nil, 413 wantErr: "boom", 414 }, 415 } { 416 called := false 417 mockClient := &mockGRPCClient{ 418 mockSearchConfiguration: func(_ context.Context, _ *proto.SearchConfigurationRequest, _ ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) { 419 called = true 420 return tc.response, nil 421 }, 422 } 423 424 testURL := &url.URL{ 425 Scheme: "http", 426 Host: "does.not.matter", 427 Path: "/", 428 } 429 430 sg := newSourcegraphClient( 431 testURL, 432 "", 433 WithShouldUseGRPC(true), 434 WithGRPCClient(mockClient), 435 ) 436 437 var got IndexOptions 438 var err error 439 sg.ForceIterateIndexOptions(func(o IndexOptions) { 440 got = o 441 }, func(_ uint32, e error) { 442 err = e 443 }, 123) 444 445 if !called { 446 t.Fatal("expected mock to be called") 447 } 448 449 if err != nil { 450 if tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr) { 451 t.Fatalf("unexpected error: %v", err) 452 } 453 } 454 455 if tc.want == nil { 456 continue 457 } 458 459 tc.want.CloneURL = sg.getCloneURL(got.Name) 460 461 if diff := cmp.Diff(tc.want, &got, cmpopts.EquateEmpty()); diff != "" { 462 t.Errorf("mismatch (-want +got):\n%s", diff) 463 } 464 } 465 466 // Mimic our fingerprint API, which doesn't return anything if the 467 // repo hasn't changed. 468 t.Run("unchanged", func(t *testing.T) { 469 470 called := false 471 mockClient := &mockGRPCClient{ 472 mockSearchConfiguration: func(_ context.Context, _ *proto.SearchConfigurationRequest, _ ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) { 473 called = true 474 return nil, nil 475 }, 476 } 477 478 testURL := &url.URL{ 479 Scheme: "http", 480 Host: "does.not.matter", 481 Path: "/", 482 } 483 484 sg := newSourcegraphClient( 485 testURL, 486 "", 487 WithShouldUseGRPC(true), 488 WithGRPCClient(mockClient)) 489 490 gotAtLeastOneOption := false 491 var err error 492 sg.ForceIterateIndexOptions(func(_ IndexOptions) { 493 gotAtLeastOneOption = true 494 }, func(_ uint32, e error) { 495 err = e 496 }, 123) 497 498 if !called { 499 t.Fatal("expected mock to be called") 500 } 501 502 if err != nil { 503 t.Fatalf("unexpected error: %v", err) 504 } 505 506 if gotAtLeastOneOption { 507 t.Fatalf("expected no options, got %v", gotAtLeastOneOption) 508 } 509 }) 510 511 }) 512 t.Run("REST", func(t *testing.T) { 513 var response []byte 514 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 515 if err := r.ParseForm(); err != nil { 516 http.Error(w, err.Error(), http.StatusBadRequest) 517 return 518 } 519 if got, want := r.URL.String(), "/.internal/search/configuration"; got != want { 520 http.Error(w, fmt.Sprintf("got URL %v want %v", got, want), http.StatusBadRequest) 521 return 522 } 523 if got, want := r.Form, (url.Values{"repoID": []string{"123"}}); !reflect.DeepEqual(got, want) { 524 http.Error(w, fmt.Sprintf("got URL %v want %v", got, want), http.StatusBadRequest) 525 return 526 } 527 _, _ = w.Write(response) 528 })) 529 defer server.Close() 530 531 u, err := url.Parse(server.URL) 532 if err != nil { 533 t.Fatal(err) 534 } 535 536 sg := newSourcegraphClient(u, "", WithBatchSize(0)) 537 538 cases := map[string]*IndexOptions{ 539 `{"Symbols": true, "LargeFiles": ["foo","bar"]}`: { 540 Symbols: true, 541 LargeFiles: []string{"foo", "bar"}, 542 }, 543 544 `{"Symbols": false, "LargeFiles": ["foo","bar"]}`: { 545 LargeFiles: []string{"foo", "bar"}, 546 }, 547 548 `{}`: {}, 549 550 `{"Symbols": true}`: { 551 Symbols: true, 552 }, 553 554 `{"RepoID": 123}`: { 555 RepoID: 123, 556 }, 557 558 `{"Error": "boom"}`: nil, 559 } 560 561 for r, want := range cases { 562 response = []byte(r) 563 564 var got IndexOptions 565 var err error 566 sg.ForceIterateIndexOptions(func(o IndexOptions) { 567 got = o 568 }, func(_ uint32, e error) { 569 err = e 570 }, 123) 571 572 if err != nil && want != nil { 573 t.Fatalf("unexpected error: %v", err) 574 } 575 if want == nil { 576 continue 577 } 578 579 want.CloneURL = sg.getCloneURL(got.Name) 580 581 if d := cmp.Diff(*want, got); d != "" { 582 t.Log("response", r) 583 t.Errorf("mismatch (-want +got):\n%s", d) 584 } 585 } 586 587 // Special case our fingerprint API which doesn't return anything if the 588 // repo hasn't changed. 589 t.Run("unchanged", func(t *testing.T) { 590 response = []byte(``) 591 592 got := false 593 var err error 594 sg.ForceIterateIndexOptions(func(_ IndexOptions) { 595 got = true 596 }, func(_ uint32, e error) { 597 err = e 598 }, 123) 599 if err != nil { 600 t.Fatalf("unexpected error: %v", err) 601 } 602 603 if got { 604 t.Fatalf("expected no options, got %v", got) 605 } 606 }) 607 }) 608 609 var response []byte 610 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 611 if err := r.ParseForm(); err != nil { 612 http.Error(w, err.Error(), http.StatusBadRequest) 613 return 614 } 615 if got, want := r.URL.String(), "/.internal/search/configuration"; got != want { 616 http.Error(w, fmt.Sprintf("got URL %v want %v", got, want), http.StatusBadRequest) 617 return 618 } 619 if got, want := r.Form, (url.Values{"repoID": []string{"123"}}); !reflect.DeepEqual(got, want) { 620 http.Error(w, fmt.Sprintf("got URL %v want %v", got, want), http.StatusBadRequest) 621 return 622 } 623 _, _ = w.Write(response) 624 })) 625 defer server.Close() 626 627 u, err := url.Parse(server.URL) 628 if err != nil { 629 t.Fatal(err) 630 } 631 632 sg := newSourcegraphClient(u, "", WithBatchSize(0)) 633 634 cases := map[string]*IndexOptions{ 635 `{"Symbols": true, "LargeFiles": ["foo","bar"]}`: { 636 Symbols: true, 637 LargeFiles: []string{"foo", "bar"}, 638 }, 639 640 `{"Symbols": false, "LargeFiles": ["foo","bar"]}`: { 641 LargeFiles: []string{"foo", "bar"}, 642 }, 643 644 `{}`: {}, 645 646 `{"Symbols": true}`: { 647 Symbols: true, 648 }, 649 650 `{"RepoID": 123}`: { 651 RepoID: 123, 652 }, 653 654 `{"Error": "boom"}`: nil, 655 } 656 657 for r, want := range cases { 658 response = []byte(r) 659 660 var got IndexOptions 661 var err error 662 sg.ForceIterateIndexOptions(func(o IndexOptions) { 663 got = o 664 }, func(_ uint32, e error) { 665 err = e 666 }, 123) 667 668 if err != nil && want != nil { 669 t.Fatalf("unexpected error: %v", err) 670 } 671 if want == nil { 672 continue 673 } 674 675 want.CloneURL = sg.getCloneURL(got.Name) 676 677 if d := cmp.Diff(*want, got); d != "" { 678 t.Log("response", r) 679 t.Errorf("mismatch (-want +got):\n%s", d) 680 } 681 } 682 683 // Special case our fingerprint API which doesn't return anything if the 684 // repo hasn't changed. 685 t.Run("unchanged", func(t *testing.T) { 686 response = []byte(``) 687 688 got := false 689 var err error 690 sg.ForceIterateIndexOptions(func(_ IndexOptions) { 691 got = true 692 }, func(_ uint32, e error) { 693 err = e 694 }, 123) 695 if err != nil { 696 t.Fatalf("unexpected error: %v", err) 697 } 698 699 if got { 700 t.Fatalf("expected no options, got %v", got) 701 } 702 }) 703} 704 705func TestIndex(t *testing.T) { 706 cases := []struct { 707 name string 708 args indexArgs 709 mockRepositoryMetadata *zoekt.Repository 710 want []string 711 }{{ 712 name: "minimal", 713 args: indexArgs{ 714 IndexOptions: IndexOptions{ 715 Name: "test/repo", 716 CloneURL: "http://api.test/.internal/git/test/repo", 717 Branches: []zoekt.RepositoryBranch{{Name: "HEAD", Version: "deadbeef"}}, 718 }, 719 }, 720 want: []string{ 721 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git", 722 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef", 723 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef", 724 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0", 725 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0", 726 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo", 727 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0", 728 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0", 729 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0", 730 "zoekt-git-index -submodules=false -branches HEAD -disable_ctags $TMPDIR/test%2Frepo.git", 731 }, 732 }, { 733 name: "minimal-id", 734 args: indexArgs{ 735 IndexOptions: IndexOptions{ 736 Name: "test/repo", 737 CloneURL: "http://api.test/.internal/git/test/repo", 738 Branches: []zoekt.RepositoryBranch{{Name: "HEAD", Version: "deadbeef"}}, 739 RepoID: 123, 740 }, 741 }, 742 want: []string{ 743 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git", 744 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef", 745 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef", 746 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0", 747 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0", 748 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo", 749 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0", 750 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0", 751 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 123", 752 "zoekt-git-index -submodules=false -branches HEAD -disable_ctags $TMPDIR/test%2Frepo.git", 753 }, 754 }, { 755 name: "all", 756 args: indexArgs{ 757 Incremental: true, 758 IndexDir: "/data/index", 759 Parallelism: 4, 760 FileLimit: 123, 761 IndexOptions: IndexOptions{ 762 Name: "test/repo", 763 CloneURL: "http://api.test/.internal/git/test/repo", 764 LargeFiles: []string{"foo", "bar"}, 765 Symbols: true, 766 Branches: []zoekt.RepositoryBranch{ 767 {Name: "HEAD", Version: "deadbeef"}, 768 {Name: "dev", Version: "feebdaed"}, // ignored for archive 769 }, 770 }, 771 }, 772 want: []string{ 773 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git", 774 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef feebdaed", 775 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef", 776 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/dev feebdaed", 777 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0", 778 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0", 779 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo", 780 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0", 781 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0", 782 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0", 783 "zoekt-git-index -submodules=false -incremental -branches HEAD,dev " + 784 "-file_limit 123 -parallelism 4 -index /data/index -require_ctags -large_file foo -large_file bar " + 785 "$TMPDIR/test%2Frepo.git", 786 }, 787 }, { 788 name: "delta", 789 args: indexArgs{ 790 Incremental: true, 791 IndexDir: "/data/index", 792 Parallelism: 4, 793 FileLimit: 123, 794 UseDelta: true, 795 IndexOptions: IndexOptions{ 796 RepoID: 0, 797 Name: "test/repo", 798 CloneURL: "http://api.test/.internal/git/test/repo", 799 LargeFiles: []string{"foo", "bar"}, 800 Symbols: true, 801 Branches: []zoekt.RepositoryBranch{ 802 {Name: "HEAD", Version: "deadbeef"}, 803 {Name: "dev", Version: "feebdaed"}, 804 {Name: "release", Version: "12345678"}, 805 }, 806 }, 807 DeltaShardNumberFallbackThreshold: 22, 808 }, 809 mockRepositoryMetadata: &zoekt.Repository{ 810 ID: 0, 811 Name: "test/repo", 812 Branches: []zoekt.RepositoryBranch{ 813 {Name: "HEAD", Version: "oldhead"}, 814 {Name: "dev", Version: "olddev"}, 815 {Name: "release", Version: "oldrelease"}, 816 }, 817 }, 818 want: []string{ 819 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git", 820 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef feebdaed 12345678 oldhead olddev oldrelease", 821 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef", 822 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/dev feebdaed", 823 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/release 12345678", 824 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0", 825 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0", 826 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo", 827 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0", 828 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0", 829 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0", 830 "zoekt-git-index -submodules=false -incremental -branches HEAD,dev,release " + 831 "-delta -delta_threshold 22 -file_limit 123 -parallelism 4 -index /data/index -require_ctags -large_file foo -large_file bar " + 832 "$TMPDIR/test%2Frepo.git", 833 }, 834 }} 835 836 for _, tc := range cases { 837 t.Run(tc.name, func(t *testing.T) { 838 839 var got []string 840 runCmd := func(c *exec.Cmd) error { 841 cmd := strings.Join(c.Args, " ") 842 cmd = strings.ReplaceAll(cmd, filepath.Clean(os.TempDir()), "$TMPDIR") 843 got = append(got, cmd) 844 return nil 845 } 846 847 findRepositoryMetadata := func(args *indexArgs) (repository *zoekt.Repository, metadata *zoekt.IndexMetadata, ok bool, err error) { 848 if tc.mockRepositoryMetadata == nil { 849 return args.BuildOptions().FindRepositoryMetadata() 850 } 851 852 return tc.mockRepositoryMetadata, &zoekt.IndexMetadata{}, true, nil 853 } 854 855 c := gitIndexConfig{ 856 runCmd: runCmd, 857 findRepositoryMetadata: findRepositoryMetadata, 858 } 859 860 if err := gitIndex(c, &tc.args, sourcegraphNop{}, logtest.Scoped(t)); err != nil { 861 t.Fatal(err) 862 } 863 if !cmp.Equal(got, tc.want) { 864 t.Errorf("git mismatch (-want +got):\n%s", cmp.Diff(tc.want, got, splitargs)) 865 } 866 }) 867 } 868} 869 870var splitargs = cmpopts.AcyclicTransformer("splitargs", func(cmd string) []string { 871 return strings.Split(cmd, " ") 872}) 873 874type mockGRPCClient struct { 875 mockSearchConfiguration func(context.Context, *proto.SearchConfigurationRequest, ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) 876 mockList func(context.Context, *proto.ListRequest, ...grpc.CallOption) (*proto.ListResponse, error) 877 mockRepositoryRank func(context.Context, *proto.RepositoryRankRequest, ...grpc.CallOption) (*proto.RepositoryRankResponse, error) 878 mockDocumentRanks func(context.Context, *proto.DocumentRanksRequest, ...grpc.CallOption) (*proto.DocumentRanksResponse, error) 879 mockUpdateIndexStatus func(context.Context, *proto.UpdateIndexStatusRequest, ...grpc.CallOption) (*proto.UpdateIndexStatusResponse, error) 880} 881 882func (m *mockGRPCClient) SearchConfiguration(ctx context.Context, in *proto.SearchConfigurationRequest, opts ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) { 883 if m.mockSearchConfiguration != nil { 884 return m.mockSearchConfiguration(ctx, in, opts...) 885 } 886 887 return nil, fmt.Errorf("mock RPC SearchConfiguration not implemented") 888} 889 890func (m *mockGRPCClient) List(ctx context.Context, in *proto.ListRequest, opts ...grpc.CallOption) (*proto.ListResponse, error) { 891 if m.mockList != nil { 892 return m.mockList(ctx, in, opts...) 893 } 894 895 return nil, fmt.Errorf("mock RPC List not implemented") 896} 897 898func (m *mockGRPCClient) RepositoryRank(ctx context.Context, in *proto.RepositoryRankRequest, opts ...grpc.CallOption) (*proto.RepositoryRankResponse, error) { 899 if m.mockRepositoryRank != nil { 900 return m.mockRepositoryRank(ctx, in, opts...) 901 } 902 903 return nil, fmt.Errorf("mock RPC RepositoryRank not implemented") 904} 905 906func (m *mockGRPCClient) DocumentRanks(ctx context.Context, in *proto.DocumentRanksRequest, opts ...grpc.CallOption) (*proto.DocumentRanksResponse, error) { 907 if m.mockDocumentRanks != nil { 908 return m.mockDocumentRanks(ctx, in, opts...) 909 } 910 911 return nil, fmt.Errorf("mock RPC DocumentRanks not implemented") 912} 913 914func (m *mockGRPCClient) UpdateIndexStatus(ctx context.Context, in *proto.UpdateIndexStatusRequest, opts ...grpc.CallOption) (*proto.UpdateIndexStatusResponse, error) { 915 if m.mockUpdateIndexStatus != nil { 916 return m.mockUpdateIndexStatus(ctx, in, opts...) 917 } 918 919 return nil, fmt.Errorf("mock RPC UpdateIndexStatus not implemented") 920} 921 922var _ proto.ZoektConfigurationServiceClient = &mockGRPCClient{}