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