fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

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} 371 372func TestMonthsSince1970(t *testing.T) { 373 tests := []struct { 374 name string 375 input time.Time 376 expected uint16 377 }{ 378 {"Before 1970", time.Date(1950, 12, 31, 0, 0, 0, 0, time.UTC), 0}, 379 {"Unix 0", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), 0}, 380 {"Feb 1970", time.Date(1970, 2, 1, 0, 0, 0, 0, time.UTC), 1}, 381 {"Year 1989", time.Date(1989, 12, 13, 0, 0, 0, 0, time.UTC), 239}, 382 {"Sep 2024", time.Date(2024, 9, 20, 0, 0, 0, 0, time.UTC), 656}, 383 {"Oct 2024", time.Date(2024, 10, 20, 0, 0, 0, 0, time.UTC), 657}, 384 {"Apr 7431", time.Date(7431, 4, 1, 0, 0, 0, 0, time.UTC), 65535}, 385 {"9999", time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC), 65535}, 386 } 387 388 for _, tt := range tests { 389 t.Run(tt.name, func(t *testing.T) { 390 result := monthsSince1970(tt.input) 391 if result != tt.expected { 392 t.Errorf("expected %d, got %d", tt.expected, result) 393 } 394 }) 395 } 396}