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