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

Configure Feed

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

at main 22 kB View raw
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/index" 36 "github.com/sourcegraph/zoekt/query" 37) 38 39// TODO(hanwen): cut & paste from ../ . Should create internal test 40// util package. 41type memSeeker struct { 42 data []byte 43} 44 45func (s *memSeeker) Close() {} 46func (s *memSeeker) Read(off, sz uint32) ([]byte, error) { 47 return s.data[off : off+sz], nil 48} 49 50func (s *memSeeker) Size() (uint32, error) { 51 return uint32(len(s.data)), nil 52} 53 54func (s *memSeeker) Name() string { 55 return "memSeeker" 56} 57 58func searcherForTest(t *testing.T, b *index.ShardBuilder) zoekt.Streamer { 59 var buf bytes.Buffer 60 if err := b.Write(&buf); err != nil { 61 t.Fatal(err) 62 } 63 f := &memSeeker{buf.Bytes()} 64 65 searcher, err := index.NewSearcher(f) 66 if err != nil { 67 t.Fatalf("NewSearcher: %v", err) 68 } 69 70 return adapter{Searcher: searcher} 71} 72 73type adapter struct { 74 zoekt.Searcher 75} 76 77func (a adapter) StreamSearch(ctx context.Context, q query.Q, opts *zoekt.SearchOptions, sender zoekt.Sender) (err error) { 78 sr, err := a.Searcher.Search(ctx, q, opts) 79 if err != nil { 80 return err 81 } 82 sender.Send(sr) 83 return nil 84} 85 86func TestBasic(t *testing.T) { 87 b, err := index.NewShardBuilder(&zoekt.Repository{ 88 Name: "name", 89 URL: "repo-url", 90 CommitURLTemplate: `{{ URLJoinPath "https://github.com/org/repo/commit/" .Version}}`, 91 FileURLTemplate: `{{ URLJoinPath "https://github.com/org/repo/blob/" .Version .Path}}`, 92 LineFragmentTemplate: "#L{{.LineNumber}}", 93 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 94 }) 95 if err != nil { 96 t.Fatalf("NewShardBuilder: %v", err) 97 } 98 if err := b.Add(index.Document{ 99 // use a name which requires correct escaping. https://github.com/sourcegraph/zoekt/issues/807 100 Name: "foo/bar+baz", 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="https://github.com/org/repo/blob/1234/foo/bar%2Bbaz"`, 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 (45B)", 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 := index.NewShardBuilder(&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("NewShardBuilder: %v", err) 163 } 164 if err := b.Add(index.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(index.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 := index.NewShardBuilder(&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("NewShardBuilder: %v", err) 213 } 214 if err := b.Add(index.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 := index.NewShardBuilder(&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("NewShardBuilder: %v", err) 283 } 284 if err := b.Add(index.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 := index.NewShardBuilder(&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("NewShardBuilder: %v", err) 338 } 339 if err := b.Add(index.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(index.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(index.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(index.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\n", 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\n", 435 }, 436 }, 437 Before: "second snippet\nthird thing\n", 438 After: "fifth block\nsixth example\n", 439 }, 440 }, 441 }, 442 }, 443 "/search?q=one&format=json&ctx=2": { 444 "index 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\n", 457 }, 458 }, 459 After: "second snippet\nthird thing\n", 460 }, 461 }, 462 }, 463 }, 464 "/search?q=seventh&format=json&ctx=2": { 465 "index 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\n", 481 }, 482 }, 483 }, 484 }, 485 "/search?q=seventh&format=json&ctx=10": { 486 "index 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\n", 502 }, 503 }, 504 }, 505 }, 506 "/search?q=one&format=json&ctx=10": { 507 "index 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\n", 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 Post: "\n", 541 }, 542 }, 543 Before: "un \n \n", 544 After: " \n\n", 545 }, 546 }, 547 }, 548 }, 549 "/search?q=water&format=json&ctx=4": { 550 "context returns new lines", 551 FileMatch{ 552 FileName: "f3", 553 Repo: "name", 554 Matches: []Match{ 555 { 556 FileName: "f3", 557 LineNum: 5, 558 Fragments: []Fragment{ 559 { 560 Pre: "to carry ", 561 Match: "water", 562 Post: " in the no later bla\n", 563 }, 564 }, 565 Before: "\n\n\n\n", 566 After: "\n\n\n", 567 }, 568 }, 569 }, 570 }, 571 "/search?q=pastures&format=json&ctx=1": { 572 "context returns empty end line", 573 FileMatch{ 574 FileName: "f5", 575 Repo: "name", 576 Matches: []Match{ 577 { 578 FileName: "f5", 579 LineNum: 3, 580 Fragments: []Fragment{ 581 { 582 Pre: "", 583 Match: "pastures", 584 Post: "\n", 585 }, 586 }, 587 Before: "green\n", 588 After: "\n", 589 }, 590 }, 591 }, 592 }, 593 } { 594 checkResultMatches(t, ts, req, expected) 595 } 596} 597 598func matchesPartiallyEqual(a, b []Match) bool { 599 if len(a) != len(b) { 600 return false 601 } 602 for i := range a { 603 if a[i].FileName != b[i].FileName { 604 return false 605 } 606 if a[i].LineNum != b[i].LineNum { 607 return false 608 } 609 if !reflect.DeepEqual(a[i].Before, b[i].Before) { 610 return false 611 } 612 if !reflect.DeepEqual(a[i].After, b[i].After) { 613 return false 614 } 615 if !reflect.DeepEqual(a[i].Fragments, b[i].Fragments) { 616 return false 617 } 618 } 619 return true 620} 621 622func checkResultMatches(t *testing.T, ts *httptest.Server, req string, expected Expectation) { 623 res, err := http.Get(ts.URL + req) 624 if err != nil { 625 t.Fatal(err) 626 } 627 resultBytes, err := io.ReadAll(res.Body) 628 res.Body.Close() 629 if err != nil { 630 log.Fatal(err) 631 } 632 633 var result ApiSearchResult 634 if err := json.Unmarshal(resultBytes, &result); err != nil { 635 log.Fatal(err) 636 } 637 638 if len(result.Result.FileMatches) != 1 { 639 t.Fatalf("Expected search to return just one result but it was %d", len(result.Result.FileMatches)) 640 } 641 match := result.Result.FileMatches[0] 642 if match.FileName == expected.fileMatch.FileName && match.Repo == expected.fileMatch.Repo { 643 if matchesPartiallyEqual(match.Matches, expected.fileMatch.Matches) { 644 return 645 } 646 } 647 648 t.Errorf( 649 "result doesn't match case <%s>:\nDiff:\n %v", 650 expected.title, 651 cmp.Diff(expected.fileMatch.Matches, result.Result.FileMatches[0].Matches)) 652} 653 654func TestContextLinesMustBeValid(t *testing.T) { 655 b, err := index.NewShardBuilder(&zoekt.Repository{ 656 Name: "name", 657 URL: "repo-url", 658 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 659 }) 660 if err != nil { 661 t.Fatalf("NewShardBuilder: %v", err) 662 } 663 if err := b.Add(index.Document{ 664 Name: "f2", 665 Content: []byte("to carry water in the no later bla"), 666 Branches: []string{"master"}, 667 }); err != nil { 668 t.Fatalf("Add: %v", err) 669 } 670 s := searcherForTest(t, b) 671 srv := Server{ 672 Searcher: s, 673 Top: Top, 674 HTML: true, 675 } 676 677 mux, err := NewMux(&srv) 678 if err != nil { 679 t.Fatalf("NewMux: %v", err) 680 } 681 682 ts := httptest.NewServer(mux) 683 defer ts.Close() 684 685 // Don't care about ctx if format is not json 686 code := getHttpStatusCode(t, ts, "/search?q=water&ctx=10") 687 if code != 200 { 688 t.Errorf("Expected 200 but got %v", code) 689 } 690 691 // ctx must be a valid integer in the right range. 692 for _, want := range []string{"foo", "-1", "20"} { 693 code := getHttpStatusCode(t, ts, "/search?q=water&format=json&ctx="+want) 694 if code != 418 { 695 t.Errorf("Expected 418 but got %v", code) 696 } 697 } 698} 699 700func getHttpStatusCode(t *testing.T, ts *httptest.Server, req string) int { 701 res, err := http.Get(ts.URL + req) 702 if err != nil { 703 t.Fatal(err) 704 } 705 return res.StatusCode 706} 707 708type crashSearcher struct { 709 zoekt.Streamer 710} 711 712func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) { 713 res := zoekt.SearchResult{} 714 res.Stats.Crashes = 1 715 return &res, nil 716} 717 718func TestCrash(t *testing.T) { 719 srv := Server{ 720 Searcher: &crashSearcher{}, 721 Top: Top, 722 HTML: true, 723 } 724 725 mux, err := NewMux(&srv) 726 if err != nil { 727 t.Fatalf("NewMux: %v", err) 728 } 729 730 ts := httptest.NewServer(mux) 731 defer ts.Close() 732 733 res, err := http.Get(ts.URL + "/search?q=water") 734 if err != nil { 735 t.Fatal(err) 736 } 737 resultBytes, err := io.ReadAll(res.Body) 738 res.Body.Close() 739 if err != nil { 740 t.Fatal(err) 741 } 742 743 result := string(resultBytes) 744 if want := "1 shards crashed"; !strings.Contains(result, want) { 745 t.Errorf("result did not have %q: %s", want, result) 746 } 747} 748 749func TestHostCustomization(t *testing.T) { 750 b, err := index.NewShardBuilder(&zoekt.Repository{ 751 Name: "name", 752 }) 753 if err != nil { 754 t.Fatalf("NewShardBuilder: %v", err) 755 } 756 if err := b.Add(index.Document{ 757 Name: "file", 758 Content: []byte("bla"), 759 }); err != nil { 760 t.Fatalf("Add: %v", err) 761 } 762 763 s := searcherForTest(t, b) 764 srv := Server{ 765 Searcher: s, 766 Top: Top, 767 HTML: true, 768 HostCustomQueries: map[string]string{ 769 "myproject.io": "r:myproject", 770 }, 771 } 772 773 mux, err := NewMux(&srv) 774 if err != nil { 775 t.Fatalf("NewMux: %v", err) 776 } 777 778 ts := httptest.NewServer(mux) 779 defer ts.Close() 780 781 req, err := http.NewRequest("GET", ts.URL, &bytes.Buffer{}) 782 if err != nil { 783 t.Fatalf("NewRequest: %v", err) 784 } 785 req.Host = "myproject.io" 786 res, err := (&http.Client{}).Do(req) 787 if err != nil { 788 t.Fatalf("Do(%v): %v", req, err) 789 } 790 resultBytes, err := io.ReadAll(res.Body) 791 res.Body.Close() 792 if err != nil { 793 t.Fatalf("ReadAll: %v", err) 794 } 795 796 if got, want := string(resultBytes), "r:myproject"; !strings.Contains(got, want) { 797 t.Fatalf("got %s, want substring %q", got, want) 798 } 799} 800 801func TestDupResult(t *testing.T) { 802 b, err := index.NewShardBuilder(&zoekt.Repository{ 803 Name: "name", 804 }) 805 if err != nil { 806 t.Fatalf("NewShardBuilder: %v", err) 807 } 808 809 for i := range 2 { 810 if err := b.Add(index.Document{ 811 Name: fmt.Sprintf("file%d", i), 812 Content: []byte("bla"), 813 }); err != nil { 814 t.Fatalf("Add: %v", err) 815 } 816 } 817 s := searcherForTest(t, b) 818 srv := Server{ 819 Searcher: s, 820 Top: Top, 821 HTML: true, 822 } 823 824 mux, err := NewMux(&srv) 825 if err != nil { 826 t.Fatalf("NewMux: %v", err) 827 } 828 829 ts := httptest.NewServer(mux) 830 defer ts.Close() 831 832 req, err := http.NewRequest("GET", ts.URL+"/search?q=bla", &bytes.Buffer{}) 833 if err != nil { 834 t.Fatalf("NewRequest: %v", err) 835 } 836 res, err := (&http.Client{}).Do(req) 837 if err != nil { 838 t.Fatalf("Do(%v): %v", req, err) 839 } 840 resultBytes, err := io.ReadAll(res.Body) 841 res.Body.Close() 842 if err != nil { 843 t.Fatalf("ReadAll: %v", err) 844 } 845 846 if got, want := string(resultBytes), "Duplicate result"; !strings.Contains(got, want) { 847 t.Fatalf("got %s, want substring %q", got, want) 848 } 849} 850 851func TestTruncateLine(t *testing.T) { 852 b, err := index.NewShardBuilder(&zoekt.Repository{ 853 Name: "name", 854 }) 855 if err != nil { 856 t.Fatalf("NewShardBuilder: %v", err) 857 } 858 859 largePadding := bytes.Repeat([]byte{'a'}, 100*1000) // 100kb 860 if err := b.Add(index.Document{ 861 Name: "file", 862 Content: append(append(largePadding, []byte("helloworld")...), largePadding...), 863 }); err != nil { 864 t.Fatalf("Add: %v", err) 865 } 866 s := searcherForTest(t, b) 867 srv := Server{ 868 Searcher: s, 869 Top: Top, 870 HTML: true, 871 } 872 873 mux, err := NewMux(&srv) 874 if err != nil { 875 t.Fatalf("NewMux: %v", err) 876 } 877 878 ts := httptest.NewServer(mux) 879 defer ts.Close() 880 881 req, err := http.NewRequest("GET", ts.URL+"/search?q=helloworld", &bytes.Buffer{}) 882 if err != nil { 883 t.Fatalf("NewRequest: %v", err) 884 } 885 res, err := (&http.Client{}).Do(req) 886 if err != nil { 887 t.Fatalf("Do(%v): %v", req, err) 888 } 889 resultBytes, err := io.ReadAll(res.Body) 890 res.Body.Close() 891 if err != nil { 892 t.Fatalf("ReadAll: %v", err) 893 } 894 895 if got, want := len(resultBytes)/1000, 10; got > want { 896 t.Fatalf("got %dkb response, want <= %dkb", got, want) 897 } 898 result := string(resultBytes) 899 if want := "aa<b>helloworld</b>aa"; !strings.Contains(result, want) { 900 t.Fatalf("got %s, want substring %q", result, want) 901 } 902 if want := "bytes skipped)..."; !strings.Contains(result, want) { 903 t.Fatalf("got %s, want substring %q", result, want) 904 } 905} 906 907func TestHealthz(t *testing.T) { 908 b, err := index.NewShardBuilder(&zoekt.Repository{ 909 Name: "name", 910 }) 911 if err != nil { 912 t.Fatalf("NewShardBuilder: %v", err) 913 } 914 915 for i := range 2 { 916 if err := b.Add(index.Document{ 917 Name: fmt.Sprintf("file%d", i), 918 Content: []byte("bla"), 919 }); err != nil { 920 t.Fatalf("Add: %v", err) 921 } 922 } 923 s := searcherForTest(t, b) 924 srv := Server{ 925 Searcher: s, 926 Top: Top, 927 HTML: true, 928 } 929 930 mux, err := NewMux(&srv) 931 if err != nil { 932 t.Fatalf("NewMux: %v", err) 933 } 934 935 ts := httptest.NewServer(mux) 936 t.Cleanup(ts.Close) 937 938 req, err := http.NewRequest("GET", ts.URL+"/healthz", nil) 939 if err != nil { 940 t.Fatalf("NewRequest: %v", err) 941 } 942 res, err := http.DefaultClient.Do(req) 943 if err != nil { 944 t.Fatalf("Do(%v): %v", req, err) 945 } 946 947 t.Cleanup(func() { 948 res.Body.Close() 949 }) 950 951 if res.StatusCode != http.StatusOK { 952 t.Fatalf("want 200 status code, got: %v", res.StatusCode) 953 } 954 955 var result zoekt.SearchResult 956 err = json.NewDecoder(res.Body).Decode(&result) 957 if err != nil { 958 t.Fatalf("json.Decode: %v", err) 959 } 960 961 if reflect.DeepEqual(result, zoekt.SearchResult{}) { 962 t.Fatal("empty result in response") 963 } 964} 965 966func assertResults(t *testing.T, files []zoekt.FileMatch, want string) { 967 t.Helper() 968 969 var lines []string 970 for _, fm := range files { 971 for _, cm := range fm.ChunkMatches { 972 lines = append(lines, fmt.Sprintf("%s: %s", fm.FileName, string(cm.Content))) 973 } 974 } 975 sort.Strings(lines) 976 got := strings.TrimSpace(strings.Join(lines, "\n")) 977 want = strings.TrimSpace(want) 978 979 if d := cmp.Diff(want, got); d != "" { 980 t.Fatalf("unexpected results (-want, +got):\n%s", d) 981 } 982}