fork of https://github.com/sourcegraph/zoekt
1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package gitindex
16
17import (
18 "bytes"
19 "context"
20 "fmt"
21 "os"
22 "os/exec"
23 "path/filepath"
24 "runtime"
25 "sort"
26 "testing"
27
28 "github.com/go-git/go-git/v5"
29 "github.com/go-git/go-git/v5/plumbing"
30 "github.com/google/go-cmp/cmp"
31 "github.com/google/go-cmp/cmp/cmpopts"
32 "github.com/sourcegraph/zoekt"
33 "github.com/sourcegraph/zoekt/build"
34 "github.com/sourcegraph/zoekt/ignore"
35 "github.com/sourcegraph/zoekt/query"
36 "github.com/sourcegraph/zoekt/shards"
37)
38
39func TestIndexEmptyRepo(t *testing.T) {
40 dir := t.TempDir()
41
42 cmd := exec.Command("git", "init", "-b", "master", "repo")
43 cmd.Dir = dir
44
45 if err := cmd.Run(); err != nil {
46 t.Fatalf("cmd.Run: %v", err)
47 }
48
49 desc := zoekt.Repository{
50 Name: "repo",
51 }
52 opts := Options{
53 RepoDir: filepath.Join(dir, "repo", ".git"),
54 BuildOptions: build.Options{
55 RepositoryDescription: desc,
56 IndexDir: dir,
57 },
58 }
59
60 if _, err := IndexGitRepo(opts); err != nil {
61 t.Fatalf("IndexGitRepo: %v", err)
62 }
63}
64
65func TestIndexDeltaBasic(t *testing.T) {
66 type branchToDocumentMap map[string][]zoekt.Document
67
68 type step struct {
69 name string
70 addedDocuments branchToDocumentMap
71 deletedDocuments branchToDocumentMap
72 optFn func(t *testing.T, options *Options)
73
74 expectedFallbackToNormalBuild bool
75 expectedDocuments []zoekt.Document
76 }
77
78 helloWorld := zoekt.Document{Name: "hello_world.txt", Content: []byte("hello")}
79
80 fruitV1 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("strawberry")}
81 fruitV1InFolder := zoekt.Document{Name: "the_best/best_fruit.txt", Content: fruitV1.Content}
82 fruitV1WithNewName := zoekt.Document{Name: "new_fruit.txt", Content: fruitV1.Content}
83
84 fruitV2 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("grapes")}
85 fruitV2InFolder := zoekt.Document{Name: "the_best/best_fruit.txt", Content: fruitV2.Content}
86
87 fruitV3 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("oranges")}
88 fruitV4 := zoekt.Document{Name: "best_fruit.txt", Content: []byte("apples")}
89
90 foo := zoekt.Document{Name: "foo.txt", Content: []byte("bar")}
91
92 emptySourcegraphIgnore := zoekt.Document{Name: ignore.IgnoreFile}
93 sourcegraphIgnoreWithContent := zoekt.Document{Name: ignore.IgnoreFile, Content: []byte("good_content.txt")}
94
95 for _, test := range []struct {
96 name string
97 branches []string
98 steps []step
99 }{
100 {
101 name: "modification",
102 branches: []string{"main"},
103 steps: []step{
104 {
105 name: "setup",
106 addedDocuments: branchToDocumentMap{
107 "main": []zoekt.Document{helloWorld, fruitV1},
108 },
109
110 expectedDocuments: []zoekt.Document{helloWorld, fruitV1},
111 },
112 {
113 name: "add newer version of fruits",
114 addedDocuments: branchToDocumentMap{
115 "main": []zoekt.Document{fruitV2},
116 },
117 optFn: func(t *testing.T, o *Options) {
118 o.BuildOptions.IsDelta = true
119 },
120
121 expectedDocuments: []zoekt.Document{helloWorld, fruitV2},
122 },
123 },
124 },
125 {
126 name: "modification only inside nested folder",
127 branches: []string{"main"},
128 steps: []step{
129 {
130 name: "setup",
131 addedDocuments: branchToDocumentMap{
132 "main": []zoekt.Document{foo, fruitV1InFolder},
133 },
134
135 expectedDocuments: []zoekt.Document{foo, fruitV1InFolder},
136 },
137 {
138 name: "add newer version of fruits inside folder",
139 addedDocuments: branchToDocumentMap{
140 "main": []zoekt.Document{fruitV2InFolder},
141 },
142 optFn: func(t *testing.T, o *Options) {
143 o.BuildOptions.IsDelta = true
144 },
145
146 expectedDocuments: []zoekt.Document{foo, fruitV2InFolder},
147 },
148 },
149 },
150 {
151 name: "addition",
152 branches: []string{"main"},
153 steps: []step{
154 {
155 name: "setup",
156 addedDocuments: branchToDocumentMap{
157 "main": []zoekt.Document{helloWorld, fruitV1},
158 },
159
160 expectedDocuments: []zoekt.Document{helloWorld, fruitV1},
161 },
162 {
163 name: "add new file - foo",
164 addedDocuments: branchToDocumentMap{
165 "main": []zoekt.Document{foo},
166 },
167 optFn: func(t *testing.T, o *Options) {
168 o.BuildOptions.IsDelta = true
169 },
170
171 expectedDocuments: []zoekt.Document{helloWorld, fruitV1, foo},
172 },
173 },
174 },
175 {
176 name: "deletion",
177 branches: []string{"main"},
178 steps: []step{
179 {
180 name: "setup",
181 addedDocuments: branchToDocumentMap{
182 "main": []zoekt.Document{helloWorld, fruitV1, foo},
183 },
184
185 expectedDocuments: []zoekt.Document{helloWorld, fruitV1, foo},
186 },
187 {
188 name: "delete foo file",
189 addedDocuments: nil,
190 deletedDocuments: branchToDocumentMap{
191 "main": []zoekt.Document{foo},
192 },
193
194 optFn: func(t *testing.T, o *Options) {
195 o.BuildOptions.IsDelta = true
196 },
197
198 expectedDocuments: []zoekt.Document{helloWorld, fruitV1},
199 },
200 },
201 },
202 {
203 name: "addition and deletion on only one branch",
204 branches: []string{"main", "release", "dev"},
205 steps: []step{
206 {
207 name: "setup",
208 addedDocuments: branchToDocumentMap{
209 "main": []zoekt.Document{fruitV1},
210 "release": []zoekt.Document{fruitV2},
211 "dev": []zoekt.Document{fruitV3},
212 },
213
214 expectedDocuments: []zoekt.Document{fruitV1, fruitV2, fruitV3},
215 },
216 {
217 name: "replace fruits v3 with v4 on 'dev', delete fruits on 'main'",
218 addedDocuments: branchToDocumentMap{
219 "dev": []zoekt.Document{fruitV4},
220 },
221 deletedDocuments: branchToDocumentMap{
222 "main": []zoekt.Document{fruitV1},
223 },
224
225 optFn: func(t *testing.T, o *Options) {
226 o.BuildOptions.IsDelta = true
227 },
228
229 expectedDocuments: []zoekt.Document{fruitV2, fruitV4},
230 },
231 },
232 },
233 {
234 name: "rename",
235 branches: []string{"main", "release"},
236 steps: []step{
237 {
238 name: "setup",
239 addedDocuments: branchToDocumentMap{
240 "main": []zoekt.Document{fruitV1},
241 "release": []zoekt.Document{fruitV2},
242 },
243 expectedDocuments: []zoekt.Document{fruitV1, fruitV2},
244 },
245 {
246 name: "rename fruits file on 'main' + ensure that unmodified fruits file on 'release' is still searchable",
247 addedDocuments: branchToDocumentMap{
248 "main": []zoekt.Document{fruitV1WithNewName},
249 },
250 deletedDocuments: branchToDocumentMap{
251 "main": []zoekt.Document{fruitV1},
252 },
253
254 optFn: func(t *testing.T, o *Options) {
255 o.BuildOptions.IsDelta = true
256 },
257
258 expectedDocuments: []zoekt.Document{fruitV1WithNewName, fruitV2},
259 },
260 },
261 },
262 {
263 name: "modification: update one branch with version of document from another branch (a.k.a. Keegan's test)",
264 branches: []string{"main", "dev"},
265 steps: []step{
266 {
267 name: "setup",
268 addedDocuments: branchToDocumentMap{
269 "main": []zoekt.Document{fruitV1},
270 "dev": []zoekt.Document{fruitV2},
271 },
272 expectedDocuments: []zoekt.Document{fruitV1, fruitV2},
273 },
274 {
275 name: "switch main to dev's older version of fruits + bump dev's fruits to new version",
276 addedDocuments: branchToDocumentMap{
277 "main": []zoekt.Document{fruitV2},
278 "dev": []zoekt.Document{fruitV3},
279 },
280
281 optFn: func(t *testing.T, o *Options) {
282 o.BuildOptions.IsDelta = true
283 },
284
285 expectedDocuments: []zoekt.Document{fruitV2, fruitV3},
286 },
287 },
288 },
289 {
290 name: "no-op delta builds (reindexing the same commits)",
291 branches: []string{"main", "dev"},
292 steps: []step{
293 {
294 name: "setup",
295 addedDocuments: branchToDocumentMap{
296 "main": []zoekt.Document{fruitV1, foo},
297 "dev": []zoekt.Document{helloWorld},
298 },
299 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld},
300 },
301 {
302 name: "first no-op (normal build -> delta build)",
303 optFn: func(t *testing.T, o *Options) {
304 o.BuildOptions.IsDelta = true
305 },
306
307 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld},
308 },
309 {
310 name: "second no-op (delta build -> delta build)",
311 optFn: func(t *testing.T, o *Options) {
312 o.BuildOptions.IsDelta = true
313 },
314
315 expectedDocuments: []zoekt.Document{fruitV1, foo, helloWorld},
316 },
317 },
318 },
319 {
320 name: "should fallback to normal build if no prior shards exist",
321 branches: []string{"main"},
322 steps: []step{
323 {
324 name: "attempt delta build on a repository that hasn't been indexed yet",
325 addedDocuments: branchToDocumentMap{
326 "main": []zoekt.Document{helloWorld},
327 },
328 optFn: func(t *testing.T, o *Options) {
329 o.BuildOptions.IsDelta = true
330 },
331
332 expectedFallbackToNormalBuild: true,
333 expectedDocuments: []zoekt.Document{helloWorld},
334 },
335 },
336 },
337 {
338 name: "should fallback to normal build if the set of requested repository branches changes",
339 branches: []string{"main", "release", "dev"},
340 steps: []step{
341 {
342 name: "setup",
343 addedDocuments: branchToDocumentMap{
344 "main": []zoekt.Document{fruitV1},
345 "release": []zoekt.Document{fruitV2},
346 "dev": []zoekt.Document{fruitV3},
347 },
348
349 expectedDocuments: []zoekt.Document{fruitV1, fruitV2, fruitV3},
350 },
351 {
352 name: "try delta build after dropping 'main' branch from index ",
353 addedDocuments: branchToDocumentMap{
354 "release": []zoekt.Document{fruitV4},
355 },
356 optFn: func(t *testing.T, o *Options) {
357 o.Branches = []string{"HEAD", "release", "dev"} // a bit of a hack to override it this way, but it gets the job done
358 o.BuildOptions.IsDelta = true
359 },
360
361 expectedFallbackToNormalBuild: true,
362 expectedDocuments: []zoekt.Document{fruitV3, fruitV4},
363 },
364 },
365 },
366 {
367 name: "should fallback to normal build if one or more index options updates requires a full build",
368 branches: []string{"main"},
369 steps: []step{
370 {
371 name: "setup",
372 addedDocuments: branchToDocumentMap{
373 "main": []zoekt.Document{fruitV1},
374 },
375
376 expectedDocuments: []zoekt.Document{fruitV1},
377 },
378 {
379 name: "try delta build after updating Disable CTags index option",
380 addedDocuments: branchToDocumentMap{
381 "main": []zoekt.Document{fruitV2},
382 },
383 optFn: func(t *testing.T, o *Options) {
384 o.BuildOptions.IsDelta = true
385 o.BuildOptions.DisableCTags = true
386 },
387
388 expectedFallbackToNormalBuild: true,
389 expectedDocuments: []zoekt.Document{fruitV2},
390 },
391 {
392 name: "try delta build after reverting Disable CTags index option",
393 addedDocuments: branchToDocumentMap{
394 "main": []zoekt.Document{fruitV3},
395 },
396 optFn: func(t *testing.T, o *Options) {
397 o.BuildOptions.IsDelta = true
398 o.BuildOptions.DisableCTags = false
399 },
400
401 expectedFallbackToNormalBuild: true,
402 expectedDocuments: []zoekt.Document{fruitV3},
403 },
404 },
405 },
406 {
407 name: "should successfully perform multiple delta builds after disabling symbols",
408 branches: []string{"main"},
409 steps: []step{
410 {
411 name: "setup",
412 addedDocuments: branchToDocumentMap{
413 "main": []zoekt.Document{fruitV1},
414 },
415
416 expectedDocuments: []zoekt.Document{fruitV1},
417 },
418 {
419 name: "try delta build after updating Disable CTags index option",
420 addedDocuments: branchToDocumentMap{
421 "main": []zoekt.Document{fruitV2},
422 },
423 optFn: func(t *testing.T, o *Options) {
424 o.BuildOptions.IsDelta = true
425 o.BuildOptions.DisableCTags = true
426 },
427
428 expectedFallbackToNormalBuild: true,
429 expectedDocuments: []zoekt.Document{fruitV2},
430 },
431 {
432 name: "try another delta build while CTags is still disabled",
433 addedDocuments: branchToDocumentMap{
434 "main": []zoekt.Document{fruitV3},
435 },
436 optFn: func(t *testing.T, o *Options) {
437 o.BuildOptions.IsDelta = true
438 o.BuildOptions.DisableCTags = true
439 },
440
441 expectedDocuments: []zoekt.Document{fruitV3},
442 },
443 },
444 },
445 {
446 name: "should fallback to normal build if repository has unsupported Sourcegraph ignore file",
447 branches: []string{"main"},
448 steps: []step{
449 {
450 name: "setup",
451 addedDocuments: branchToDocumentMap{
452 "main": []zoekt.Document{emptySourcegraphIgnore},
453 },
454
455 expectedDocuments: []zoekt.Document{emptySourcegraphIgnore},
456 },
457 {
458 name: "attempt delta build after modifying ignore file",
459 addedDocuments: branchToDocumentMap{
460 "main": []zoekt.Document{sourcegraphIgnoreWithContent},
461 },
462 optFn: func(t *testing.T, o *Options) {
463 o.BuildOptions.IsDelta = true
464 },
465
466 expectedFallbackToNormalBuild: true,
467 expectedDocuments: []zoekt.Document{sourcegraphIgnoreWithContent},
468 },
469 },
470 },
471 {
472 name: "should fallback to a full, normal build if the repository has more than the specified threshold of shards",
473 branches: []string{"main"},
474 steps: []step{
475 {
476 name: "setup: first shard",
477 addedDocuments: branchToDocumentMap{
478 "main": []zoekt.Document{foo},
479 },
480
481 expectedDocuments: []zoekt.Document{foo},
482 },
483 {
484 name: "setup: second shard (delta)",
485 addedDocuments: branchToDocumentMap{
486 "main": []zoekt.Document{fruitV1},
487 },
488 optFn: func(t *testing.T, o *Options) {
489 o.BuildOptions.IsDelta = true
490 },
491
492 expectedDocuments: []zoekt.Document{foo, fruitV1},
493 },
494 {
495 name: "setup: third shard (delta)",
496 addedDocuments: branchToDocumentMap{
497 "main": []zoekt.Document{helloWorld},
498 },
499 optFn: func(t *testing.T, o *Options) {
500 o.BuildOptions.IsDelta = true
501 },
502
503 expectedDocuments: []zoekt.Document{foo, fruitV1, helloWorld},
504 },
505 {
506 name: "attempt another delta build after we already blew past the shard threshold",
507 addedDocuments: branchToDocumentMap{
508 "main": []zoekt.Document{fruitV2InFolder},
509 },
510 optFn: func(t *testing.T, o *Options) {
511 o.DeltaShardNumberFallbackThreshold = 2
512 o.BuildOptions.IsDelta = true
513 },
514
515 expectedFallbackToNormalBuild: true,
516 expectedDocuments: []zoekt.Document{foo, fruitV1, helloWorld, fruitV2InFolder},
517 },
518 },
519 },
520 } {
521 test := test
522
523 t.Run(test.name, func(t *testing.T) {
524 t.Parallel()
525
526 indexDir := t.TempDir()
527 repositoryDir := t.TempDir()
528
529 // setup: initialize the repository and all of its branches
530 runScript(t, repositoryDir, "git init -b master")
531 runScript(t, repositoryDir, fmt.Sprintf("git config user.email %q", "you@example.com"))
532 runScript(t, repositoryDir, fmt.Sprintf("git config user.name %q", "Your Name"))
533
534 for _, b := range test.branches {
535 runScript(t, repositoryDir, fmt.Sprintf("git checkout -b %q", b))
536 runScript(t, repositoryDir, fmt.Sprintf("git commit --allow-empty -m %q", "empty commit"))
537 }
538
539 for _, step := range test.steps {
540 t.Run(step.name, func(t *testing.T) {
541 for _, b := range test.branches {
542 // setup: for each branch, process any document deletions / additions and commit those changes
543
544 hadChange := false
545
546 runScript(t, repositoryDir, fmt.Sprintf("git checkout %q", b))
547
548 for _, d := range step.deletedDocuments[b] {
549 hadChange = true
550
551 file := filepath.Join(repositoryDir, d.Name)
552
553 err := os.Remove(file)
554 if err != nil {
555 t.Fatalf("deleting file %q: %s", d.Name, err)
556 }
557
558 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file))
559 }
560
561 for _, d := range step.addedDocuments[b] {
562 hadChange = true
563
564 file := filepath.Join(repositoryDir, d.Name)
565
566 err := os.MkdirAll(filepath.Dir(file), 0o755)
567 if err != nil {
568 t.Fatalf("ensuring that folders exist for file %q: %s", file, err)
569 }
570
571 err = os.WriteFile(file, d.Content, 0o644)
572 if err != nil {
573 t.Fatalf("writing file %q: %s", d.Name, err)
574 }
575
576 runScript(t, repositoryDir, fmt.Sprintf("git add %q", file))
577 }
578
579 if !hadChange {
580 continue
581 }
582
583 runScript(t, repositoryDir, fmt.Sprintf("git commit -m %q", step.name))
584 }
585
586 // setup: prepare indexOptions with given overrides
587 buildOptions := build.Options{
588 IndexDir: indexDir,
589 RepositoryDescription: zoekt.Repository{
590 Name: "repository",
591 },
592 IsDelta: false,
593 }
594 buildOptions.SetDefaults()
595
596 branches := append([]string{"HEAD"}, test.branches...)
597
598 options := Options{
599 RepoDir: filepath.Join(repositoryDir, ".git"),
600 BuildOptions: buildOptions,
601 Branches: branches,
602 }
603
604 if step.optFn != nil {
605 step.optFn(t, &options)
606 }
607
608 // setup: prepare spy versions of prepare delta / normal build so that we can observe
609 // whether they were called appropriately
610 deltaBuildCalled := false
611 prepareDeltaSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchVersions map[string]map[string]plumbing.Hash, changedOrDeletedPaths []string, err error) {
612 deltaBuildCalled = true
613 return prepareDeltaBuild(options, repository)
614 }
615
616 normalBuildCalled := false
617 prepareNormalSpy := func(options Options, repository *git.Repository) (repos map[fileKey]BlobLocation, branchVersions map[string]map[string]plumbing.Hash, err error) {
618 normalBuildCalled = true
619 return prepareNormalBuild(options, repository)
620 }
621
622 // run test
623 _, err := indexGitRepo(options, gitIndexConfig{
624 prepareDeltaBuild: prepareDeltaSpy,
625 prepareNormalBuild: prepareNormalSpy,
626 })
627 if err != nil {
628 t.Fatalf("IndexGitRepo: %s", err)
629 }
630
631 if options.BuildOptions.IsDelta != deltaBuildCalled {
632 // We should always try a delta build if we request it in the options.
633 t.Fatalf("expected deltaBuildCalled to be %t, got %t", options.BuildOptions.IsDelta, deltaBuildCalled)
634 }
635
636 if options.BuildOptions.IsDelta && (step.expectedFallbackToNormalBuild != normalBuildCalled) {
637 // We only check the normal spy on delta builds because it's only considered a "fallback" if we
638 // asked for a delta build in the first place.
639 t.Fatalf("expected normalBuildCalled to be %t, got %t", step.expectedFallbackToNormalBuild, normalBuildCalled)
640 }
641
642 // examine outcome: load shards into a searcher instance and run a dummy search query
643 // that returns every document contained in the shards
644 //
645 // then, compare returned set of documents with the expected set for the step and see if they agree
646
647 ss, err := shards.NewDirectorySearcher(indexDir)
648 if err != nil {
649 t.Fatalf("NewDirectorySearcher(%s): %s", indexDir, err)
650 }
651 defer ss.Close()
652
653 searchOpts := &zoekt.SearchOptions{Whole: true}
654 result, err := ss.Search(context.Background(), &query.Const{Value: true}, searchOpts)
655 if err != nil {
656 t.Fatalf("Search: %s", err)
657 }
658
659 var receivedDocuments []zoekt.Document
660 for _, f := range result.Files {
661 receivedDocuments = append(receivedDocuments, zoekt.Document{
662 Name: f.FileName,
663 Content: f.Content,
664 })
665 }
666
667 for _, docs := range [][]zoekt.Document{step.expectedDocuments, receivedDocuments} {
668 sort.Slice(docs, func(i, j int) bool {
669 a, b := docs[i], docs[j]
670
671 // first compare names, then fallback to contents if the names are equal
672
673 if a.Name < b.Name {
674 return true
675 }
676
677 if a.Name > b.Name {
678 return false
679 }
680
681 return bytes.Compare(a.Content, b.Content) < 0
682 })
683 }
684
685 compareOptions := []cmp.Option{
686 cmpopts.IgnoreFields(zoekt.Document{}, "Branches"),
687 cmpopts.EquateEmpty(),
688 }
689
690 if diff := cmp.Diff(step.expectedDocuments, receivedDocuments, compareOptions...); diff != "" {
691 t.Errorf("diff in received documents (-want +got):%s\n:", diff)
692 }
693 })
694 }
695 })
696 }
697}
698
699func TestRepoPathRanks(t *testing.T) {
700 pathRanks := repoPathRanks{
701 Paths: map[string]float64{
702 "search.go": 10.23,
703 "internal/index.go": 5.5,
704 "internal/scratch.go": 0.0,
705 "backend/search_test.go": 2.1,
706 },
707 MeanRank: 3.3,
708 }
709 cases := []struct {
710 name string
711 path string
712 rank float64
713 }{
714 {
715 name: "rank for standard file",
716 path: "search.go",
717 rank: 10.23,
718 },
719 {
720 name: "file with rank 0",
721 path: "internal/scratch.go",
722 rank: 0.0,
723 },
724 {
725 name: "rank for test file",
726 path: "backend/search_test.go",
727 rank: 2.1,
728 },
729 {
730 name: "file with missing rank",
731 path: "internal/docs.md",
732 rank: 3.3,
733 },
734 {
735 name: "test file with missing rank",
736 path: "backend/index_test.go",
737 rank: 0.0,
738 },
739 {
740 name: "third-party file with missing rank",
741 path: "node_modules/search/index.js",
742 rank: 0.0,
743 },
744 }
745
746 for _, tt := range cases {
747 t.Run(tt.name, func(t *testing.T) {
748 got := pathRanks.rank(tt.path, nil)
749 if got != tt.rank {
750 t.Errorf("expected file '%s' to have rank %f, but got %f", tt.path, tt.rank, got)
751 }
752 })
753 }
754}
755
756func runScript(t *testing.T, cwd string, script string) {
757 t.Helper()
758
759 err := os.MkdirAll(cwd, 0o755)
760 if err != nil {
761 t.Fatalf("ensuring path %q exists: %s", cwd, err)
762 }
763
764 cmd := exec.Command("sh", "-euxc", script)
765 cmd.Dir = cwd
766 cmd.Env = append([]string{"GIT_CONFIG_GLOBAL=", "GIT_CONFIG_SYSTEM="}, os.Environ()...)
767
768 if out, err := cmd.CombinedOutput(); err != nil {
769 t.Fatalf("execution error: %v, output %s", err, out)
770 }
771}
772
773func TestSetTemplate(t *testing.T) {
774 repositoryDir := t.TempDir()
775
776 // setup: initialize the repository and all of its branches
777 runScript(t, repositoryDir, "git init -b master")
778 runScript(t, repositoryDir, "git config remote.origin.url git@github.com:sourcegraph/zoekt.git")
779 desc := zoekt.Repository{}
780 if err := setTemplatesFromConfig(&desc, repositoryDir); err != nil {
781 t.Fatalf("setTemplatesFromConfig: %v", err)
782 }
783
784 if got, want := desc.FileURLTemplate, "https://github.com/sourcegraph/zoekt/blob/{{.Version}}/{{.Path}}"; got != want {
785 t.Errorf("got %q, want %q", got, want)
786 }
787}
788
789func BenchmarkPrepareNormalBuild(b *testing.B) {
790 // NOTE: To run the benchmark, download a large repo (like github.com/chromium/chromium/) and change this to its path.
791 repoDir := "/path/to/your/repo"
792 repo, err := git.PlainOpen(repoDir)
793 if err != nil {
794 b.Fatalf("Failed to open test repository: %v", err)
795 }
796
797 opts := Options{
798 RepoDir: repoDir,
799 Submodules: false,
800 BranchPrefix: "refs/heads/",
801 Branches: []string{"main"},
802 BuildOptions: build.Options{
803 RepositoryDescription: zoekt.Repository{
804 Name: "test-repo",
805 URL: "https://github.com/example/test-repo",
806 },
807 },
808 }
809
810 b.ReportAllocs()
811
812 repos, branchVersions, err := prepareNormalBuild(opts, repo)
813 if err != nil {
814 b.Fatalf("prepareNormalBuild failed: %v", err)
815 }
816
817 runtime.GC()
818
819 var m runtime.MemStats
820 runtime.ReadMemStats(&m)
821 b.ReportMetric(float64(m.HeapInuse), "heap-used-bytes")
822 b.ReportMetric(float64(m.HeapInuse), "heap-allocated-bytes")
823
824 if len(repos) == 0 || len(branchVersions) == 0 {
825 b.Fatalf("Unexpected empty results")
826 }
827}