blob: bb1a50c84e50a0abccaced6091472099f3febcce [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 shards
import (
"bytes"
"context"
"fmt"
"log"
"os"
"runtime"
"testing"
"time"
"github.com/google/zoekt"
"github.com/google/zoekt/query"
)
type crashSearcher struct{}
func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) {
panic("search")
}
func (s *crashSearcher) List(ctx context.Context, q query.Q) (*zoekt.RepoList, error) {
panic("list")
}
func (s *crashSearcher) Stats() (*zoekt.RepoStats, error) {
return &zoekt.RepoStats{}, nil
}
func (s *crashSearcher) Close() {}
func (s *crashSearcher) String() string { return "crashSearcher" }
func TestCrashResilience(t *testing.T) {
out := &bytes.Buffer{}
log.SetOutput(out)
defer log.SetOutput(os.Stderr)
ss := newShardedSearcher(2)
ss.shards = map[string]rankedShard{
"x": rankedShard{Searcher: &crashSearcher{}},
}
q := &query.Substring{Pattern: "hoi"}
opts := &zoekt.SearchOptions{}
if res, err := ss.Search(context.Background(), q, opts); err != nil {
t.Fatalf("Search: %v", err)
} else if res.Stats.Crashes != 1 {
t.Errorf("got stats %#v, want crashes = 1", res.Stats)
}
if res, err := ss.List(context.Background(), q); err != nil {
t.Fatalf("List: %v", err)
} else if res.Crashes != 1 {
t.Errorf("got result %#v, want crashes = 1", res)
}
}
type rankSearcher struct {
rank uint16
}
func (s *rankSearcher) Close() {
}
func (s *rankSearcher) String() string {
return ""
}
func (s *rankSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) {
select {
case <-ctx.Done():
return &zoekt.SearchResult{}, nil
default:
}
// Ugly, but without sleep it's too fast, and we can't
// simulate the cutoff.
time.Sleep(time.Millisecond)
return &zoekt.SearchResult{
Files: []zoekt.FileMatch{
{
FileName: fmt.Sprintf("f%d", s.rank),
Score: float64(s.rank),
},
},
Stats: zoekt.Stats{
MatchCount: 1,
},
}, nil
}
func (s *rankSearcher) List(ctx context.Context, q query.Q) (*zoekt.RepoList, error) {
return &zoekt.RepoList{
Repos: []*zoekt.RepoListEntry{
{Repository: zoekt.Repository{Rank: s.rank}},
},
}, nil
}
func TestOrderByShard(t *testing.T) {
ss := newShardedSearcher(1)
n := 10 * runtime.GOMAXPROCS(0)
for i := 0; i < n; i++ {
ss.replace(fmt.Sprintf("shard%d", i),
&rankSearcher{
rank: uint16(i),
})
}
if res, err := ss.Search(context.Background(), &query.Substring{Pattern: "bla"}, &zoekt.SearchOptions{}); err != nil {
t.Errorf("Search: %v", err)
} else if len(res.Files) != n {
t.Fatalf("empty options: got %d results, want %d", len(res.Files), n)
}
opts := zoekt.SearchOptions{
TotalMaxMatchCount: 3,
}
res, err := ss.Search(context.Background(), &query.Substring{Pattern: "bla"}, &opts)
if err != nil {
t.Errorf("Search: %v", err)
}
if len(res.Files) < opts.TotalMaxMatchCount {
t.Errorf("got %d results, want %d", len(res.Files), opts.TotalMaxMatchCount)
}
if len(res.Files) == n {
t.Errorf("got %d results, want < %d", len(res.Files), n)
}
for i, f := range res.Files {
rev := n - 1 - i
want := fmt.Sprintf("f%d", rev)
got := f.FileName
if got != want {
t.Logf("%d: got %q, want %q", i, got, want)
}
}
}
type memSeeker struct {
data []byte
}
func (s *memSeeker) Name() string {
return "memseeker"
}
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 TestUnloadIndex(t *testing.T) {
b, err := zoekt.NewIndexBuilder(nil)
if err != nil {
t.Fatalf("NewIndexBuilder: %v", err)
}
for i, d := range []zoekt.Document{{
Name: "filename",
Content: []byte("needle needle needle"),
}} {
if err := b.Add(d); err != nil {
t.Fatalf("Add %d: %v", i, err)
}
}
var buf bytes.Buffer
b.Write(&buf)
indexBytes := buf.Bytes()
indexFile := &memSeeker{indexBytes}
searcher, err := zoekt.NewSearcher(indexFile)
if err != nil {
t.Fatalf("NewSearcher: %v", err)
}
ss := newShardedSearcher(2)
ss.replace("key", searcher)
var opts zoekt.SearchOptions
q := &query.Substring{Pattern: "needle"}
res, err := ss.Search(context.Background(), q, &opts)
if err != nil {
t.Fatalf("Search(%s): %v", q, err)
}
forbidden := byte(29)
for i := range indexBytes {
// non-ASCII
indexBytes[i] = forbidden
}
for _, f := range res.Files {
if bytes.Contains(f.Content, []byte{forbidden}) {
t.Errorf("found %d in content %q", forbidden, f.Content)
}
if bytes.Contains(f.Checksum, []byte{forbidden}) {
t.Errorf("found %d in checksum %q", forbidden, f.Checksum)
}
for _, l := range f.LineMatches {
if bytes.Contains(l.Line, []byte{forbidden}) {
t.Errorf("found %d in line %q", forbidden, l.Line)
}
}
}
}