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 web 16 17import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 "log" 24 "net/http" 25 "net/http/httptest" 26 "reflect" 27 "sort" 28 "strings" 29 "testing" 30 "time" 31 32 "github.com/google/go-cmp/cmp" 33 34 "github.com/sourcegraph/zoekt" 35 "github.com/sourcegraph/zoekt/query" 36 "github.com/sourcegraph/zoekt/rpc" 37 "github.com/sourcegraph/zoekt/stream" 38) 39 40// TODO(hanwen): cut & paste from ../ . Should create internal test 41// util package. 42type memSeeker struct { 43 data []byte 44} 45 46func (s *memSeeker) Close() {} 47func (s *memSeeker) Read(off, sz uint32) ([]byte, error) { 48 return s.data[off : off+sz], nil 49} 50 51func (s *memSeeker) Size() (uint32, error) { 52 return uint32(len(s.data)), nil 53} 54 55func (s *memSeeker) Name() string { 56 return "memSeeker" 57} 58 59func searcherForTest(t *testing.T, b *zoekt.IndexBuilder) zoekt.Streamer { 60 var buf bytes.Buffer 61 if err := b.Write(&buf); err != nil { 62 t.Fatal(err) 63 } 64 f := &memSeeker{buf.Bytes()} 65 66 searcher, err := zoekt.NewSearcher(f) 67 if err != nil { 68 t.Fatalf("NewSearcher: %v", err) 69 } 70 71 return adapter{Searcher: searcher} 72} 73 74type adapter struct { 75 zoekt.Searcher 76} 77 78func (a adapter) StreamSearch(ctx context.Context, q query.Q, opts *zoekt.SearchOptions, sender zoekt.Sender) (err error) { 79 sr, err := a.Searcher.Search(ctx, q, opts) 80 if err != nil { 81 return err 82 } 83 sender.Send(sr) 84 return nil 85} 86 87func TestBasic(t *testing.T) { 88 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 89 Name: "name", 90 URL: "repo-url", 91 CommitURLTemplate: "{{.Version}}", 92 FileURLTemplate: "file-url", 93 LineFragmentTemplate: "#line", 94 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 95 }) 96 if err != nil { 97 t.Fatalf("NewIndexBuilder: %v", err) 98 } 99 if err := b.Add(zoekt.Document{ 100 Name: "f2", 101 Content: []byte("to carry water in the no later bla"), 102 // --------------0123456789012345678901234567890123 103 // --------------0 1 2 3 104 Branches: []string{"master"}, 105 }); err != nil { 106 t.Fatalf("Add: %v", err) 107 } 108 109 s := searcherForTest(t, b) 110 srv := Server{ 111 Searcher: s, 112 Top: Top, 113 HTML: true, 114 } 115 116 mux, err := NewMux(&srv) 117 if err != nil { 118 t.Fatalf("NewMux: %v", err) 119 } 120 121 ts := httptest.NewServer(mux) 122 defer ts.Close() 123 124 nowStr := time.Now().UTC().Format("Jan 02, 2006 15:04") 125 for req, needles := range map[string][]string{ 126 "/": {"from 1 repositories"}, 127 "/search?q=water": { 128 "href=\"file-url#line", 129 "carry <b>water</b>", 130 }, 131 "/search?q=r:": { 132 "1234\">master", 133 "Found 1 repositories", 134 nowStr, 135 "repo-url\">name", 136 "1 files (36B)", 137 }, 138 "/search?q=magic": { 139 `value=magic`, 140 }, 141 "/search?q=foo+type:file": { 142 `value=foo`, 143 }, 144 "/robots.txt": { 145 "disallow: /search", 146 }, 147 } { 148 checkNeedles(t, ts, req, needles) 149 } 150} 151 152func TestPrint(t *testing.T) { 153 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 154 Name: "name", 155 URL: "repo-url", 156 CommitURLTemplate: "{{.Version}}", 157 FileURLTemplate: "file-url", 158 LineFragmentTemplate: "line", 159 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 160 }) 161 if err != nil { 162 t.Fatalf("NewIndexBuilder: %v", err) 163 } 164 if err := b.Add(zoekt.Document{ 165 Name: "f2", 166 Content: []byte("to carry water in the no later bla"), 167 Branches: []string{"master"}, 168 }); err != nil { 169 t.Fatalf("Add: %v", err) 170 } 171 172 if err := b.Add(zoekt.Document{ 173 Name: "dir/f2", 174 Content: []byte("blabla"), 175 Branches: []string{"master"}, 176 }); err != nil { 177 t.Fatalf("Add: %v", err) 178 } 179 180 s := searcherForTest(t, b) 181 srv := Server{ 182 Searcher: s, 183 Top: Top, 184 HTML: true, 185 Print: true, 186 } 187 188 mux, err := NewMux(&srv) 189 if err != nil { 190 t.Fatalf("NewMux: %v", err) 191 } 192 193 ts := httptest.NewServer(mux) 194 defer ts.Close() 195 196 for req, needles := range map[string][]string{ 197 "/print?q=bla&r=name&f=f2": { 198 `pre id="l1" class="inline-pre"><span class="noselect"><a href="#l1">`, 199 }, 200 } { 201 checkNeedles(t, ts, req, needles) 202 } 203} 204 205func TestPrintDefault(t *testing.T) { 206 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 207 Name: "name", 208 URL: "repo-url", 209 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 210 }) 211 if err != nil { 212 t.Fatalf("NewIndexBuilder: %v", err) 213 } 214 if err := b.Add(zoekt.Document{ 215 Name: "f2", 216 Content: []byte("to carry water in the no later bla"), 217 Branches: []string{"master"}, 218 }); err != nil { 219 t.Fatalf("Add: %v", err) 220 } 221 s := searcherForTest(t, b) 222 srv := Server{ 223 Searcher: s, 224 Top: Top, 225 HTML: true, 226 } 227 228 mux, err := NewMux(&srv) 229 if err != nil { 230 t.Fatalf("NewMux: %v", err) 231 } 232 233 ts := httptest.NewServer(mux) 234 defer ts.Close() 235 236 for req, needles := range map[string][]string{ 237 "/search?q=water": { 238 `href="print?`, 239 }, 240 } { 241 checkNeedles(t, ts, req, needles) 242 } 243} 244 245func checkNeedles(t *testing.T, ts *httptest.Server, req string, needles []string) { 246 res, err := http.Get(ts.URL + req) 247 if err != nil { 248 t.Fatal(err) 249 } 250 resultBytes, err := io.ReadAll(res.Body) 251 res.Body.Close() 252 if err != nil { 253 log.Fatal(err) 254 } 255 256 result := string(resultBytes) 257 for _, want := range needles { 258 if !strings.Contains(result, want) { 259 t.Errorf("query %q: result did not have %q: %s", req, want, result) 260 } 261 } 262 if notWant := "crashed"; strings.Contains(result, notWant) { 263 t.Errorf("result has %q: %s", notWant, result) 264 } 265 if notWant := "bytes skipped)..."; strings.Contains(result, notWant) { 266 t.Errorf("result has %q: %s", notWant, result) 267 } 268} 269 270type Expectation struct { 271 title string 272 fileMatch FileMatch 273} 274 275func TestFormatJson(t *testing.T) { 276 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 277 Name: "name", 278 URL: "repo-url", 279 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 280 }) 281 if err != nil { 282 t.Fatalf("NewIndexBuilder: %v", err) 283 } 284 if err := b.Add(zoekt.Document{ 285 Name: "f2", 286 Content: []byte("to carry water in the no later bla"), 287 Branches: []string{"master"}, 288 }); err != nil { 289 t.Fatalf("Add: %v", err) 290 } 291 s := searcherForTest(t, b) 292 srv := Server{ 293 Searcher: s, 294 Top: Top, 295 HTML: true, 296 } 297 298 mux, err := NewMux(&srv) 299 if err != nil { 300 t.Fatalf("NewMux: %v", err) 301 } 302 303 ts := httptest.NewServer(mux) 304 defer ts.Close() 305 306 expected := Expectation{ 307 "json basic test", 308 FileMatch{ 309 FileName: "f2", 310 Repo: "name", 311 Matches: []Match{ 312 { 313 FileName: "f2", 314 LineNum: 1, 315 Fragments: []Fragment{ 316 { 317 Pre: "to carry ", 318 Match: "water", 319 Post: " in the no later bla", 320 }, 321 }, 322 }, 323 }, 324 }, 325 } 326 327 checkResultMatches(t, ts, "/search?q=water&format=json", expected) 328} 329 330func TestContextLines(t *testing.T) { 331 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 332 Name: "name", 333 URL: "repo-url", 334 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 335 }) 336 if err != nil { 337 t.Fatalf("NewIndexBuilder: %v", err) 338 } 339 if err := b.Add(zoekt.Document{ 340 Name: "f2", 341 Content: []byte("one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh"), 342 Branches: []string{"master"}, 343 }); err != nil { 344 t.Fatalf("Add: %v", err) 345 } 346 if err := b.Add(zoekt.Document{ 347 Name: "f3", 348 Content: []byte("\n\n\n\nto carry water in the no later bla\n\n\n\n"), 349 Branches: []string{"master"}, 350 }); err != nil { 351 t.Fatalf("Add: %v", err) 352 } 353 if err := b.Add(zoekt.Document{ 354 Name: "f4", 355 Content: []byte("un \n \n\ttrois\n \n\nsix\n "), 356 Branches: []string{"master"}, 357 }); err != nil { 358 t.Fatalf("Add: %v", err) 359 } 360 if err := b.Add(zoekt.Document{ 361 Name: "f5", 362 Content: []byte("\ngreen\npastures\n\nhere"), 363 Branches: []string{"master"}, 364 }); err != nil { 365 t.Fatalf("Add: %v", err) 366 } 367 s := searcherForTest(t, b) 368 srv := Server{ 369 Searcher: s, 370 Top: Top, 371 HTML: true, 372 } 373 374 mux, err := NewMux(&srv) 375 if err != nil { 376 t.Fatalf("NewMux: %v", err) 377 } 378 379 ts := httptest.NewServer(mux) 380 defer ts.Close() 381 382 for req, expected := range map[string]Expectation{ 383 "/search?q=our&format=json&ctx=0": { 384 "no context doesn't return Before or After", 385 FileMatch{ 386 FileName: "f2", 387 Repo: "name", 388 Matches: []Match{ 389 { 390 FileName: "f2", 391 LineNum: 4, 392 Fragments: []Fragment{ 393 { 394 Pre: "f", 395 Match: "our", 396 Post: "th", 397 }, 398 }, 399 }, 400 }, 401 }, 402 }, 403 "/search?q=f:f2&format=json&ctx=2": { 404 "filename does not return Before or After", 405 FileMatch{ 406 FileName: "f2", 407 Repo: "name", 408 Matches: []Match{ 409 { 410 FileName: "f2", 411 LineNum: 0, 412 Fragments: []Fragment{ 413 { 414 Match: "f2", 415 }, 416 }, 417 }, 418 }, 419 }, 420 }, 421 "/search?q=our&format=json&ctx=2": { 422 "context returns Before and After", 423 FileMatch{ 424 FileName: "f2", 425 Repo: "name", 426 Matches: []Match{ 427 { 428 FileName: "f2", 429 LineNum: 4, 430 Fragments: []Fragment{ 431 { 432 Pre: "f", 433 Match: "our", 434 Post: "th", 435 }, 436 }, 437 Before: "second snippet\nthird thing", 438 After: "fifth block\nsixth example", 439 }, 440 }, 441 }, 442 }, 443 "/search?q=one&format=json&ctx=2": { 444 "match at start returns After but no Before", 445 FileMatch{ 446 FileName: "f2", 447 Repo: "name", 448 Matches: []Match{ 449 { 450 FileName: "f2", 451 LineNum: 1, 452 Fragments: []Fragment{ 453 { 454 Pre: "", 455 Match: "one", 456 Post: " line", 457 }, 458 }, 459 After: "second snippet\nthird thing", 460 }, 461 }, 462 }, 463 }, 464 "/search?q=seventh&format=json&ctx=2": { 465 "match at end returns Before but no After", 466 FileMatch{ 467 FileName: "f2", 468 Repo: "name", 469 Matches: []Match{ 470 { 471 FileName: "f2", 472 LineNum: 7, 473 Fragments: []Fragment{ 474 { 475 Pre: "", 476 Match: "seventh", 477 Post: "", 478 }, 479 }, 480 Before: "fifth block\nsixth example", 481 }, 482 }, 483 }, 484 }, 485 "/search?q=seventh&format=json&ctx=10": { 486 "match with large context at end returns whole document", 487 FileMatch{ 488 FileName: "f2", 489 Repo: "name", 490 Matches: []Match{ 491 { 492 FileName: "f2", 493 LineNum: 7, 494 Fragments: []Fragment{ 495 { 496 Pre: "", 497 Match: "seventh", 498 Post: "", 499 }, 500 }, 501 Before: "one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example", 502 }, 503 }, 504 }, 505 }, 506 "/search?q=one&format=json&ctx=10": { 507 "match with large context at start returns whole document", 508 FileMatch{ 509 FileName: "f2", 510 Repo: "name", 511 Matches: []Match{ 512 { 513 FileName: "f2", 514 LineNum: 1, 515 Fragments: []Fragment{ 516 { 517 Pre: "", 518 Match: "one", 519 Post: " line", 520 }, 521 }, 522 After: "second snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh", 523 }, 524 }, 525 }, 526 }, 527 "/search?q=trois&format=json&ctx=2": { 528 "context returns whitespaces lines", 529 FileMatch{ 530 FileName: "f4", 531 Repo: "name", 532 Matches: []Match{ 533 { 534 FileName: "f4", 535 LineNum: 3, 536 Fragments: []Fragment{ 537 { 538 Pre: "\t", 539 Match: "trois", 540 }, 541 }, 542 Before: "un \n ", 543 After: " \n", 544 }, 545 }, 546 }, 547 }, 548 "/search?q=water&format=json&ctx=4": { 549 "context returns new lines", 550 FileMatch{ 551 FileName: "f3", 552 Repo: "name", 553 Matches: []Match{ 554 { 555 FileName: "f3", 556 LineNum: 5, 557 Fragments: []Fragment{ 558 { 559 Pre: "to carry ", 560 Match: "water", 561 Post: " in the no later bla", 562 }, 563 }, 564 // Returns 3 instead of 4 new line characters since we swallow 565 // the last new line in Before, Fragments and After. 566 Before: "\n\n\n", 567 // Returns 2 instead of 3 new line characters since a 568 // trailing newline at the end of the file does not 569 // constitue a new line. 570 After: "\n\n", 571 }, 572 }, 573 }, 574 }, 575 "/search?q=pastures&format=json&ctx=1": { 576 "context returns empty end line", 577 FileMatch{ 578 FileName: "f5", 579 Repo: "name", 580 Matches: []Match{ 581 { 582 FileName: "f5", 583 LineNum: 3, 584 Fragments: []Fragment{ 585 { 586 Pre: "", 587 Match: "pastures", 588 }, 589 }, 590 Before: "green", 591 After: "", 592 }, 593 }, 594 }, 595 }, 596 } { 597 checkResultMatches(t, ts, req, expected) 598 } 599} 600 601func matchesPartiallyEqual(a, b []Match) bool { 602 if len(a) != len(b) { 603 return false 604 } 605 for i := range a { 606 if a[i].FileName != b[i].FileName { 607 return false 608 } 609 if a[i].LineNum != b[i].LineNum { 610 return false 611 } 612 if !reflect.DeepEqual(a[i].Before, b[i].Before) { 613 return false 614 } 615 if !reflect.DeepEqual(a[i].After, b[i].After) { 616 return false 617 } 618 if !reflect.DeepEqual(a[i].Fragments, b[i].Fragments) { 619 return false 620 } 621 } 622 return true 623} 624 625func checkResultMatches(t *testing.T, ts *httptest.Server, req string, expected Expectation) { 626 res, err := http.Get(ts.URL + req) 627 if err != nil { 628 t.Fatal(err) 629 } 630 resultBytes, err := io.ReadAll(res.Body) 631 res.Body.Close() 632 if err != nil { 633 log.Fatal(err) 634 } 635 636 var result ApiSearchResult 637 if err := json.Unmarshal(resultBytes, &result); err != nil { 638 log.Fatal(err) 639 } 640 641 if len(result.Result.FileMatches) != 1 { 642 t.Fatalf("Expected search to return just one result but it was %d", len(result.Result.FileMatches)) 643 } 644 match := result.Result.FileMatches[0] 645 if match.FileName == expected.fileMatch.FileName && match.Repo == expected.fileMatch.Repo { 646 if matchesPartiallyEqual(match.Matches, expected.fileMatch.Matches) { 647 return 648 } 649 } 650 651 t.Errorf( 652 "result doesn't match case <%s>:\nDiff:\n %v", 653 expected.title, 654 cmp.Diff(expected.fileMatch.Matches, result.Result.FileMatches[0].Matches)) 655} 656 657func TestContextLinesMustBeValid(t *testing.T) { 658 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 659 Name: "name", 660 URL: "repo-url", 661 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 662 }) 663 if err != nil { 664 t.Fatalf("NewIndexBuilder: %v", err) 665 } 666 if err := b.Add(zoekt.Document{ 667 Name: "f2", 668 Content: []byte("to carry water in the no later bla"), 669 Branches: []string{"master"}, 670 }); err != nil { 671 t.Fatalf("Add: %v", err) 672 } 673 s := searcherForTest(t, b) 674 srv := Server{ 675 Searcher: s, 676 Top: Top, 677 HTML: true, 678 } 679 680 mux, err := NewMux(&srv) 681 if err != nil { 682 t.Fatalf("NewMux: %v", err) 683 } 684 685 ts := httptest.NewServer(mux) 686 defer ts.Close() 687 688 // Don't care about ctx if format is not json 689 code := getHttpStatusCode(t, ts, "/search?q=water&ctx=10") 690 if code != 200 { 691 t.Errorf("Expected 200 but got %v", code) 692 } 693 694 // ctx must be a valid integer in the right range. 695 for _, want := range []string{"foo", "-1", "20"} { 696 code := getHttpStatusCode(t, ts, "/search?q=water&format=json&ctx="+want) 697 if code != 418 { 698 t.Errorf("Expected 418 but got %v", code) 699 } 700 } 701} 702 703func getHttpStatusCode(t *testing.T, ts *httptest.Server, req string) int { 704 res, err := http.Get(ts.URL + req) 705 if err != nil { 706 t.Fatal(err) 707 } 708 return res.StatusCode 709} 710 711type crashSearcher struct { 712 zoekt.Streamer 713} 714 715func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) { 716 res := zoekt.SearchResult{} 717 res.Stats.Crashes = 1 718 return &res, nil 719} 720 721func TestCrash(t *testing.T) { 722 srv := Server{ 723 Searcher: &crashSearcher{}, 724 Top: Top, 725 HTML: true, 726 } 727 728 mux, err := NewMux(&srv) 729 if err != nil { 730 t.Fatalf("NewMux: %v", err) 731 } 732 733 ts := httptest.NewServer(mux) 734 defer ts.Close() 735 736 res, err := http.Get(ts.URL + "/search?q=water") 737 if err != nil { 738 t.Fatal(err) 739 } 740 resultBytes, err := io.ReadAll(res.Body) 741 res.Body.Close() 742 if err != nil { 743 t.Fatal(err) 744 } 745 746 result := string(resultBytes) 747 if want := "1 shards crashed"; !strings.Contains(result, want) { 748 t.Errorf("result did not have %q: %s", want, result) 749 } 750} 751 752func TestHostCustomization(t *testing.T) { 753 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 754 Name: "name", 755 }) 756 if err != nil { 757 t.Fatalf("NewIndexBuilder: %v", err) 758 } 759 if err := b.Add(zoekt.Document{ 760 Name: "file", 761 Content: []byte("bla"), 762 }); err != nil { 763 t.Fatalf("Add: %v", err) 764 } 765 766 s := searcherForTest(t, b) 767 srv := Server{ 768 Searcher: s, 769 Top: Top, 770 HTML: true, 771 HostCustomQueries: map[string]string{ 772 "myproject.io": "r:myproject", 773 }, 774 } 775 776 mux, err := NewMux(&srv) 777 if err != nil { 778 t.Fatalf("NewMux: %v", err) 779 } 780 781 ts := httptest.NewServer(mux) 782 defer ts.Close() 783 784 req, err := http.NewRequest("GET", ts.URL, &bytes.Buffer{}) 785 if err != nil { 786 t.Fatalf("NewRequest: %v", err) 787 } 788 req.Host = "myproject.io" 789 res, err := (&http.Client{}).Do(req) 790 if err != nil { 791 t.Fatalf("Do(%v): %v", req, err) 792 } 793 resultBytes, err := io.ReadAll(res.Body) 794 res.Body.Close() 795 if err != nil { 796 t.Fatalf("ReadAll: %v", err) 797 } 798 799 if got, want := string(resultBytes), "r:myproject"; !strings.Contains(got, want) { 800 t.Fatalf("got %s, want substring %q", got, want) 801 } 802} 803 804func TestDupResult(t *testing.T) { 805 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 806 Name: "name", 807 }) 808 if err != nil { 809 t.Fatalf("NewIndexBuilder: %v", err) 810 } 811 812 for i := 0; i < 2; i++ { 813 if err := b.Add(zoekt.Document{ 814 Name: fmt.Sprintf("file%d", i), 815 Content: []byte("bla"), 816 }); err != nil { 817 t.Fatalf("Add: %v", err) 818 } 819 } 820 s := searcherForTest(t, b) 821 srv := Server{ 822 Searcher: s, 823 Top: Top, 824 HTML: true, 825 } 826 827 mux, err := NewMux(&srv) 828 if err != nil { 829 t.Fatalf("NewMux: %v", err) 830 } 831 832 ts := httptest.NewServer(mux) 833 defer ts.Close() 834 835 req, err := http.NewRequest("GET", ts.URL+"/search?q=bla", &bytes.Buffer{}) 836 if err != nil { 837 t.Fatalf("NewRequest: %v", err) 838 } 839 res, err := (&http.Client{}).Do(req) 840 if err != nil { 841 t.Fatalf("Do(%v): %v", req, err) 842 } 843 resultBytes, err := io.ReadAll(res.Body) 844 res.Body.Close() 845 if err != nil { 846 t.Fatalf("ReadAll: %v", err) 847 } 848 849 if got, want := string(resultBytes), "Duplicate result"; !strings.Contains(got, want) { 850 t.Fatalf("got %s, want substring %q", got, want) 851 } 852} 853 854func TestTruncateLine(t *testing.T) { 855 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 856 Name: "name", 857 }) 858 if err != nil { 859 t.Fatalf("NewIndexBuilder: %v", err) 860 } 861 862 largePadding := bytes.Repeat([]byte{'a'}, 100*1000) // 100kb 863 if err := b.Add(zoekt.Document{ 864 Name: "file", 865 Content: append(append(largePadding, []byte("helloworld")...), largePadding...), 866 }); err != nil { 867 t.Fatalf("Add: %v", err) 868 } 869 s := searcherForTest(t, b) 870 srv := Server{ 871 Searcher: s, 872 Top: Top, 873 HTML: true, 874 } 875 876 mux, err := NewMux(&srv) 877 if err != nil { 878 t.Fatalf("NewMux: %v", err) 879 } 880 881 ts := httptest.NewServer(mux) 882 defer ts.Close() 883 884 req, err := http.NewRequest("GET", ts.URL+"/search?q=helloworld", &bytes.Buffer{}) 885 if err != nil { 886 t.Fatalf("NewRequest: %v", err) 887 } 888 res, err := (&http.Client{}).Do(req) 889 if err != nil { 890 t.Fatalf("Do(%v): %v", req, err) 891 } 892 resultBytes, err := io.ReadAll(res.Body) 893 res.Body.Close() 894 if err != nil { 895 t.Fatalf("ReadAll: %v", err) 896 } 897 898 if got, want := len(resultBytes)/1000, 10; got > want { 899 t.Fatalf("got %dkb response, want <= %dkb", got, want) 900 } 901 result := string(resultBytes) 902 if want := "aa<b>helloworld</b>aa"; !strings.Contains(result, want) { 903 t.Fatalf("got %s, want substring %q", result, want) 904 } 905 if want := "bytes skipped)..."; !strings.Contains(result, want) { 906 t.Fatalf("got %s, want substring %q", result, want) 907 } 908} 909 910func TestHealthz(t *testing.T) { 911 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 912 Name: "name", 913 }) 914 if err != nil { 915 t.Fatalf("NewIndexBuilder: %v", err) 916 } 917 918 for i := 0; i < 2; i++ { 919 if err := b.Add(zoekt.Document{ 920 Name: fmt.Sprintf("file%d", i), 921 Content: []byte("bla"), 922 }); err != nil { 923 t.Fatalf("Add: %v", err) 924 } 925 } 926 s := searcherForTest(t, b) 927 srv := Server{ 928 Searcher: s, 929 Top: Top, 930 HTML: true, 931 } 932 933 mux, err := NewMux(&srv) 934 if err != nil { 935 t.Fatalf("NewMux: %v", err) 936 } 937 938 ts := httptest.NewServer(mux) 939 t.Cleanup(ts.Close) 940 941 req, err := http.NewRequest("GET", ts.URL+"/healthz", nil) 942 if err != nil { 943 t.Fatalf("NewRequest: %v", err) 944 } 945 res, err := http.DefaultClient.Do(req) 946 if err != nil { 947 t.Fatalf("Do(%v): %v", req, err) 948 } 949 950 t.Cleanup(func() { 951 res.Body.Close() 952 }) 953 954 if res.StatusCode != http.StatusOK { 955 t.Fatalf("want 200 status code, got: %v", res.StatusCode) 956 } 957 958 var result zoekt.SearchResult 959 err = json.NewDecoder(res.Body).Decode(&result) 960 if err != nil { 961 t.Fatalf("json.Decode: %v", err) 962 } 963 964 if reflect.DeepEqual(result, zoekt.SearchResult{}) { 965 t.Fatal("empty result in response") 966 } 967} 968 969func TestRPC(t *testing.T) { 970 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 971 Name: "name", 972 URL: "repo-url", 973 CommitURLTemplate: "{{.Version}}", 974 FileURLTemplate: "file-url", 975 LineFragmentTemplate: "#line", 976 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 977 }) 978 if err != nil { 979 t.Fatalf("NewIndexBuilder: %v", err) 980 } 981 if err := b.Add(zoekt.Document{ 982 Name: "f2", 983 Content: []byte("to carry water in the no later bla"), 984 // --------------0123456789012345678901234567890123 985 // --------------0 1 2 3 986 Branches: []string{"master"}, 987 }); err != nil { 988 t.Fatalf("Add: %v", err) 989 } 990 991 s := searcherForTest(t, b) 992 srv := Server{ 993 Searcher: s, 994 RPC: true, 995 Top: Top, 996 } 997 998 mux, err := NewMux(&srv) 999 if err != nil { 1000 t.Fatalf("NewMux: %v", err) 1001 } 1002 1003 ts := httptest.NewServer(mux) 1004 defer ts.Close() 1005 1006 endpoint := ts.Listener.Addr().String() 1007 1008 client := stream.NewClient("http://"+endpoint, nil).WithSearcher(rpc.Client(endpoint)) 1009 1010 ctx := context.Background() 1011 q := &query.Substring{Pattern: "water"} 1012 opts := &zoekt.SearchOptions{ChunkMatches: true} 1013 opts.SetDefaults() 1014 results, err := client.Search(ctx, q, opts) 1015 if err != nil { 1016 t.Fatal(err) 1017 } 1018 1019 assertResults(t, results.Files, "f2: to carry water in the no later bla") 1020 1021 // TODO grpc, List, StreamSearch 1022} 1023 1024func assertResults(t *testing.T, files []zoekt.FileMatch, want string) { 1025 t.Helper() 1026 1027 var lines []string 1028 for _, fm := range files { 1029 for _, cm := range fm.ChunkMatches { 1030 lines = append(lines, fmt.Sprintf("%s: %s", fm.FileName, string(cm.Content))) 1031 } 1032 } 1033 sort.Strings(lines) 1034 got := strings.TrimSpace(strings.Join(lines, "\n")) 1035 want = strings.TrimSpace(want) 1036 1037 if d := cmp.Diff(want, got); d != "" { 1038 t.Fatalf("unexpected results (-want, +got):\n%s", d) 1039 } 1040}