fork of https://github.com/sourcegraph/zoekt
1// Copyright 2016 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 build
16
17import (
18 "context"
19 "math"
20 "os"
21 "testing"
22
23 "github.com/sourcegraph/zoekt"
24 "github.com/sourcegraph/zoekt/ctags"
25 "github.com/sourcegraph/zoekt/query"
26 "github.com/sourcegraph/zoekt/shards"
27)
28
29type scoreCase struct {
30 fileName string
31 content []byte
32 query query.Q
33 language string
34 wantScore float64
35}
36
37func TestFileNameMatch(t *testing.T) {
38 cases := []scoreCase{
39 {
40 fileName: "a/b/c/config.go",
41 query: &query.Substring{FileName: true, Pattern: "config"},
42 language: "Go",
43 // 5500 (partial base at boundary) + 500 (word) + 10 (file order)
44 wantScore: 6010,
45 },
46 {
47 fileName: "a/b/c/config.go",
48 query: &query.Substring{FileName: true, Pattern: "config.go"},
49 language: "Go",
50 // 7000 (full base match) + 500 (word) + 10 (file order)
51 wantScore: 7510,
52 },
53 {
54 fileName: "a/config/c/d.go",
55 query: &query.Substring{FileName: true, Pattern: "config"},
56 language: "Go",
57 // 500 (word) + 10 (file order)
58 wantScore: 510,
59 },
60 }
61
62 for _, c := range cases {
63 checkScoring(t, c, ctags.UniversalCTags)
64 }
65}
66
67func TestJava(t *testing.T) {
68 exampleJava, err := os.ReadFile("./testdata/example.java")
69 if err != nil {
70 t.Fatal(err)
71 }
72
73 cases := []scoreCase{
74 {
75 fileName: "example.java",
76 content: exampleJava,
77 query: &query.Substring{Content: true, Pattern: "nerClass"},
78 language: "Java",
79 // 5500 (partial symbol at boundary) + 1000 (Java class) + 50 (partial word) + 10 (file order)
80 wantScore: 6560,
81 },
82 {
83 fileName: "example.java",
84 content: exampleJava,
85 query: &query.Substring{Content: true, Pattern: "StaticClass"},
86 language: "Java",
87 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word) + 10 (file order)
88 wantScore: 7010,
89 },
90 {
91 fileName: "example.java",
92 content: exampleJava,
93 query: &query.Substring{Content: true, Pattern: "innerEnum"},
94 language: "Java",
95 // 7000 (symbol) + 900 (Java enum) + 500 (word) + 10 (file order)
96 wantScore: 8410,
97 },
98 {
99 fileName: "example.java",
100 content: exampleJava,
101 query: &query.Substring{Content: true, Pattern: "innerInterface"},
102 language: "Java",
103 // 7000 (symbol) + 800 (Java interface) + 500 (word) + 10 (file order)
104 wantScore: 8310,
105 },
106 {
107 fileName: "example.java",
108 content: exampleJava,
109 query: &query.Substring{Content: true, Pattern: "innerMethod"},
110 language: "Java",
111 // 7000 (symbol) + 700 (Java method) + 500 (word) + 10 (file order)
112 wantScore: 8210,
113 },
114 {
115 fileName: "example.java",
116 content: exampleJava,
117 query: &query.Substring{Content: true, Pattern: "field"},
118 language: "Java",
119 // 7000 (symbol) + 600 (Java field) + 500 (word) + 10 (file order)
120 wantScore: 8110,
121 },
122 {
123 fileName: "example.java",
124 content: exampleJava,
125 query: &query.Substring{Content: true, Pattern: "B"},
126 language: "Java",
127 // 7000 (symbol) + 500 (Java enum constant) + 500 (word) + 10 (file order)
128 wantScore: 8010,
129 },
130 // 2 Atoms (1x content and 1x filename)
131 {
132 fileName: "example.java",
133 content: exampleJava,
134 query: &query.Substring{Pattern: "example"}, // matches filename and a Java field
135 language: "Java",
136 // 5500 (edge symbol) + 600 (Java field) + 500 (word) + 200 (atom) + 10 (file order)
137 wantScore: 6810,
138 },
139 // 3 Atoms (2x content, 1x filename)
140 {
141 fileName: "example.java",
142 content: exampleJava,
143 query: &query.Or{Children: []query.Q{
144 &query.Substring{Pattern: "example"}, // matches filename and Java field
145 &query.Substring{Content: true, Pattern: "runInnerInterface"}, // matches a Java method
146 }},
147 language: "Java",
148 // 7000 (symbol) + 700 (Java method) + 500 (word) + 266.67 (atom) + 10 (file order)
149 wantScore: 8476.667,
150 },
151 // 4 Atoms (4x content)
152 {
153 fileName: "example.java",
154 content: exampleJava,
155 query: &query.Or{Children: []query.Q{
156 &query.Substring{Content: true, Pattern: "testAnon"},
157 &query.Substring{Content: true, Pattern: "Override"},
158 &query.Substring{Content: true, Pattern: "InnerEnum"},
159 &query.Substring{Content: true, Pattern: "app"},
160 }},
161 language: "Java",
162 // 7000 (symbol) + 900 (Java enum) + 500 (word) + 300 (atom) + 10 (file order)
163 wantScore: 8710,
164 },
165 {
166 fileName: "example.java",
167 content: exampleJava,
168 query: &query.Substring{Content: true, Pattern: "unInnerInterface("},
169 language: "Java",
170 // 4000 (overlap Symbol) + 700 (Java method) + 50 (partial word) + 10 (file order)
171 wantScore: 4760,
172 },
173 {
174 fileName: "example.java",
175 content: exampleJava,
176 query: &query.Substring{Content: true, Pattern: "InnerEnum"},
177 language: "Java",
178 // 7000 (Symbol) + 900 (Java enum) + 500 (word) + 10 (file order)
179 wantScore: 8410,
180 },
181 {
182 fileName: "example.java",
183 content: exampleJava,
184 query: &query.Substring{Content: true, Pattern: "enum InnerEnum"},
185 language: "Java",
186 // 5500 (edge Symbol) + 900 (Java enum) + 500 (word) + 10 (file order)
187 wantScore: 6910,
188 },
189 {
190 fileName: "example.java",
191 content: exampleJava,
192 query: &query.Substring{Content: true, Pattern: "public enum InnerEnum {"},
193 language: "Java",
194 // 4000 (overlap Symbol) + 900 (Java enum) + 500 (word) + 10 (file order)
195 wantScore: 5410,
196 },
197 }
198
199 for _, c := range cases {
200 checkScoring(t, c, ctags.UniversalCTags)
201 }
202}
203
204func TestKotlin(t *testing.T) {
205 exampleKotlin, err := os.ReadFile("./testdata/example.kt")
206 if err != nil {
207 t.Fatal(err)
208 }
209
210 cases := []scoreCase{
211 {
212 fileName: "example.kt",
213 content: exampleKotlin,
214 query: &query.Substring{Content: true, Pattern: "oxyPreloader"},
215 language: "Kotlin",
216 // 5500 (partial symbol at boundary) + 1000 (Kotlin class) + 50 (partial word) + 10 (file order)
217 wantScore: 6560,
218 },
219 {
220 fileName: "example.kt",
221 content: exampleKotlin,
222 query: &query.Substring{Content: true, Pattern: "ViewMetadata"},
223 language: "Kotlin",
224 // 7000 (symbol) + 900 (Kotlin interface) + 500 (word) + 10 (file order)
225 wantScore: 8410,
226 },
227 {
228 fileName: "example.kt",
229 content: exampleKotlin,
230 query: &query.Substring{Content: true, Pattern: "onScrolled"},
231 language: "Kotlin",
232 // 7000 (symbol) + 800 (Kotlin method) + 500 (word) + 10 (file order)
233 wantScore: 8310,
234 },
235 {
236 fileName: "example.kt",
237 content: exampleKotlin,
238 query: &query.Substring{Content: true, Pattern: "PreloadErrorHandler"},
239 language: "Kotlin",
240 // 7000 (symbol) + 700 (Kotlin typealias) + 500 (word) + 10 (file order)
241 wantScore: 8210,
242 },
243 {
244 fileName: "example.kt",
245 content: exampleKotlin,
246 query: &query.Substring{Content: true, Pattern: "FLING_THRESHOLD_PX"},
247 language: "Kotlin",
248 // 7000 (symbol) + 600 (Kotlin constant) + 500 (word) + 10 (file order)
249 wantScore: 8110,
250 },
251 {
252 fileName: "example.kt",
253 content: exampleKotlin,
254 query: &query.Substring{Content: true, Pattern: "scrollState"},
255 language: "Kotlin",
256 // 7000 (symbol) + 500 (Kotlin variable) + 500 (word) + 10 (file order)
257 wantScore: 8010,
258 },
259 }
260
261 parserType := ctags.UniversalCTags
262 for _, c := range cases {
263 t.Run(c.language, func(t *testing.T) {
264 checkScoring(t, c, parserType)
265 })
266 }
267}
268
269func TestCpp(t *testing.T) {
270 exampleCpp, err := os.ReadFile("./testdata/example.cc")
271 if err != nil {
272 t.Fatal(err)
273 }
274
275 cases := []scoreCase{
276 {
277 fileName: "example.cc",
278 content: exampleCpp,
279 query: &query.Substring{Content: true, Pattern: "FooClass"},
280 language: "C++",
281 // 7000 (Symbol) + 1000 (C++ class) + 500 (full word) + 10 (file order)
282 wantScore: 8510,
283 },
284 {
285 fileName: "example.cc",
286 content: exampleCpp,
287 query: &query.Substring{Content: true, Pattern: "NestedEnum"},
288 language: "C++",
289 // 7000 (Symbol) + 900 (C++ enum) + 500 (full word) + 10 (file order)
290 wantScore: 8410,
291 },
292 {
293 fileName: "example.cc",
294 content: exampleCpp,
295 query: &query.Substring{Content: true, Pattern: "main"},
296 language: "C++",
297 // 7000 (Symbol) + 800 (C++ function) + 500 (full word) + 10 (file order)
298 wantScore: 8310,
299 },
300 {
301 fileName: "example.cc",
302 content: exampleCpp,
303 query: &query.Substring{Content: true, Pattern: "FooStruct"},
304 language: "C++",
305 // 7000 (Symbol) + 700 (C++ struct) + 500 (full word) + 10 (file order)
306 wantScore: 8210,
307 },
308 {
309 fileName: "example.cc",
310 content: exampleCpp,
311 query: &query.Substring{Content: true, Pattern: "TheUnion"},
312 language: "C++",
313 // 7000 (Symbol) + 600 (C++ union) + 500 (full word) + 10 (file order)
314 wantScore: 8110,
315 },
316 }
317
318 parserType := ctags.UniversalCTags
319 for _, c := range cases {
320 t.Run(c.language, func(t *testing.T) {
321 checkScoring(t, c, parserType)
322 })
323 }
324}
325
326func TestPython(t *testing.T) {
327 examplePython, err := os.ReadFile("./testdata/example.py")
328 if err != nil {
329 t.Fatal(err)
330 }
331
332 cases := []scoreCase{
333 {
334 fileName: "example.py",
335 content: examplePython,
336 query: &query.Substring{Content: true, Pattern: "C1"},
337 language: "Python",
338 // 7000 (symbol) + 1000 (Python class) + 500 (word) + 10 (file order)
339 wantScore: 8510,
340 },
341 {
342 fileName: "example.py",
343 content: examplePython,
344 query: &query.Substring{Content: true, Pattern: "g"},
345 language: "Python",
346 // 7000 (symbol) + 800 (Python function) + 500 (word) + 10 (file order)
347 wantScore: 8310,
348 },
349 }
350
351 for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} {
352 for _, c := range cases {
353 checkScoring(t, c, parserType)
354 }
355 }
356
357 // Only test SCIP, as universal-ctags doesn't correctly recognize this as a method
358 scipOnlyCase := scoreCase{
359 fileName: "example.py",
360 content: examplePython,
361 query: &query.Substring{Content: true, Pattern: "__init__"},
362 language: "Python",
363 // 7000 (symbol) + 800 (Python method) + 50 (partial word) + 10 (file order)
364 wantScore: 7860,
365 }
366
367 checkScoring(t, scipOnlyCase, ctags.ScipCTags)
368}
369
370func TestRuby(t *testing.T) {
371 exampleRuby, err := os.ReadFile("./testdata/example.rb")
372 if err != nil {
373 t.Fatal(err)
374 }
375
376 cases := []scoreCase{
377 {
378 fileName: "example.rb",
379 content: exampleRuby,
380 query: &query.Substring{Content: true, Pattern: "Parental"},
381 language: "Ruby",
382 // 7000 (symbol) + 1000 (Ruby class) + 500 (word) + 10 (file order)
383 wantScore: 8510,
384 },
385 {
386 fileName: "example.rb",
387 content: exampleRuby,
388 query: &query.Substring{Content: true, Pattern: "parental_func"},
389 language: "Ruby",
390 // 7000 (symbol) + 900 (Ruby method) + 500 (word) + 10 (file order)
391 wantScore: 8410,
392 },
393 {
394 fileName: "example.rb",
395 content: exampleRuby,
396 query: &query.Substring{Content: true, Pattern: "MyModule"},
397 language: "Ruby",
398 // 7000 (symbol) + 500 (Ruby module) + 500 (word) + 10 (file order)
399 wantScore: 8210,
400 },
401 }
402
403 for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} {
404 for _, c := range cases {
405 checkScoring(t, c, parserType)
406 }
407 }
408}
409
410func TestScala(t *testing.T) {
411 exampleScala, err := os.ReadFile("./testdata/example.scala")
412 if err != nil {
413 t.Fatal(err)
414 }
415
416 cases := []scoreCase{
417 {
418 fileName: "example.scala",
419 content: exampleScala,
420 query: &query.Substring{Content: true, Pattern: "SymbolIndexBucket"},
421 language: "Scala",
422 // 7000 (symbol) + 1000 (Scala class) + 500 (word) + 10 (file order)
423 wantScore: 8510,
424 },
425 {
426 fileName: "example.scala",
427 content: exampleScala,
428 query: &query.Substring{Content: true, Pattern: "stdLibPatches"},
429 language: "Scala",
430 // 7000 (symbol) + 800 (Scala object) + 500 (word) + 10 (file order)
431 wantScore: 8310,
432 },
433 {
434 fileName: "example.scala",
435 content: exampleScala,
436 query: &query.Substring{Content: true, Pattern: "close"},
437 language: "Scala",
438 // 7000 (symbol) + 700 (Scala method) + 500 (word) + 10 (file order)
439 wantScore: 8210,
440 },
441 {
442 fileName: "example.scala",
443 content: exampleScala,
444 query: &query.Substring{Content: true, Pattern: "javaSymbol"},
445 language: "Scala",
446 // 7000 (symbol) + 500 (Scala method) + 500 (word) + 10 (file order)
447 wantScore: 8010,
448 },
449 }
450
451 parserType := ctags.UniversalCTags
452 for _, c := range cases {
453 checkScoring(t, c, parserType)
454 }
455}
456
457func TestGo(t *testing.T) {
458 cases := []scoreCase{
459 {
460 fileName: "src/net/http/client.go",
461 content: []byte(`
462package http
463type aInterface interface {}
464`),
465 query: &query.Substring{Content: true, Pattern: "aInterface"},
466 language: "Go",
467 // 7000 (full base match) + 1000 (Go interface) + 500 (word) + 10 (file order)
468 wantScore: 8510,
469 },
470 {
471 fileName: "src/net/http/client.go",
472 content: []byte(`
473package http
474type aStruct struct {}
475`),
476 query: &query.Substring{Content: true, Pattern: "aStruct"},
477 language: "Go",
478 // 7000 (full base match) + 900 (Go struct) + 500 (word) + 10 (file order)
479 wantScore: 8410,
480 },
481 {
482 fileName: "src/net/http/client.go",
483 content: []byte(`
484package http
485func aFunc() bool {}
486`),
487 query: &query.Substring{Content: true, Pattern: "aFunc"},
488 language: "Go",
489 // 7000 (full base match) + 800 (Go function) + 500 (word) + 10 (file order)
490 wantScore: 8310,
491 },
492 {
493 fileName: "src/net/http/client.go",
494 content: []byte(`
495package http
496func Get() {
497 panic("")
498}
499`),
500 query: &query.And{Children: []query.Q{
501 &query.Symbol{Expr: &query.Substring{Pattern: "http", Content: true}},
502 &query.Symbol{Expr: &query.Substring{Pattern: "Get", Content: true}},
503 }},
504 language: "Go",
505 // 7000 (full base match) + 800 (Go func) + 50 (Exported Go) + 500 (word) + 200 (atom) + 10 (file order)
506 wantScore: 8560,
507 },
508 }
509
510 for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} {
511 for _, c := range cases {
512 checkScoring(t, c, parserType)
513 }
514 }
515}
516
517func skipIfCTagsUnavailable(t *testing.T, parserType ctags.CTagsParserType) {
518 // Never skip universal-ctags tests in CI
519 if os.Getenv("CI") != "" && parserType == ctags.UniversalCTags {
520 return
521 }
522
523 switch parserType {
524 case ctags.UniversalCTags:
525 requireCTags(t)
526 case ctags.ScipCTags:
527 if checkScipCTags() == "" {
528 t.Skip("scip-ctags not available")
529 }
530 default:
531 t.Fatalf("unexpected parser type")
532 }
533}
534
535func checkScoring(t *testing.T, c scoreCase, parserType ctags.CTagsParserType) {
536 skipIfCTagsUnavailable(t, parserType)
537
538 name := c.language
539 if parserType == ctags.ScipCTags {
540 name += "-scip"
541 }
542
543 t.Run(name, func(t *testing.T) {
544 dir := t.TempDir()
545
546 opts := Options{
547 IndexDir: dir,
548 RepositoryDescription: zoekt.Repository{
549 Name: "repo",
550 },
551 LanguageMap: ctags.LanguageMap{
552 normalizeLanguage(c.language): parserType,
553 },
554 }
555
556 epsilon := 0.01
557
558 b, err := NewBuilder(opts)
559 if err != nil {
560 t.Fatalf("NewBuilder: %v", err)
561 }
562 if err := b.AddFile(c.fileName, c.content); err != nil {
563 t.Fatal(err)
564 }
565 if err := b.Finish(); err != nil {
566 t.Fatalf("Finish: %v", err)
567 }
568
569 ss, err := shards.NewDirectorySearcher(dir)
570 if err != nil {
571 t.Fatalf("NewDirectorySearcher(%s): %v", dir, err)
572 }
573 defer ss.Close()
574
575 srs, err := ss.Search(context.Background(), c.query, &zoekt.SearchOptions{DebugScore: true})
576 if err != nil {
577 t.Fatal(err)
578 }
579
580 if got, want := len(srs.Files), 1; got != want {
581 t.Fatalf("file matches: want %d, got %d", want, got)
582 }
583
584 if got := srs.Files[0].Score; math.Abs(got-c.wantScore) > epsilon {
585 t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore)
586 }
587
588 if got := srs.Files[0].Language; got != c.language {
589 t.Fatalf("want %s, got %s", c.language, got)
590 }
591 })
592}
593
594func TestDocumentRanks(t *testing.T) {
595 requireCTags(t)
596 dir := t.TempDir()
597
598 opts := Options{
599 IndexDir: dir,
600 RepositoryDescription: zoekt.Repository{
601 Name: "repo",
602 },
603 DocumentRanksVersion: "ranking",
604 }
605
606 searchQuery := &query.Substring{Content: true, Pattern: "Inner"}
607 exampleJava, err := os.ReadFile("./testdata/example.java")
608 if err != nil {
609 t.Fatal(err)
610 }
611
612 cases := []struct {
613 name string
614 documentRank float64
615 documentRanksWeight float64
616 wantScore float64
617 }{
618 {
619 name: "score with no document ranks",
620 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order)
621 wantScore: 7010.00,
622 },
623 {
624 name: "score with document ranks",
625 documentRank: 0.8,
626 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 225 (file rank) + 10 (file order)
627 wantScore: 7235.00,
628 },
629 {
630 name: "score with custom document ranks weight",
631 documentRank: 0.8,
632 documentRanksWeight: 1000.0,
633 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 25.00 (file rank) + 10 (file order)
634 wantScore: 7035.00,
635 },
636 }
637
638 for _, c := range cases {
639 t.Run(c.name, func(t *testing.T) {
640 b, err := NewBuilder(opts)
641 if err != nil {
642 t.Fatalf("NewBuilder: %v", err)
643 }
644
645 err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava, Ranks: []float64{c.documentRank}})
646 if err != nil {
647 t.Fatal(err)
648 }
649
650 if err := b.Finish(); err != nil {
651 t.Fatalf("Finish: %v", err)
652 }
653
654 ss, err := shards.NewDirectorySearcher(dir)
655 if err != nil {
656 t.Fatalf("NewDirectorySearcher(%s): %v", dir, err)
657 }
658 defer ss.Close()
659
660 srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{
661 UseDocumentRanks: true,
662 DocumentRanksWeight: c.documentRanksWeight,
663 DebugScore: true,
664 })
665 if err != nil {
666 t.Fatal(err)
667 }
668
669 if got, want := len(srs.Files), 1; got != want {
670 t.Fatalf("file matches: want %d, got %d", want, got)
671 }
672
673 if got := srs.Files[0].Score; got != c.wantScore {
674 t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore)
675 }
676 })
677 }
678}
679
680func TestRepoRanks(t *testing.T) {
681 requireCTags(t)
682 dir := t.TempDir()
683
684 opts := Options{
685 IndexDir: dir,
686 RepositoryDescription: zoekt.Repository{
687 Name: "repo",
688 },
689 DocumentRanksVersion: "ranking",
690 }
691
692 searchQuery := &query.Substring{Content: true, Pattern: "Inner"}
693 exampleJava, err := os.ReadFile("./testdata/example.java")
694 if err != nil {
695 t.Fatal(err)
696 }
697
698 cases := []struct {
699 name string
700 repoRank uint16
701 wantScore float64
702 }{
703 {
704 name: "no shard rank",
705 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order)
706 wantScore: 7010.00,
707 },
708 {
709 name: "medium shard rank",
710 repoRank: 30000,
711 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 9.16 (repo rank)
712 wantScore: 7019.16,
713 },
714 {
715 name: "high shard rank",
716 repoRank: 60000,
717 // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 18.31 (repo rank)
718 wantScore: 7028.31,
719 },
720 }
721
722 for _, c := range cases {
723 t.Run(c.name, func(t *testing.T) {
724 opts.RepositoryDescription = zoekt.Repository{
725 Name: "repo",
726 Rank: c.repoRank,
727 }
728
729 b, err := NewBuilder(opts)
730 if err != nil {
731 t.Fatalf("NewBuilder: %v", err)
732 }
733
734 err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava})
735 if err != nil {
736 t.Fatal(err)
737 }
738
739 if err := b.Finish(); err != nil {
740 t.Fatalf("Finish: %v", err)
741 }
742
743 ss, err := shards.NewDirectorySearcher(dir)
744 if err != nil {
745 t.Fatalf("NewDirectorySearcher(%s): %v", dir, err)
746 }
747 defer ss.Close()
748
749 srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{
750 UseDocumentRanks: true,
751 DebugScore: true,
752 })
753 if err != nil {
754 t.Fatal(err)
755 }
756
757 if got, want := len(srs.Files), 1; got != want {
758 t.Fatalf("file matches: want %d, got %d", want, got)
759 }
760
761 if got := srs.Files[0].Score; math.Abs(got-c.wantScore) >= 0.01 {
762 t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore)
763 }
764 })
765 }
766}