blob: 479441b119c62fd6eb9137c8be6bbb0476149a33 [file] [log] [blame]
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package web
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/google/zoekt"
"github.com/google/zoekt/query"
)
// TODO(hanwen): cut & paste from ../ . Should create internal test
// util package.
type memSeeker struct {
data []byte
}
func (s *memSeeker) Close() {}
func (s *memSeeker) Read(off, sz uint32) ([]byte, error) {
return s.data[off : off+sz], nil
}
func (s *memSeeker) Size() (uint32, error) {
return uint32(len(s.data)), nil
}
func (s *memSeeker) Name() string {
return "memSeeker"
}
func searcherForTest(t *testing.T, b *zoekt.IndexBuilder) zoekt.Searcher {
var buf bytes.Buffer
b.Write(&buf)
f := &memSeeker{buf.Bytes()}
searcher, err := zoekt.NewSearcher(f)
if err != nil {
t.Fatalf("NewSearcher: %v", err)
}
return searcher
}
func TestBasic(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
URL: "repo-url",
CommitURLTemplate: "{{.Version}}",
FileURLTemplate: "file-url",
LineFragmentTemplate: "#line",
Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}},
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
if err := b.Add(zoekt.Document{
Name: "f2",
Content: []byte("to carry water in the no later bla"),
// ------------- 0123456789012345678901234567890123
// ------------- 0 1 2 3
Branches: []string{"master"},
}); err != nil {
t.Fatalf("Add: %v", err)
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
nowStr := time.Now().Format("Jan 02, 2006 15:04")
for req, needles := range map[string][]string{
"/": {"from 1 repositories"},
"/search?q=water": {
"href=\"file-url#line",
"carry <b>water</b>",
},
"/search?q=r:": {
"1234\">master",
"Found 1 repositories",
nowStr,
"repo-url\">name",
"1 files (36)",
},
"/search?q=magic": {
`value=magic`,
},
"/robots.txt": {
"disallow: /search",
},
} {
checkNeedles(t, ts, req, needles)
}
}
func TestPrint(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
URL: "repo-url",
CommitURLTemplate: "{{.Version}}",
FileURLTemplate: "file-url",
LineFragmentTemplate: "line",
Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}},
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
if err := b.Add(zoekt.Document{
Name: "f2",
Content: []byte("to carry water in the no later bla"),
Branches: []string{"master"},
}); err != nil {
t.Fatalf("Add: %v", err)
}
if err := b.Add(zoekt.Document{
Name: "dir/f2",
Content: []byte("blabla"),
Branches: []string{"master"},
}); err != nil {
t.Fatalf("Add: %v", err)
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
Print: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
for req, needles := range map[string][]string{
"/print?q=bla&r=name&f=f2": {
`pre id="l1" class="inline-pre"><span class="noselect"><a href="#l1">`,
},
} {
checkNeedles(t, ts, req, needles)
}
}
func TestPrintDefault(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
URL: "repo-url",
Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}},
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
if err := b.Add(zoekt.Document{
Name: "f2",
Content: []byte("to carry water in the no later bla"),
Branches: []string{"master"},
}); err != nil {
t.Fatalf("Add: %v", err)
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
for req, needles := range map[string][]string{
"/search?q=water": {
`href="print?`,
},
} {
checkNeedles(t, ts, req, needles)
}
}
func checkNeedles(t *testing.T, ts *httptest.Server, req string, needles []string) {
res, err := http.Get(ts.URL + req)
if err != nil {
t.Fatal(err)
}
resultBytes, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
result := string(resultBytes)
for _, want := range needles {
if !strings.Contains(result, want) {
t.Errorf("query %q: result did not have %q: %s", req, want, result)
}
}
if notWant := "crashed"; strings.Contains(result, notWant) {
t.Errorf("result has %q: %s", notWant, result)
}
if notWant := "bytes skipped)..."; strings.Contains(result, notWant) {
t.Errorf("result has %q: %s", notWant, result)
}
}
type crashSearcher struct {
zoekt.Searcher
}
func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) {
res := zoekt.SearchResult{}
res.Stats.Crashes = 1
return &res, nil
}
func TestCrash(t *testing.T) {
srv := Server{
Searcher: &crashSearcher{},
Top: Top,
HTML: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
res, err := http.Get(ts.URL + "/search?q=water")
if err != nil {
t.Fatal(err)
}
resultBytes, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Fatal(err)
}
result := string(resultBytes)
if want := "1 shards crashed"; !strings.Contains(result, want) {
t.Errorf("result did not have %q: %s", want, result)
}
}
func TestHostCustomization(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
if err := b.Add(zoekt.Document{
Name: "file",
Content: []byte("bla"),
}); err != nil {
t.Fatalf("Add: %v", err)
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
HostCustomQueries: map[string]string{
"myproject.io": "r:myproject",
},
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
req, err := http.NewRequest("GET", ts.URL, &bytes.Buffer{})
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
req.Host = "myproject.io"
res, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Do(%v): %v", req, err)
}
resultBytes, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Fatalf("ReadAll: %v", err)
}
if got, want := string(resultBytes), "r:myproject"; !strings.Contains(got, want) {
t.Fatalf("got %s, want substring %q", got, want)
}
}
func TestDupResult(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
for i := 0; i < 2; i++ {
if err := b.Add(zoekt.Document{
Name: fmt.Sprintf("file%d", i),
Content: []byte("bla"),
}); err != nil {
t.Fatalf("Add: %v", err)
}
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
req, err := http.NewRequest("GET", ts.URL+"/search?q=bla", &bytes.Buffer{})
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
res, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Do(%v): %v", req, err)
}
resultBytes, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Fatalf("ReadAll: %v", err)
}
if got, want := string(resultBytes), "Duplicate result"; !strings.Contains(got, want) {
t.Fatalf("got %s, want substring %q", got, want)
}
}
func TestTruncateLine(t *testing.T) {
b, err := zoekt.NewIndexBuilder(&zoekt.Repository{
Name: "name",
})
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
largePadding := bytes.Repeat([]byte{'a'}, 100*1000) // 100kb
if err := b.Add(zoekt.Document{
Name: "file",
Content: append(append(largePadding, []byte("helloworld")...), largePadding...),
}); err != nil {
t.Fatalf("Add: %v", err)
}
s := searcherForTest(t, b)
srv := Server{
Searcher: s,
Top: Top,
HTML: true,
}
mux, err := NewMux(&srv)
if err != nil {
t.Fatalf("NewMux: %v", err)
}
ts := httptest.NewServer(mux)
defer ts.Close()
req, err := http.NewRequest("GET", ts.URL+"/search?q=helloworld", &bytes.Buffer{})
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
res, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Do(%v): %v", req, err)
}
resultBytes, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Fatalf("ReadAll: %v", err)
}
if got, want := len(resultBytes)/1000, 10; got > want {
t.Fatalf("got %dkb response, want <= %dkb", got, want)
}
result := string(resultBytes)
if want := "aa<b>helloworld</b>aa"; !strings.Contains(result, want) {
t.Fatalf("got %s, want substring %q", result, want)
}
if want := "bytes skipped)..."; !strings.Contains(result, want) {
t.Fatalf("got %s, want substring %q", result, want)
}
}