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