fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

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