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 var repoURLs map[string]string
138 var lineFragments map[string]string
139
140 if f1 != nil {
141 repoURLs = f1.RepoURLs
142 lineFragments = f1.LineFragments
143 }
144
145 p1 := f1.ToProto()
146 f2 := SearchResultFromProto(p1, repoURLs, lineFragments)
147
148 return reflect.DeepEqual(f1, f2)
149 }
150 if err := quick.Check(f, nil); err != nil {
151 t.Fatal(err)
152 }
153 })
154
155 t.Run("Repository", func(t *testing.T) {
156 f := func(f1 *Repository) bool {
157 p1 := f1.ToProto()
158 f2 := RepositoryFromProto(p1)
159 if diff := cmp.Diff(f1, &f2, cmpopts.IgnoreUnexported(Repository{})); diff != "" {
160 fmt.Printf("got diff: %s", diff)
161 return false
162 }
163 return true
164 }
165 if err := quick.Check(f, nil); err != nil {
166 t.Fatal(err)
167 }
168 })
169
170 t.Run("IndexMetadata", func(t *testing.T) {
171 f := func(f1 *IndexMetadata) bool {
172 p1 := f1.ToProto()
173 f2 := IndexMetadataFromProto(p1)
174 if diff := cmp.Diff(f1, &f2); diff != "" {
175 fmt.Printf("got diff: %s", diff)
176 return false
177 }
178 return true
179 }
180 if err := quick.Check(f, nil); err != nil {
181 t.Fatal(err)
182 }
183 })
184
185 t.Run("RepoStats", func(t *testing.T) {
186 f := func(f1 RepoStats) bool {
187 p1 := f1.ToProto()
188 f2 := RepoStatsFromProto(p1)
189 if diff := cmp.Diff(f1, f2); diff != "" {
190 fmt.Printf("got diff: %s", diff)
191 return false
192 }
193 return true
194 }
195 if err := quick.Check(f, nil); err != nil {
196 t.Fatal(err)
197 }
198 })
199
200 t.Run("RepoListEntry", func(t *testing.T) {
201 r1 := &RepoListEntry{
202 Repository: Repository{
203 ID: 1,
204 Name: "test",
205 URL: "testurl",
206 Source: "testsource",
207 Branches: []RepositoryBranch{{
208 Name: "branch",
209 Version: "version",
210 }},
211 SubRepoMap: map[string]*Repository{
212 "test": {
213 ID: 2,
214 Name: "subrepo",
215 Branches: []RepositoryBranch{},
216 SubRepoMap: map[string]*Repository{},
217 FileTombstones: map[string]struct{}{},
218 },
219 },
220 CommitURLTemplate: "committemplate",
221 FileURLTemplate: "fileurltemplate",
222 LineFragmentTemplate: "linefragmenttemplate",
223 priority: 10,
224 RawConfig: map[string]string{
225 "a": "b",
226 },
227 Rank: 32,
228 IndexOptions: "indexoptions",
229 HasSymbols: true,
230 Tombstone: false,
231 LatestCommitDate: time.Now(),
232 FileTombstones: map[string]struct{}{
233 "test1": {},
234 },
235 },
236 IndexMetadata: IndexMetadata{
237 IndexFormatVersion: 32,
238 IndexFeatureVersion: 42,
239 IndexMinReaderVersion: 52,
240 IndexTime: time.Now(),
241 PlainASCII: true,
242 LanguageMap: map[string]uint16{
243 "go": 1,
244 },
245 ZoektVersion: "32",
246 ID: "52",
247 },
248 Stats: RepoStats{
249 Repos: 3,
250 Shards: 4,
251 Documents: 5,
252 IndexBytes: 6,
253 ContentBytes: 7,
254 NewLinesCount: 8,
255 DefaultBranchNewLinesCount: 9,
256 OtherBranchesNewLinesCount: 10,
257 },
258 }
259
260 p1 := r1.ToProto()
261 r2 := RepoListEntryFromProto(p1)
262 if diff := cmp.Diff(r1, r2, cmpopts.IgnoreUnexported(Repository{})); diff != "" {
263 t.Fatalf("got diff: %s", diff)
264 }
265 })
266
267 t.Run("RepositoryBranch", func(t *testing.T) {
268 f := func(f1 RepositoryBranch) bool {
269 p1 := f1.ToProto()
270 f2 := RepositoryBranchFromProto(p1)
271 if diff := cmp.Diff(f1, f2); diff != "" {
272 fmt.Printf("got diff: %s", diff)
273 return false
274 }
275 return true
276 }
277 if err := quick.Check(f, nil); err != nil {
278 t.Fatal(err)
279 }
280 })
281
282 t.Run("MinimalRepoListEntry", func(t *testing.T) {
283 f := func(f1 MinimalRepoListEntry) bool {
284 p1 := f1.ToProto()
285 f2 := MinimalRepoListEntryFromProto(p1)
286 if diff := cmp.Diff(f1, f2); diff != "" {
287 fmt.Printf("got diff: %s", diff)
288 return false
289 }
290 return true
291 }
292 if err := quick.Check(f, nil); err != nil {
293 t.Fatal(err)
294 }
295 })
296
297 t.Run("ListOptions", func(t *testing.T) {
298 f := func(f1 *ListOptions) bool {
299 p1 := f1.ToProto()
300 f2 := ListOptionsFromProto(p1)
301 if diff := cmp.Diff(f1, f2); diff != "" {
302 fmt.Printf("got diff: %s", diff)
303 return false
304 }
305 return true
306 }
307 if err := quick.Check(f, nil); err != nil {
308 t.Fatal(err)
309 }
310 })
311
312 t.Run("SearchOptions", func(t *testing.T) {
313 f := func(f1 *SearchOptions) bool {
314 if f1 != nil {
315 // Ignore deprecated and unimplemented fields
316 f1.ShardMaxImportantMatch = 0
317 f1.TotalMaxImportantMatch = 0
318 f1.SpanContext = nil
319 }
320 p1 := f1.ToProto()
321 f2 := SearchOptionsFromProto(p1)
322 if diff := cmp.Diff(f1, f2); diff != "" {
323 fmt.Printf("got diff: %s", diff)
324 return false
325 }
326 return true
327 }
328 if err := quick.Check(f, nil); err != nil {
329 t.Fatal(err)
330 }
331 })
332}
333
334func (*IndexMetadata) Generate(r *rand.Rand, _ int) reflect.Value {
335 indexTime := time.Now().Add(time.Duration(r.Int63n(1000)) * time.Hour)
336 var i IndexMetadata
337 i.IndexFormatVersion = gen(i.IndexFormatVersion, r)
338 i.IndexFeatureVersion = gen(i.IndexFeatureVersion, r)
339 i.IndexMinReaderVersion = gen(i.IndexMinReaderVersion, r)
340 i.IndexTime = indexTime
341 i.PlainASCII = gen(i.PlainASCII, r)
342 i.LanguageMap = gen(i.LanguageMap, r)
343 i.ZoektVersion = gen(i.ZoektVersion, r)
344 i.ID = gen(i.ID, r)
345 return reflect.ValueOf(&i)
346}
347
348func (*Repository) Generate(rng *rand.Rand, _ int) reflect.Value {
349 latestCommitDate := time.Now().Add(time.Duration(rng.Int63n(1000)) * time.Hour)
350 var r Repository
351 v := &Repository{
352 ID: gen(r.ID, rng),
353 Name: gen(r.Name, rng),
354 URL: gen(r.URL, rng),
355 Source: gen(r.Source, rng),
356 Branches: gen(r.Branches, rng),
357 SubRepoMap: map[string]*Repository{},
358 CommitURLTemplate: gen(r.CommitURLTemplate, rng),
359 FileURLTemplate: gen(r.FileURLTemplate, rng),
360 LineFragmentTemplate: gen(r.LineFragmentTemplate, rng),
361 priority: gen(r.priority, rng),
362 RawConfig: gen(r.RawConfig, rng),
363 Rank: gen(r.Rank, rng),
364 IndexOptions: gen(r.IndexOptions, rng),
365 HasSymbols: gen(r.HasSymbols, rng),
366 Tombstone: gen(r.Tombstone, rng),
367 LatestCommitDate: latestCommitDate,
368 FileTombstones: gen(r.FileTombstones, rng),
369 }
370 return reflect.ValueOf(v)
371}
372
373func (RepoListField) Generate(rng *rand.Rand, _ int) reflect.Value {
374 switch rng.Int() % 3 {
375 case 0:
376 return reflect.ValueOf(RepoListField(RepoListFieldRepos))
377 case 1:
378 return reflect.ValueOf(RepoListField(RepoListFieldMinimal))
379 default:
380 return reflect.ValueOf(RepoListField(RepoListFieldReposMap))
381 }
382}
383
384func gen[T any](sample T, r *rand.Rand) T {
385 var t T
386 v, _ := quick.Value(reflect.TypeOf(t), r)
387 return v.Interface().(T)
388}
389
390// This is a real search result that is intended to be a reasonable representative
391// for serialization benchmarks.
392// Generated by modifying the code to dump the proto to a file, then running a
393// fairly broadly-matching search.
394var (
395 //go:embed testdata/search_result_1.pb
396 exampleSearchResultBytes []byte
397
398 // The proto struct representation of the search result
399 exampleSearchResultProto = func() *v1.SearchResponse {
400 sr := new(v1.SearchResponse)
401 err := proto.Unmarshal(exampleSearchResultBytes, sr)
402 if err != nil {
403 panic(err)
404 }
405 return sr
406 }()
407
408 // The non-proto struct representation of the search result
409 exampleSearchResultGo = SearchResultFromProto(exampleSearchResultProto, nil, nil)
410)
411
412func BenchmarkGobRoundtrip(b *testing.B) {
413 for _, count := range []int{1, 100, 1000, 10000} {
414 b.Run(fmt.Sprintf("count=%d", count), func(b *testing.B) {
415 for i := 0; i < b.N; i++ {
416 var buf bytes.Buffer
417 enc := gob.NewEncoder(&buf)
418
419 for i := 0; i < count; i++ {
420 err := enc.Encode(exampleSearchResultGo)
421 if err != nil {
422 panic(err)
423 }
424
425 }
426
427 dec := gob.NewDecoder(&buf)
428 for i := 0; i < count; i++ {
429 var res SearchResult
430 err := dec.Decode(&res)
431 if err != nil {
432 panic(err)
433 }
434 }
435 }
436 })
437 }
438}
439
440func BenchmarkProtoRoundtrip(b *testing.B) {
441 for _, count := range []int{1, 100, 1000, 10000} {
442 b.Run(fmt.Sprintf("count=%d", count), func(b *testing.B) {
443 for i := 0; i < b.N; i++ {
444 buffers := make([][]byte, 0, count)
445 for i := 0; i < count; i++ {
446 buf, err := proto.Marshal(exampleSearchResultProto)
447 if err != nil {
448 b.Fatal(err)
449 }
450 buffers = append(buffers, buf)
451 }
452
453 for _, buf := range buffers {
454 res := new(v1.SearchResponse)
455 err := proto.Unmarshal(buf, res)
456 if err != nil {
457 b.Fatal(err)
458 }
459 }
460 }
461 })
462 }
463}