fork of https://github.com/sourcegraph/zoekt
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{}