fork of https://github.com/sourcegraph/zoekt
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 zoekt // import "github.com/sourcegraph/zoekt"
16
17import (
18 "bytes"
19 _ "embed"
20 "encoding/gob"
21 "fmt"
22 "math/rand"
23 "reflect"
24 "testing"
25 "testing/quick"
26 "time"
27
28 "github.com/google/go-cmp/cmp"
29 "github.com/google/go-cmp/cmp/cmpopts"
30 "google.golang.org/protobuf/proto"
31
32 v1 "github.com/sourcegraph/zoekt/grpc/v1"
33)
34
35func TestProtoRoundtrip(t *testing.T) {
36 t.Run("FileMatch", func(t *testing.T) {
37 f := func(f1 FileMatch) bool {
38 p1 := f1.ToProto()
39 f2 := FileMatchFromProto(p1)
40 return reflect.DeepEqual(f1, f2)
41 }
42 if err := quick.Check(f, nil); err != nil {
43 t.Fatal(err)
44 }
45 })
46
47 t.Run("ChunkMatch", func(t *testing.T) {
48 f := func(f1 ChunkMatch) bool {
49 p1 := f1.ToProto()
50 f2 := ChunkMatchFromProto(p1)
51 return reflect.DeepEqual(f1, f2)
52 }
53 if err := quick.Check(f, nil); err != nil {
54 t.Fatal(err)
55 }
56 })
57
58 t.Run("Range", func(t *testing.T) {
59 f := func(f1 Range) bool {
60 p1 := f1.ToProto()
61 f2 := RangeFromProto(p1)
62 return reflect.DeepEqual(f1, f2)
63 }
64 if err := quick.Check(f, nil); err != nil {
65 t.Fatal(err)
66 }
67 })
68
69 t.Run("Location", func(t *testing.T) {
70 f := func(f1 Range) bool {
71 p1 := f1.ToProto()
72 f2 := RangeFromProto(p1)
73 return reflect.DeepEqual(f1, f2)
74 }
75 if err := quick.Check(f, nil); err != nil {
76 t.Fatal(err)
77 }
78 })
79
80 t.Run("LineMatch", func(t *testing.T) {
81 f := func(f1 LineMatch) bool {
82 p1 := f1.ToProto()
83 f2 := LineMatchFromProto(p1)
84 return reflect.DeepEqual(f1, f2)
85 }
86 if err := quick.Check(f, nil); err != nil {
87 t.Fatal(err)
88 }
89 })
90
91 t.Run("Symbol", func(t *testing.T) {
92 f := func(f1 *Symbol) bool {
93 p1 := f1.ToProto()
94 f2 := SymbolFromProto(p1)
95 return reflect.DeepEqual(f1, f2)
96 }
97 if err := quick.Check(f, nil); err != nil {
98 t.Fatal(err)
99 }
100 })
101
102 t.Run("FlushReson", func(t *testing.T) {
103 f := func(f1 FlushReason) bool {
104 p1 := f1.ToProto()
105 f2 := FlushReasonFromProto(p1)
106 return reflect.DeepEqual(f1.String(), f2.String())
107 }
108 if err := quick.Check(f, nil); err != nil {
109 t.Fatal(err)
110 }
111 })
112
113 t.Run("Stats", func(t *testing.T) {
114 f := func(f1 Stats) bool {
115 p1 := f1.ToProto()
116 f2 := StatsFromProto(p1)
117 return reflect.DeepEqual(f1, f2)
118 }
119 if err := quick.Check(f, nil); err != nil {
120 t.Fatal(err)
121 }
122 })
123
124 t.Run("Progress", func(t *testing.T) {
125 f := func(f1 Progress) bool {
126 p1 := f1.ToProto()
127 f2 := ProgressFromProto(p1)
128 return reflect.DeepEqual(f1, f2)
129 }
130 if err := quick.Check(f, nil); err != nil {
131 t.Fatal(err)
132 }
133 })
134
135 t.Run("SearchResult", func(t *testing.T) {
136 f := func(f1 *SearchResult) bool {
137 p1 := f1.ToProto()
138 f2 := SearchResultFromProto(p1)
139 return reflect.DeepEqual(f1, f2)
140 }
141 if err := quick.Check(f, nil); err != nil {
142 t.Fatal(err)
143 }
144 })
145
146 t.Run("Repository", func(t *testing.T) {
147 f := func(f1 *Repository) bool {
148 p1 := f1.ToProto()
149 f2 := RepositoryFromProto(p1)
150 if diff := cmp.Diff(f1, &f2, cmpopts.IgnoreUnexported(Repository{})); diff != "" {
151 fmt.Printf("got diff: %s", diff)
152 return false
153 }
154 return true
155 }
156 if err := quick.Check(f, nil); err != nil {
157 t.Fatal(err)
158 }
159 })
160
161 t.Run("IndexMetadata", func(t *testing.T) {
162 f := func(f1 *IndexMetadata) bool {
163 p1 := f1.ToProto()
164 f2 := IndexMetadataFromProto(p1)
165 if diff := cmp.Diff(f1, &f2); diff != "" {
166 fmt.Printf("got diff: %s", diff)
167 return false
168 }
169 return true
170 }
171 if err := quick.Check(f, nil); err != nil {
172 t.Fatal(err)
173 }
174 })
175
176 t.Run("RepoStats", func(t *testing.T) {
177 f := func(f1 RepoStats) bool {
178 p1 := f1.ToProto()
179 f2 := RepoStatsFromProto(p1)
180 if diff := cmp.Diff(f1, f2); diff != "" {
181 fmt.Printf("got diff: %s", diff)
182 return false
183 }
184 return true
185 }
186 if err := quick.Check(f, nil); err != nil {
187 t.Fatal(err)
188 }
189 })
190
191 t.Run("RepoListEntry", func(t *testing.T) {
192 r1 := &RepoListEntry{
193 Repository: Repository{
194 ID: 1,
195 Name: "test",
196 URL: "testurl",
197 Source: "testsource",
198 Branches: []RepositoryBranch{{
199 Name: "branch",
200 Version: "version",
201 }},
202 SubRepoMap: map[string]*Repository{
203 "test": {
204 ID: 2,
205 Name: "subrepo",
206 Branches: []RepositoryBranch{},
207 SubRepoMap: map[string]*Repository{},
208 FileTombstones: map[string]struct{}{},
209 },
210 },
211 CommitURLTemplate: "committemplate",
212 FileURLTemplate: "fileurltemplate",
213 LineFragmentTemplate: "linefragmenttemplate",
214 priority: 10,
215 RawConfig: map[string]string{
216 "a": "b",
217 },
218 Rank: 32,
219 IndexOptions: "indexoptions",
220 HasSymbols: true,
221 Tombstone: false,
222 LatestCommitDate: time.Now(),
223 FileTombstones: map[string]struct{}{
224 "test1": {},
225 },
226 },
227 IndexMetadata: IndexMetadata{
228 IndexFormatVersion: 32,
229 IndexFeatureVersion: 42,
230 IndexMinReaderVersion: 52,
231 IndexTime: time.Now(),
232 PlainASCII: true,
233 LanguageMap: map[string]uint16{
234 "go": 1,
235 },
236 ZoektVersion: "32",
237 ID: "52",
238 },
239 Stats: RepoStats{
240 Repos: 3,
241 Shards: 4,
242 Documents: 5,
243 IndexBytes: 6,
244 ContentBytes: 7,
245 NewLinesCount: 8,
246 DefaultBranchNewLinesCount: 9,
247 OtherBranchesNewLinesCount: 10,
248 },
249 }
250
251 p1 := r1.ToProto()
252 r2 := RepoListEntryFromProto(p1)
253 if diff := cmp.Diff(r1, r2, cmpopts.IgnoreUnexported(Repository{})); diff != "" {
254 t.Fatalf("got diff: %s", diff)
255 }
256 })
257
258 t.Run("RepositoryBranch", func(t *testing.T) {
259 f := func(f1 RepositoryBranch) bool {
260 p1 := f1.ToProto()
261 f2 := RepositoryBranchFromProto(p1)
262 if diff := cmp.Diff(f1, f2); diff != "" {
263 fmt.Printf("got diff: %s", diff)
264 return false
265 }
266 return true
267 }
268 if err := quick.Check(f, nil); err != nil {
269 t.Fatal(err)
270 }
271 })
272
273 t.Run("MinimalRepoListEntry", func(t *testing.T) {
274 f := func(f1 MinimalRepoListEntry) bool {
275 p1 := f1.ToProto()
276 f2 := MinimalRepoListEntryFromProto(p1)
277 if diff := cmp.Diff(f1, f2); diff != "" {
278 fmt.Printf("got diff: %s", diff)
279 return false
280 }
281 return true
282 }
283 if err := quick.Check(f, nil); err != nil {
284 t.Fatal(err)
285 }
286 })
287
288 t.Run("ListOptions", func(t *testing.T) {
289 f := func(f1 *ListOptions) bool {
290 p1 := f1.ToProto()
291 f2 := ListOptionsFromProto(p1)
292 if diff := cmp.Diff(f1, f2); diff != "" {
293 fmt.Printf("got diff: %s", diff)
294 return false
295 }
296 return true
297 }
298 if err := quick.Check(f, nil); err != nil {
299 t.Fatal(err)
300 }
301 })
302
303 t.Run("SearchOptions", func(t *testing.T) {
304 f := func(f1 *SearchOptions) bool {
305 if f1 != nil {
306 // Ignore deprecated and unimplemented fields
307 f1.ShardMaxImportantMatch = 0
308 f1.TotalMaxImportantMatch = 0
309 f1.SpanContext = nil
310 }
311 p1 := f1.ToProto()
312 f2 := SearchOptionsFromProto(p1)
313 if diff := cmp.Diff(f1, f2); diff != "" {
314 fmt.Printf("got diff: %s", diff)
315 return false
316 }
317 return true
318 }
319 if err := quick.Check(f, nil); err != nil {
320 t.Fatal(err)
321 }
322 })
323}
324
325func (*IndexMetadata) Generate(r *rand.Rand, _ int) reflect.Value {
326 indexTime := time.Now().Add(time.Duration(r.Int63n(1000)) * time.Hour)
327 var i IndexMetadata
328 i.IndexFormatVersion = gen(i.IndexFormatVersion, r)
329 i.IndexFeatureVersion = gen(i.IndexFeatureVersion, r)
330 i.IndexMinReaderVersion = gen(i.IndexMinReaderVersion, r)
331 i.IndexTime = indexTime
332 i.PlainASCII = gen(i.PlainASCII, r)
333 i.LanguageMap = gen(i.LanguageMap, r)
334 i.ZoektVersion = gen(i.ZoektVersion, r)
335 i.ID = gen(i.ID, r)
336 return reflect.ValueOf(&i)
337}
338
339func (*Repository) Generate(rng *rand.Rand, _ int) reflect.Value {
340 latestCommitDate := time.Now().Add(time.Duration(rng.Int63n(1000)) * time.Hour)
341 var r Repository
342 v := &Repository{
343 ID: gen(r.ID, rng),
344 Name: gen(r.Name, rng),
345 URL: gen(r.URL, rng),
346 Source: gen(r.Source, rng),
347 Branches: gen(r.Branches, rng),
348 SubRepoMap: map[string]*Repository{},
349 CommitURLTemplate: gen(r.CommitURLTemplate, rng),
350 FileURLTemplate: gen(r.FileURLTemplate, rng),
351 LineFragmentTemplate: gen(r.LineFragmentTemplate, rng),
352 priority: gen(r.priority, rng),
353 RawConfig: gen(r.RawConfig, rng),
354 Rank: gen(r.Rank, rng),
355 IndexOptions: gen(r.IndexOptions, rng),
356 HasSymbols: gen(r.HasSymbols, rng),
357 Tombstone: gen(r.Tombstone, rng),
358 LatestCommitDate: latestCommitDate,
359 FileTombstones: gen(r.FileTombstones, rng),
360 }
361 return reflect.ValueOf(v)
362}
363
364func (RepoListField) Generate(rng *rand.Rand, _ int) reflect.Value {
365 switch rng.Int() % 3 {
366 case 0:
367 return reflect.ValueOf(RepoListField(RepoListFieldRepos))
368 case 1:
369 return reflect.ValueOf(RepoListField(RepoListFieldMinimal))
370 default:
371 return reflect.ValueOf(RepoListField(RepoListFieldReposMap))
372 }
373}
374
375func gen[T any](sample T, r *rand.Rand) T {
376 var t T
377 v, _ := quick.Value(reflect.TypeOf(t), r)
378 return v.Interface().(T)
379}
380
381// This is a real search result that is intended to be a reasonable representative
382// for serialization benchmarks.
383// Generated by modifying the code to dump the proto to a file, then running a
384// fairly broadly-matching search.
385var (
386 //go:embed testdata/search_result_1.pb
387 exampleSearchResultBytes []byte
388
389 // The proto struct representation of the search result
390 exampleSearchResultProto = func() *v1.SearchResponse {
391 sr := new(v1.SearchResponse)
392 err := proto.Unmarshal(exampleSearchResultBytes, sr)
393 if err != nil {
394 panic(err)
395 }
396 return sr
397 }()
398
399 // The non-proto struct representation of the search result
400 exampleSearchResultGo = SearchResultFromProto(exampleSearchResultProto)
401)
402
403func BenchmarkGobRoundtrip(b *testing.B) {
404 for _, count := range []int{1, 100, 1000, 10000} {
405 b.Run(fmt.Sprintf("count=%d", count), func(b *testing.B) {
406 for i := 0; i < b.N; i++ {
407 var buf bytes.Buffer
408 enc := gob.NewEncoder(&buf)
409
410 for i := 0; i < count; i++ {
411 err := enc.Encode(exampleSearchResultGo)
412 if err != nil {
413 panic(err)
414 }
415
416 }
417
418 dec := gob.NewDecoder(&buf)
419 for i := 0; i < count; i++ {
420 var res SearchResult
421 err := dec.Decode(&res)
422 if err != nil {
423 panic(err)
424 }
425 }
426 }
427 })
428 }
429}
430
431func BenchmarkProtoRoundtrip(b *testing.B) {
432 for _, count := range []int{1, 100, 1000, 10000} {
433 b.Run(fmt.Sprintf("count=%d", count), func(b *testing.B) {
434 for i := 0; i < b.N; i++ {
435 buffers := make([][]byte, 0, count)
436 for i := 0; i < count; i++ {
437 buf, err := proto.Marshal(exampleSearchResultProto)
438 if err != nil {
439 b.Fatal(err)
440 }
441 buffers = append(buffers, buf)
442 }
443
444 for _, buf := range buffers {
445 res := new(v1.SearchResponse)
446 err := proto.Unmarshal(buf, res)
447 if err != nil {
448 b.Fatal(err)
449 }
450 }
451 }
452 })
453 }
454}