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