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