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} 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}