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 After: "\n\n\n", 564 }, 565 }, 566 }, 567 }, 568 "/search?q=pastures&format=json&ctx=1": { 569 "context returns empty end line", 570 FileMatch{ 571 FileName: "f5", 572 Repo: "name", 573 Matches: []Match{ 574 { 575 FileName: "f5", 576 LineNum: 3, 577 Fragments: []Fragment{ 578 { 579 Pre: "", 580 Match: "pastures", 581 }, 582 }, 583 Before: "green", 584 After: "", 585 }, 586 }, 587 }, 588 }, 589 } { 590 checkResultMatches(t, ts, req, expected) 591 } 592} 593 594func matchesPartiallyEqual(a, b []Match) bool { 595 if len(a) != len(b) { 596 return false 597 } 598 for i := range a { 599 if a[i].FileName != b[i].FileName { 600 return false 601 } 602 if a[i].LineNum != b[i].LineNum { 603 return false 604 } 605 if !reflect.DeepEqual(a[i].Before, b[i].Before) { 606 return false 607 } 608 if !reflect.DeepEqual(a[i].After, b[i].After) { 609 return false 610 } 611 if !reflect.DeepEqual(a[i].Fragments, b[i].Fragments) { 612 return false 613 } 614 } 615 return true 616} 617 618func checkResultMatches(t *testing.T, ts *httptest.Server, req string, expected Expectation) { 619 res, err := http.Get(ts.URL + req) 620 if err != nil { 621 t.Fatal(err) 622 } 623 resultBytes, err := io.ReadAll(res.Body) 624 res.Body.Close() 625 if err != nil { 626 log.Fatal(err) 627 } 628 629 var result ApiSearchResult 630 if err := json.Unmarshal(resultBytes, &result); err != nil { 631 log.Fatal(err) 632 } 633 634 if len(result.Result.FileMatches) != 1 { 635 t.Fatalf("Expected search to return just one result but it was %d", len(result.Result.FileMatches)) 636 } 637 match := result.Result.FileMatches[0] 638 if match.FileName == expected.fileMatch.FileName && match.Repo == expected.fileMatch.Repo { 639 if matchesPartiallyEqual(match.Matches, expected.fileMatch.Matches) { 640 return 641 } 642 } 643 644 t.Errorf( 645 "result doesn't match case <%s>:\nDiff:\n %v", 646 expected.title, 647 cmp.Diff(expected.fileMatch.Matches, result.Result.FileMatches[0].Matches)) 648} 649 650func TestContextLinesMustBeValid(t *testing.T) { 651 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 652 Name: "name", 653 URL: "repo-url", 654 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 655 }) 656 if err != nil { 657 t.Fatalf("NewIndexBuilder: %v", err) 658 } 659 if err := b.Add(zoekt.Document{ 660 Name: "f2", 661 Content: []byte("to carry water in the no later bla"), 662 Branches: []string{"master"}, 663 }); err != nil { 664 t.Fatalf("Add: %v", err) 665 } 666 s := searcherForTest(t, b) 667 srv := Server{ 668 Searcher: s, 669 Top: Top, 670 HTML: true, 671 } 672 673 mux, err := NewMux(&srv) 674 if err != nil { 675 t.Fatalf("NewMux: %v", err) 676 } 677 678 ts := httptest.NewServer(mux) 679 defer ts.Close() 680 681 // Don't care about ctx if format is not json 682 code := getHttpStatusCode(t, ts, "/search?q=water&ctx=10") 683 if code != 200 { 684 t.Errorf("Expected 200 but got %v", code) 685 } 686 687 // ctx must be a valid integer in the right range. 688 for _, want := range []string{"foo", "-1", "20"} { 689 code := getHttpStatusCode(t, ts, "/search?q=water&format=json&ctx="+want) 690 if code != 418 { 691 t.Errorf("Expected 418 but got %v", code) 692 } 693 } 694} 695 696func getHttpStatusCode(t *testing.T, ts *httptest.Server, req string) int { 697 res, err := http.Get(ts.URL + req) 698 if err != nil { 699 t.Fatal(err) 700 } 701 return res.StatusCode 702} 703 704type crashSearcher struct { 705 zoekt.Streamer 706} 707 708func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) { 709 res := zoekt.SearchResult{} 710 res.Stats.Crashes = 1 711 return &res, nil 712} 713 714func TestCrash(t *testing.T) { 715 srv := Server{ 716 Searcher: &crashSearcher{}, 717 Top: Top, 718 HTML: true, 719 } 720 721 mux, err := NewMux(&srv) 722 if err != nil { 723 t.Fatalf("NewMux: %v", err) 724 } 725 726 ts := httptest.NewServer(mux) 727 defer ts.Close() 728 729 res, err := http.Get(ts.URL + "/search?q=water") 730 if err != nil { 731 t.Fatal(err) 732 } 733 resultBytes, err := io.ReadAll(res.Body) 734 res.Body.Close() 735 if err != nil { 736 t.Fatal(err) 737 } 738 739 result := string(resultBytes) 740 if want := "1 shards crashed"; !strings.Contains(result, want) { 741 t.Errorf("result did not have %q: %s", want, result) 742 } 743} 744 745func TestHostCustomization(t *testing.T) { 746 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 747 Name: "name", 748 }) 749 if err != nil { 750 t.Fatalf("NewIndexBuilder: %v", err) 751 } 752 if err := b.Add(zoekt.Document{ 753 Name: "file", 754 Content: []byte("bla"), 755 }); err != nil { 756 t.Fatalf("Add: %v", err) 757 } 758 759 s := searcherForTest(t, b) 760 srv := Server{ 761 Searcher: s, 762 Top: Top, 763 HTML: true, 764 HostCustomQueries: map[string]string{ 765 "myproject.io": "r:myproject", 766 }, 767 } 768 769 mux, err := NewMux(&srv) 770 if err != nil { 771 t.Fatalf("NewMux: %v", err) 772 } 773 774 ts := httptest.NewServer(mux) 775 defer ts.Close() 776 777 req, err := http.NewRequest("GET", ts.URL, &bytes.Buffer{}) 778 if err != nil { 779 t.Fatalf("NewRequest: %v", err) 780 } 781 req.Host = "myproject.io" 782 res, err := (&http.Client{}).Do(req) 783 if err != nil { 784 t.Fatalf("Do(%v): %v", req, err) 785 } 786 resultBytes, err := io.ReadAll(res.Body) 787 res.Body.Close() 788 if err != nil { 789 t.Fatalf("ReadAll: %v", err) 790 } 791 792 if got, want := string(resultBytes), "r:myproject"; !strings.Contains(got, want) { 793 t.Fatalf("got %s, want substring %q", got, want) 794 } 795} 796 797func TestDupResult(t *testing.T) { 798 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 799 Name: "name", 800 }) 801 if err != nil { 802 t.Fatalf("NewIndexBuilder: %v", err) 803 } 804 805 for i := 0; i < 2; i++ { 806 if err := b.Add(zoekt.Document{ 807 Name: fmt.Sprintf("file%d", i), 808 Content: []byte("bla"), 809 }); err != nil { 810 t.Fatalf("Add: %v", err) 811 } 812 } 813 s := searcherForTest(t, b) 814 srv := Server{ 815 Searcher: s, 816 Top: Top, 817 HTML: true, 818 } 819 820 mux, err := NewMux(&srv) 821 if err != nil { 822 t.Fatalf("NewMux: %v", err) 823 } 824 825 ts := httptest.NewServer(mux) 826 defer ts.Close() 827 828 req, err := http.NewRequest("GET", ts.URL+"/search?q=bla", &bytes.Buffer{}) 829 if err != nil { 830 t.Fatalf("NewRequest: %v", err) 831 } 832 res, err := (&http.Client{}).Do(req) 833 if err != nil { 834 t.Fatalf("Do(%v): %v", req, err) 835 } 836 resultBytes, err := io.ReadAll(res.Body) 837 res.Body.Close() 838 if err != nil { 839 t.Fatalf("ReadAll: %v", err) 840 } 841 842 if got, want := string(resultBytes), "Duplicate result"; !strings.Contains(got, want) { 843 t.Fatalf("got %s, want substring %q", got, want) 844 } 845} 846 847func TestTruncateLine(t *testing.T) { 848 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 849 Name: "name", 850 }) 851 if err != nil { 852 t.Fatalf("NewIndexBuilder: %v", err) 853 } 854 855 largePadding := bytes.Repeat([]byte{'a'}, 100*1000) // 100kb 856 if err := b.Add(zoekt.Document{ 857 Name: "file", 858 Content: append(append(largePadding, []byte("helloworld")...), largePadding...), 859 }); err != nil { 860 t.Fatalf("Add: %v", err) 861 } 862 s := searcherForTest(t, b) 863 srv := Server{ 864 Searcher: s, 865 Top: Top, 866 HTML: true, 867 } 868 869 mux, err := NewMux(&srv) 870 if err != nil { 871 t.Fatalf("NewMux: %v", err) 872 } 873 874 ts := httptest.NewServer(mux) 875 defer ts.Close() 876 877 req, err := http.NewRequest("GET", ts.URL+"/search?q=helloworld", &bytes.Buffer{}) 878 if err != nil { 879 t.Fatalf("NewRequest: %v", err) 880 } 881 res, err := (&http.Client{}).Do(req) 882 if err != nil { 883 t.Fatalf("Do(%v): %v", req, err) 884 } 885 resultBytes, err := io.ReadAll(res.Body) 886 res.Body.Close() 887 if err != nil { 888 t.Fatalf("ReadAll: %v", err) 889 } 890 891 if got, want := len(resultBytes)/1000, 10; got > want { 892 t.Fatalf("got %dkb response, want <= %dkb", got, want) 893 } 894 result := string(resultBytes) 895 if want := "aa<b>helloworld</b>aa"; !strings.Contains(result, want) { 896 t.Fatalf("got %s, want substring %q", result, want) 897 } 898 if want := "bytes skipped)..."; !strings.Contains(result, want) { 899 t.Fatalf("got %s, want substring %q", result, want) 900 } 901} 902 903func TestHealthz(t *testing.T) { 904 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 905 Name: "name", 906 }) 907 if err != nil { 908 t.Fatalf("NewIndexBuilder: %v", err) 909 } 910 911 for i := 0; i < 2; i++ { 912 if err := b.Add(zoekt.Document{ 913 Name: fmt.Sprintf("file%d", i), 914 Content: []byte("bla"), 915 }); err != nil { 916 t.Fatalf("Add: %v", err) 917 } 918 } 919 s := searcherForTest(t, b) 920 srv := Server{ 921 Searcher: s, 922 Top: Top, 923 HTML: true, 924 } 925 926 mux, err := NewMux(&srv) 927 if err != nil { 928 t.Fatalf("NewMux: %v", err) 929 } 930 931 ts := httptest.NewServer(mux) 932 t.Cleanup(ts.Close) 933 934 req, err := http.NewRequest("GET", ts.URL+"/healthz", nil) 935 if err != nil { 936 t.Fatalf("NewRequest: %v", err) 937 } 938 res, err := http.DefaultClient.Do(req) 939 if err != nil { 940 t.Fatalf("Do(%v): %v", req, err) 941 } 942 943 t.Cleanup(func() { 944 res.Body.Close() 945 }) 946 947 if res.StatusCode != http.StatusOK { 948 t.Fatalf("want 200 status code, got: %v", res.StatusCode) 949 } 950 951 var result zoekt.SearchResult 952 err = json.NewDecoder(res.Body).Decode(&result) 953 if err != nil { 954 t.Fatalf("json.Decode: %v", err) 955 } 956 957 if reflect.DeepEqual(result, zoekt.SearchResult{}) { 958 t.Fatal("empty result in response") 959 } 960} 961 962func TestRPC(t *testing.T) { 963 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 964 Name: "name", 965 URL: "repo-url", 966 CommitURLTemplate: "{{.Version}}", 967 FileURLTemplate: "file-url", 968 LineFragmentTemplate: "#line", 969 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 970 }) 971 if err != nil { 972 t.Fatalf("NewIndexBuilder: %v", err) 973 } 974 if err := b.Add(zoekt.Document{ 975 Name: "f2", 976 Content: []byte("to carry water in the no later bla"), 977 // --------------0123456789012345678901234567890123 978 // --------------0 1 2 3 979 Branches: []string{"master"}, 980 }); err != nil { 981 t.Fatalf("Add: %v", err) 982 } 983 984 s := searcherForTest(t, b) 985 srv := Server{ 986 Searcher: s, 987 RPC: true, 988 Top: Top, 989 } 990 991 mux, err := NewMux(&srv) 992 if err != nil { 993 t.Fatalf("NewMux: %v", err) 994 } 995 996 ts := httptest.NewServer(mux) 997 defer ts.Close() 998 999 endpoint := ts.Listener.Addr().String() 1000 1001 client := stream.NewClient("http://"+endpoint, nil).WithSearcher(rpc.Client(endpoint)) 1002 1003 ctx := context.Background() 1004 q := &query.Substring{Pattern: "water"} 1005 opts := &zoekt.SearchOptions{ChunkMatches: true} 1006 opts.SetDefaults() 1007 results, err := client.Search(ctx, q, opts) 1008 if err != nil { 1009 t.Fatal(err) 1010 } 1011 1012 assertResults(t, results.Files, "f2: to carry water in the no later bla") 1013 1014 // TODO grpc, List, StreamSearch 1015} 1016 1017func assertResults(t *testing.T, files []zoekt.FileMatch, want string) { 1018 t.Helper() 1019 1020 var lines []string 1021 for _, fm := range files { 1022 for _, cm := range fm.ChunkMatches { 1023 lines = append(lines, fmt.Sprintf("%s: %s", fm.FileName, string(cm.Content))) 1024 } 1025 } 1026 sort.Strings(lines) 1027 got := strings.TrimSpace(strings.Join(lines, "\n")) 1028 want = strings.TrimSpace(want) 1029 1030 if d := cmp.Diff(want, got); d != "" { 1031 t.Fatalf("unexpected results (-want, +got):\n%s", d) 1032 } 1033}