fork of https://github.com/sourcegraph/zoekt
1// Copyright 2021 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 zoekt // import "github.com/sourcegraph/zoekt"
16
17import (
18 "bytes"
19 "encoding/gob"
20 "reflect"
21 "strings"
22 "testing"
23 "time"
24
25 "github.com/grafana/regexp"
26)
27
28/*
29BenchmarkMinimalRepoListEncodings/slice-8 570 2145665 ns/op 753790 bytes 3981 B/op 0 allocs/op
30BenchmarkMinimalRepoListEncodings/map-8 360 3337522 ns/op 740778 bytes 377777 B/op 13002 allocs/op
31*/
32func BenchmarkMinimalRepoListEncodings(b *testing.B) {
33 size := uint32(13000) // 2021-06-24 rough estimate of number of repos on a replica.
34
35 type Slice struct {
36 ID uint32
37 HasSymbols bool
38 Branches []RepositoryBranch
39 IndexTimeUnix int64
40 }
41
42 branches := []RepositoryBranch{{Name: "HEAD", Version: strings.Repeat("a", 40)}}
43 mapData := make(map[uint32]*MinimalRepoListEntry, size)
44 sliceData := make([]Slice, 0, size)
45 indexTime := time.Now().Unix()
46
47 for id := uint32(1); id <= size; id++ {
48 mapData[id] = &MinimalRepoListEntry{
49 HasSymbols: true,
50 Branches: branches,
51 IndexTimeUnix: indexTime,
52 }
53 sliceData = append(sliceData, Slice{
54 ID: id,
55 HasSymbols: true,
56 Branches: branches,
57 IndexTimeUnix: indexTime,
58 })
59 }
60
61 b.Run("slice", benchmarkEncoding(sliceData))
62
63 b.Run("map", benchmarkEncoding(mapData))
64}
65
66func benchmarkEncoding(data interface{}) func(*testing.B) {
67 return func(b *testing.B) {
68 b.Helper()
69
70 var buf bytes.Buffer
71 enc := gob.NewEncoder(&buf)
72 err := enc.Encode(data)
73 if err != nil {
74 b.Fatal(err)
75 }
76
77 b.ReportAllocs()
78 b.ResetTimer()
79 b.ReportMetric(float64(buf.Len()), "bytes")
80 for i := 0; i < b.N; i++ {
81 _ = enc.Encode(data)
82 buf.Reset()
83 }
84 }
85}
86
87func TestSizeBytesSearchResult(t *testing.T) {
88 sr := SearchResult{
89 Stats: Stats{}, // 129 bytes
90 Progress: Progress{}, // 16 bytes
91 Files: []FileMatch{{ // 24 bytes + 460 bytes
92 Score: 0, // 8 bytes
93 Debug: "", // 16 bytes
94 FileName: "", // 16 bytes
95 Repository: "", // 16 bytes
96 Branches: nil, // 24 bytes
97 LineMatches: nil, // 24 bytes
98 ChunkMatches: []ChunkMatch{{ // 24 bytes + 208 bytes (see TestSizeByteChunkMatches)
99 Content: []byte("foo"),
100 ContentStart: Location{},
101 FileName: false,
102 Ranges: []Range{{}},
103 SymbolInfo: []*Symbol{{}},
104 Score: 0,
105 DebugScore: "",
106 }},
107 RepositoryID: 0, // 4 bytes
108 RepositoryPriority: 0, // 8 bytes
109 Content: nil, // 24 bytes
110 Checksum: nil, // 24 bytes
111 Language: "", // 16 bytes
112 SubRepositoryName: "", // 16 bytes
113 SubRepositoryPath: "", // 16 bytes
114 Version: "", // 16 bytes
115 }},
116 RepoURLs: nil, // 48 bytes
117 LineFragments: nil, // 48 bytes
118 }
119
120 var wantBytes uint64 = 725
121 if sr.SizeBytes() != wantBytes {
122 t.Fatalf("want %d, got %d", wantBytes, sr.SizeBytes())
123 }
124}
125
126func TestSizeBytesChunkMatches(t *testing.T) {
127 cm := ChunkMatch{
128 Content: []byte("foo"), // 24 + 3 bytes
129 ContentStart: Location{}, // 12 bytes
130 FileName: false, // 1 byte
131 Ranges: []Range{{}}, // 24 bytes (slice header) + 24 bytes (content)
132 SymbolInfo: []*Symbol{{}}, // 24 bytes (slice header) + 4 * 16 bytes (string header) + 8 bytes (pointer)
133 Score: 0, // 8 byte
134 DebugScore: "", // 16 bytes (string header)
135 }
136
137 var wantBytes uint64 = 208
138 if cm.sizeBytes() != wantBytes {
139 t.Fatalf("want %d, got %d", wantBytes, cm.sizeBytes())
140 }
141}
142
143func TestMatchSize(t *testing.T) {
144 cases := []struct {
145 v any
146 size int
147 }{{
148 v: FileMatch{},
149 size: 256,
150 }, {
151 v: ChunkMatch{},
152 size: 112,
153 }, {
154 v: candidateMatch{},
155 size: 80,
156 }, {
157 v: candidateChunk{},
158 size: 40,
159 }}
160 for _, c := range cases {
161 got := reflect.TypeOf(c.v).Size()
162 if int(got) != c.size {
163 t.Errorf(`sizeof struct %T has changed from %d to %d.
164These are match structs that occur a lot in memory, so we optimize size.
165When changing, please ensure there isn't unnecessary padding via the
166tool fieldalignment then update this test.`, c.v, c.size, got)
167 }
168 }
169}
170
171func TestSearchOptions_String(t *testing.T) {
172 // To make sure we don't forget to update the string implementation we use
173 // reflection to generate a SearchOptions with every field being non
174 // default. We then check that the field name is present in the output.
175 opts := SearchOptions{}
176 var fieldNames []string
177 rv := reflect.ValueOf(&opts).Elem()
178 for i := 0; i < rv.NumField(); i++ {
179 f := rv.Field(i)
180 name := rv.Type().Field(i).Name
181 fieldNames = append(fieldNames, name)
182 switch f.Kind() {
183 case reflect.Bool:
184 f.SetBool(true)
185 case reflect.Int:
186 f.SetInt(1)
187 case reflect.Int64:
188 f.SetInt(1)
189 case reflect.Float64:
190 f.SetFloat(1)
191 case reflect.Map:
192 // Only map is SpanContext
193 f.Set(reflect.ValueOf(map[string]string{"key": "value"}))
194 default:
195 t.Fatalf("add support for %s field (%s)", f.Kind(), name)
196 }
197 }
198
199 s := opts.String()
200 for _, name := range fieldNames {
201 found, err := regexp.MatchString("\\b"+regexp.QuoteMeta(name)+"\\b", s)
202 if err != nil {
203 t.Fatal(err)
204 }
205 if !found {
206 t.Errorf("could not find field %q in string output of SearchOptions:\n%s", name, s)
207 }
208 }
209
210 webDefaults := SearchOptions{
211 MaxWallTime: 10 * time.Second,
212 }
213 webDefaults.SetDefaults()
214
215 // Now we hand craft a few corner and common cases
216 cases := []struct {
217 Opts SearchOptions
218 Want string
219 }{{
220 // Empty
221 Opts: SearchOptions{},
222 Want: "zoekt.SearchOptions{ }",
223 }, {
224 // healthz options
225 Opts: SearchOptions{ShardMaxMatchCount: 1, TotalMaxMatchCount: 1, MaxDocDisplayCount: 1},
226 Want: "zoekt.SearchOptions{ ShardMaxMatchCount=1 TotalMaxMatchCount=1 MaxDocDisplayCount=1 }",
227 }, {
228 // zoekt-webserver defaults
229 Opts: webDefaults,
230 Want: "zoekt.SearchOptions{ ShardMaxMatchCount=100000 TotalMaxMatchCount=1000000 MaxWallTime=10s }",
231 }}
232
233 for _, tc := range cases {
234 got := tc.Opts.String()
235 if got != tc.Want {
236 t.Errorf("unexpected String for %#v:\ngot: %s\nwant: %s", tc.Opts, got, tc.Want)
237 }
238 }
239}
240
241func TestRepositoryMergeMutable(t *testing.T) {
242 a := Repository{
243 ID: 0,
244 Name: "name",
245 Branches: []RepositoryBranch{
246 {
247 Name: "branchName",
248 Version: "branchVersion",
249 },
250 },
251 RawConfig: nil,
252 URL: "url",
253 CommitURLTemplate: "commitUrlTemplate",
254 FileURLTemplate: "fileUrlTemplate",
255 LineFragmentTemplate: "lineFragmentTemplate",
256 }
257
258 t.Run("different ID", func(t *testing.T) {
259 b := a
260 b.ID = 1
261 mutated, err := a.MergeMutable(&b)
262 if err == nil {
263 t.Fatalf("want err, got mutated=%t", mutated)
264 }
265 })
266 t.Run("different Name", func(t *testing.T) {
267 b := a
268 b.Name = "otherName"
269 mutated, err := a.MergeMutable(&b)
270 if err == nil {
271 t.Fatalf("want err, got mutated=%t", mutated)
272 }
273 })
274 t.Run("different Branches", func(t *testing.T) {
275 b := a
276 b.Branches = []RepositoryBranch{
277 {
278 Name: "otherBranchName",
279 Version: "branchVersion",
280 },
281 }
282 mutated, err := a.MergeMutable(&b)
283 if err == nil {
284 t.Fatalf("want err, got mutated=%t", mutated)
285 }
286 })
287 t.Run("different RawConfig", func(t *testing.T) {
288 b := a
289 b.RawConfig = map[string]string{"foo": "bar"}
290 mutated, err := a.MergeMutable(&b)
291 if err != nil {
292 t.Fatalf("got err %v", err)
293 }
294 if !mutated {
295 t.Fatalf("want mutated=true, got false")
296 }
297 if !reflect.DeepEqual(a.RawConfig, b.RawConfig) {
298 t.Fatalf("got different RawConfig, %v vs %v", a.RawConfig, b.RawConfig)
299 }
300 })
301 t.Run("different URL", func(t *testing.T) {
302 b := a
303 b.URL = "otherURL"
304 mutated, err := a.MergeMutable(&b)
305 if err != nil {
306 t.Fatalf("got err %v", err)
307 }
308 if !mutated {
309 t.Fatalf("want mutated=true, got false")
310 }
311 if a.URL != b.URL {
312 t.Fatalf("got different URL, %s vs %s", a.URL, b.URL)
313 }
314 })
315 t.Run("different CommitURLTemplate", func(t *testing.T) {
316 b := a
317 b.CommitURLTemplate = "otherCommitUrlTemplate"
318 mutated, err := a.MergeMutable(&b)
319 if err != nil {
320 t.Fatalf("got err %v", err)
321 }
322 if !mutated {
323 t.Fatalf("want mutated=true, got false")
324 }
325 if a.CommitURLTemplate != b.CommitURLTemplate {
326 t.Fatalf("got different CommitURLTemplate, %s vs %s", a.CommitURLTemplate, b.CommitURLTemplate)
327 }
328 })
329 t.Run("different FileURLTemplate", func(t *testing.T) {
330 b := a
331 b.FileURLTemplate = "otherFileUrlTemplate"
332 mutated, err := a.MergeMutable(&b)
333 if err != nil {
334 t.Fatalf("got err %v", err)
335 }
336 if !mutated {
337 t.Fatalf("want mutated=true, got false")
338 }
339 if a.FileURLTemplate != b.FileURLTemplate {
340 t.Fatalf("got different FileURLTemplate, %s vs %s", a.FileURLTemplate, b.FileURLTemplate)
341 }
342 })
343 t.Run("different LineFragmentTemplate", func(t *testing.T) {
344 b := a
345 b.LineFragmentTemplate = "otherLineFragmentTemplate"
346 mutated, err := a.MergeMutable(&b)
347 if err != nil {
348 t.Fatalf("got err %v", err)
349 }
350 if !mutated {
351 t.Fatalf("want mutated=true, got false")
352 }
353 if a.LineFragmentTemplate != b.LineFragmentTemplate {
354 t.Fatalf("got different LineFragmentTemplate, %s vs %s", a.LineFragmentTemplate, b.LineFragmentTemplate)
355 }
356 })
357 t.Run("all same", func(t *testing.T) {
358 b := a
359 mutated, err := a.MergeMutable(&b)
360 if err != nil {
361 t.Fatalf("got err %v", err)
362 }
363 if mutated {
364 t.Fatalf("want mutated=false, got true")
365 }
366 if !reflect.DeepEqual(a, b) {
367 t.Fatalf("got different Repository, %v vs %v", a, b)
368 }
369 })
370}