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 "github.com/sourcegraph/zoekt" 34 "github.com/sourcegraph/zoekt/query" 35 "github.com/sourcegraph/zoekt/rpc" 36 "github.com/sourcegraph/zoekt/stream" 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 *zoekt.IndexBuilder) 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 := zoekt.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 := zoekt.NewIndexBuilder(&zoekt.Repository{ 88 Name: "name", 89 URL: "repo-url", 90 CommitURLTemplate: "{{.Version}}", 91 FileURLTemplate: "file-url", 92 LineFragmentTemplate: "#line", 93 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 94 }) 95 if err != nil { 96 t.Fatalf("NewIndexBuilder: %v", err) 97 } 98 if err := b.Add(zoekt.Document{ 99 Name: "f2", 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=\"file-url#line", 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 (36B)", 136 }, 137 "/search?q=magic": { 138 `value=magic`, 139 }, 140 "/robots.txt": { 141 "disallow: /search", 142 }, 143 } { 144 checkNeedles(t, ts, req, needles) 145 } 146} 147 148func TestPrint(t *testing.T) { 149 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 150 Name: "name", 151 URL: "repo-url", 152 CommitURLTemplate: "{{.Version}}", 153 FileURLTemplate: "file-url", 154 LineFragmentTemplate: "line", 155 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 156 }) 157 if err != nil { 158 t.Fatalf("NewIndexBuilder: %v", err) 159 } 160 if err := b.Add(zoekt.Document{ 161 Name: "f2", 162 Content: []byte("to carry water in the no later bla"), 163 Branches: []string{"master"}, 164 }); err != nil { 165 t.Fatalf("Add: %v", err) 166 } 167 168 if err := b.Add(zoekt.Document{ 169 Name: "dir/f2", 170 Content: []byte("blabla"), 171 Branches: []string{"master"}, 172 }); err != nil { 173 t.Fatalf("Add: %v", err) 174 } 175 176 s := searcherForTest(t, b) 177 srv := Server{ 178 Searcher: s, 179 Top: Top, 180 HTML: true, 181 Print: true, 182 } 183 184 mux, err := NewMux(&srv) 185 if err != nil { 186 t.Fatalf("NewMux: %v", err) 187 } 188 189 ts := httptest.NewServer(mux) 190 defer ts.Close() 191 192 for req, needles := range map[string][]string{ 193 "/print?q=bla&r=name&f=f2": { 194 `pre id="l1" class="inline-pre"><span class="noselect"><a href="#l1">`, 195 }, 196 } { 197 checkNeedles(t, ts, req, needles) 198 } 199} 200 201func TestPrintDefault(t *testing.T) { 202 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 203 Name: "name", 204 URL: "repo-url", 205 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 206 }) 207 if err != nil { 208 t.Fatalf("NewIndexBuilder: %v", err) 209 } 210 if err := b.Add(zoekt.Document{ 211 Name: "f2", 212 Content: []byte("to carry water in the no later bla"), 213 Branches: []string{"master"}, 214 }); err != nil { 215 t.Fatalf("Add: %v", err) 216 } 217 s := searcherForTest(t, b) 218 srv := Server{ 219 Searcher: s, 220 Top: Top, 221 HTML: true, 222 } 223 224 mux, err := NewMux(&srv) 225 if err != nil { 226 t.Fatalf("NewMux: %v", err) 227 } 228 229 ts := httptest.NewServer(mux) 230 defer ts.Close() 231 232 for req, needles := range map[string][]string{ 233 "/search?q=water": { 234 `href="print?`, 235 }, 236 } { 237 checkNeedles(t, ts, req, needles) 238 } 239} 240 241func checkNeedles(t *testing.T, ts *httptest.Server, req string, needles []string) { 242 res, err := http.Get(ts.URL + req) 243 if err != nil { 244 t.Fatal(err) 245 } 246 resultBytes, err := io.ReadAll(res.Body) 247 res.Body.Close() 248 if err != nil { 249 log.Fatal(err) 250 } 251 252 result := string(resultBytes) 253 for _, want := range needles { 254 if !strings.Contains(result, want) { 255 t.Errorf("query %q: result did not have %q: %s", req, want, result) 256 } 257 } 258 if notWant := "crashed"; strings.Contains(result, notWant) { 259 t.Errorf("result has %q: %s", notWant, result) 260 } 261 if notWant := "bytes skipped)..."; strings.Contains(result, notWant) { 262 t.Errorf("result has %q: %s", notWant, result) 263 } 264} 265 266type Expectation struct { 267 title string 268 fileMatch FileMatch 269} 270 271func TestFormatJson(t *testing.T) { 272 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 273 Name: "name", 274 URL: "repo-url", 275 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 276 }) 277 if err != nil { 278 t.Fatalf("NewIndexBuilder: %v", err) 279 } 280 if err := b.Add(zoekt.Document{ 281 Name: "f2", 282 Content: []byte("to carry water in the no later bla"), 283 Branches: []string{"master"}, 284 }); err != nil { 285 t.Fatalf("Add: %v", err) 286 } 287 s := searcherForTest(t, b) 288 srv := Server{ 289 Searcher: s, 290 Top: Top, 291 HTML: true, 292 } 293 294 mux, err := NewMux(&srv) 295 if err != nil { 296 t.Fatalf("NewMux: %v", err) 297 } 298 299 ts := httptest.NewServer(mux) 300 defer ts.Close() 301 302 expected := Expectation{ 303 "json basic test", 304 FileMatch{ 305 FileName: "f2", 306 Repo: "name", 307 Matches: []Match{ 308 { 309 FileName: "f2", 310 LineNum: 1, 311 Fragments: []Fragment{ 312 { 313 Pre: "to carry ", 314 Match: "water", 315 Post: " in the no later bla", 316 }, 317 }, 318 }, 319 }, 320 }, 321 } 322 323 checkResultMatches(t, ts, "/search?q=water&format=json", expected) 324} 325 326func TestContextLines(t *testing.T) { 327 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 328 Name: "name", 329 URL: "repo-url", 330 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 331 }) 332 if err != nil { 333 t.Fatalf("NewIndexBuilder: %v", err) 334 } 335 if err := b.Add(zoekt.Document{ 336 Name: "f2", 337 Content: []byte("one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh"), 338 Branches: []string{"master"}, 339 }); err != nil { 340 t.Fatalf("Add: %v", err) 341 } 342 if err := b.Add(zoekt.Document{ 343 Name: "f3", 344 Content: []byte("\n\n\n\nto carry water in the no later bla\n\n\n\n"), 345 Branches: []string{"master"}, 346 }); err != nil { 347 t.Fatalf("Add: %v", err) 348 } 349 if err := b.Add(zoekt.Document{ 350 Name: "f4", 351 Content: []byte("un \n \n\ttrois\n \n\nsix\n "), 352 Branches: []string{"master"}, 353 }); err != nil { 354 t.Fatalf("Add: %v", err) 355 } 356 if err := b.Add(zoekt.Document{ 357 Name: "f5", 358 Content: []byte("\ngreen\npastures\n\nhere"), 359 Branches: []string{"master"}, 360 }); err != nil { 361 t.Fatalf("Add: %v", err) 362 } 363 s := searcherForTest(t, b) 364 srv := Server{ 365 Searcher: s, 366 Top: Top, 367 HTML: true, 368 } 369 370 mux, err := NewMux(&srv) 371 if err != nil { 372 t.Fatalf("NewMux: %v", err) 373 } 374 375 ts := httptest.NewServer(mux) 376 defer ts.Close() 377 378 for req, expected := range map[string]Expectation{ 379 "/search?q=our&format=json&ctx=0": { 380 "no context doesn't return Before or After", 381 FileMatch{ 382 FileName: "f2", 383 Repo: "name", 384 Matches: []Match{ 385 { 386 FileName: "f2", 387 LineNum: 4, 388 Fragments: []Fragment{ 389 { 390 Pre: "f", 391 Match: "our", 392 Post: "th", 393 }, 394 }, 395 }, 396 }, 397 }, 398 }, 399 "/search?q=f:f2&format=json&ctx=2": { 400 "filename does not return Before or After", 401 FileMatch{ 402 FileName: "f2", 403 Repo: "name", 404 Matches: []Match{ 405 { 406 FileName: "f2", 407 LineNum: 0, 408 Fragments: []Fragment{ 409 { 410 Match: "f2", 411 }, 412 }, 413 }, 414 }, 415 }, 416 }, 417 "/search?q=our&format=json&ctx=2": { 418 "context returns Before and After", 419 FileMatch{ 420 FileName: "f2", 421 Repo: "name", 422 Matches: []Match{ 423 { 424 FileName: "f2", 425 LineNum: 4, 426 Fragments: []Fragment{ 427 { 428 Pre: "f", 429 Match: "our", 430 Post: "th", 431 }, 432 }, 433 Before: "second snippet\nthird thing", 434 After: "fifth block\nsixth example", 435 }, 436 }, 437 }, 438 }, 439 "/search?q=one&format=json&ctx=2": { 440 "match at start returns After but no Before", 441 FileMatch{ 442 FileName: "f2", 443 Repo: "name", 444 Matches: []Match{ 445 { 446 FileName: "f2", 447 LineNum: 1, 448 Fragments: []Fragment{ 449 { 450 Pre: "", 451 Match: "one", 452 Post: " line", 453 }, 454 }, 455 After: "second snippet\nthird thing", 456 }, 457 }, 458 }, 459 }, 460 "/search?q=seventh&format=json&ctx=2": { 461 "match at end returns Before but no After", 462 FileMatch{ 463 FileName: "f2", 464 Repo: "name", 465 Matches: []Match{ 466 { 467 FileName: "f2", 468 LineNum: 7, 469 Fragments: []Fragment{ 470 { 471 Pre: "", 472 Match: "seventh", 473 Post: "", 474 }, 475 }, 476 Before: "fifth block\nsixth example", 477 }, 478 }, 479 }, 480 }, 481 "/search?q=seventh&format=json&ctx=10": { 482 "match with large context at end returns whole document", 483 FileMatch{ 484 FileName: "f2", 485 Repo: "name", 486 Matches: []Match{ 487 { 488 FileName: "f2", 489 LineNum: 7, 490 Fragments: []Fragment{ 491 { 492 Pre: "", 493 Match: "seventh", 494 Post: "", 495 }, 496 }, 497 Before: "one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example", 498 }, 499 }, 500 }, 501 }, 502 "/search?q=one&format=json&ctx=10": { 503 "match with large context at start returns whole document", 504 FileMatch{ 505 FileName: "f2", 506 Repo: "name", 507 Matches: []Match{ 508 { 509 FileName: "f2", 510 LineNum: 1, 511 Fragments: []Fragment{ 512 { 513 Pre: "", 514 Match: "one", 515 Post: " line", 516 }, 517 }, 518 After: "second snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh", 519 }, 520 }, 521 }, 522 }, 523 "/search?q=trois&format=json&ctx=2": { 524 "context returns whitespaces lines", 525 FileMatch{ 526 FileName: "f4", 527 Repo: "name", 528 Matches: []Match{ 529 { 530 FileName: "f4", 531 LineNum: 3, 532 Fragments: []Fragment{ 533 { 534 Pre: "\t", 535 Match: "trois", 536 }, 537 }, 538 Before: "un \n ", 539 After: " \n", 540 }, 541 }, 542 }, 543 }, 544 "/search?q=water&format=json&ctx=4": { 545 "context returns new lines", 546 FileMatch{ 547 FileName: "f3", 548 Repo: "name", 549 Matches: []Match{ 550 { 551 FileName: "f3", 552 LineNum: 5, 553 Fragments: []Fragment{ 554 { 555 Pre: "to carry ", 556 Match: "water", 557 Post: " in the no later bla", 558 }, 559 }, 560 // Returns 3 instead of 4 new line characters since we swallow 561 // the last new line in Before, Fragments and After. 562 Before: "\n\n\n", 563 // Returns 2 instead of 3 new line characters since a 564 // trailing newline at the end of the file does not 565 // constitue a new line. 566 After: "\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 }, 585 }, 586 Before: "green", 587 After: "", 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 TestRPC(t *testing.T) { 966 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 967 Name: "name", 968 URL: "repo-url", 969 CommitURLTemplate: "{{.Version}}", 970 FileURLTemplate: "file-url", 971 LineFragmentTemplate: "#line", 972 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 973 }) 974 if err != nil { 975 t.Fatalf("NewIndexBuilder: %v", err) 976 } 977 if err := b.Add(zoekt.Document{ 978 Name: "f2", 979 Content: []byte("to carry water in the no later bla"), 980 // --------------0123456789012345678901234567890123 981 // --------------0 1 2 3 982 Branches: []string{"master"}, 983 }); err != nil { 984 t.Fatalf("Add: %v", err) 985 } 986 987 s := searcherForTest(t, b) 988 srv := Server{ 989 Searcher: s, 990 RPC: true, 991 Top: Top, 992 } 993 994 mux, err := NewMux(&srv) 995 if err != nil { 996 t.Fatalf("NewMux: %v", err) 997 } 998 999 ts := httptest.NewServer(mux) 1000 defer ts.Close() 1001 1002 endpoint := ts.Listener.Addr().String() 1003 1004 client := stream.NewClient("http://"+endpoint, nil).WithSearcher(rpc.Client(endpoint)) 1005 1006 ctx := context.Background() 1007 q := &query.Substring{Pattern: "water"} 1008 opts := &zoekt.SearchOptions{ChunkMatches: true} 1009 opts.SetDefaults() 1010 results, err := client.Search(ctx, q, opts) 1011 if err != nil { 1012 t.Fatal(err) 1013 } 1014 1015 assertResults(t, results.Files, "f2: to carry water in the no later bla") 1016 1017 // TODO grpc, List, StreamSearch 1018} 1019 1020func assertResults(t *testing.T, files []zoekt.FileMatch, want string) { 1021 t.Helper() 1022 1023 var lines []string 1024 for _, fm := range files { 1025 for _, cm := range fm.ChunkMatches { 1026 lines = append(lines, fmt.Sprintf("%s: %s", fm.FileName, string(cm.Content))) 1027 } 1028 } 1029 sort.Strings(lines) 1030 got := strings.TrimSpace(strings.Join(lines, "\n")) 1031 want = strings.TrimSpace(want) 1032 1033 if d := cmp.Diff(want, got); d != "" { 1034 t.Fatalf("unexpected results (-want, +got):\n%s", d) 1035 } 1036}