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