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