github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/config/cache_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "fmt" 21 "sync" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 "golang.org/x/sync/errgroup" 27 v1 "k8s.io/api/core/v1" 28 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 29 "sigs.k8s.io/prow/pkg/git/v2" 30 ) 31 32 type fakeConfigAgent struct { 33 sync.Mutex 34 c *Config 35 } 36 37 func (fca *fakeConfigAgent) Config() *Config { 38 fca.Lock() 39 defer fca.Unlock() 40 return fca.c 41 } 42 43 func TestNewInRepoConfigCache(t *testing.T) { 44 // Invalid size arguments result in a nil cache and non-nil error. 45 invalids := []int{-1, 0} 46 for _, invalid := range invalids { 47 48 fca := &fakeConfigAgent{} 49 cf := &testClientFactory{} 50 cache, err := NewInRepoConfigCache(invalid, fca, cf) 51 52 if err == nil { 53 t.Fatal("Expected non-nil error, got nil") 54 } 55 56 if err.Error() != "Must provide a positive size" { 57 t.Errorf("Expected error 'Must provide a positive size', got '%v'", err.Error()) 58 } 59 60 if cache != nil { 61 t.Errorf("Expected nil cache, got %v", cache) 62 } 63 } 64 65 // Valid size arguments. 66 valids := []int{1, 5, 1000} 67 for _, valid := range valids { 68 69 fca := &fakeConfigAgent{} 70 cf := &testClientFactory{} 71 cache, err := NewInRepoConfigCache(valid, fca, cf) 72 73 if err != nil { 74 t.Errorf("Expected error 'nil' got '%v'", err.Error()) 75 } 76 77 if cache == nil { 78 t.Errorf("Expected non-nil cache, got nil") 79 } 80 } 81 } 82 83 func goodSHAGetter(sha string) func() (string, error) { 84 return func() (string, error) { 85 return sha, nil 86 } 87 } 88 89 func badSHAGetter() (string, error) { 90 return "", fmt.Errorf("badSHAGetter") 91 } 92 93 func TestGetProwYAMLCached(t *testing.T) { 94 // fakeProwYAMLGetter mocks prowYAMLGetter(). Instead of using the 95 // git.ClientFactory (and other operations), we just use a simple map to get 96 // the *ProwYAML value we want. For simplicity we just reuse MakeCacheKey 97 // even though we're not using a cache. The point of fakeProwYAMLGetter is to 98 // act as a "source of truth" of authoritative *ProwYAML values for purposes 99 // of the test cases in this unit test. 100 fakeProwYAMLGetter := make(map[CacheKey]*ProwYAML) 101 102 // goodValConstructor mocks config.getProwYAML. 103 // This map pretends to be an expensive computation in order to generate a 104 // *ProwYAML value. 105 goodValConstructor := func(gc git.ClientFactory, identifier, baseBranch string, baseSHAGetter RefGetter, headSHAGetters ...RefGetter) (*ProwYAML, error) { 106 107 baseSHA, headSHAs, err := GetAndCheckRefs(baseSHAGetter, headSHAGetters...) 108 if err != nil { 109 t.Fatal(err) 110 } 111 112 kp := CacheKeyParts{ 113 Identifier: identifier, 114 BaseSHA: baseSHA, 115 HeadSHAs: headSHAs, 116 } 117 key, err := kp.CacheKey() 118 if err != nil { 119 t.Fatal(err) 120 } 121 122 val, ok := fakeProwYAMLGetter[key] 123 if ok { 124 return val, nil 125 } 126 127 return nil, fmt.Errorf("unable to construct *ProwYAML value") 128 } 129 130 fakeCacheKeyPartsSlice := []CacheKeyParts{ 131 { 132 Identifier: "foo/bar", 133 BaseSHA: "ba5e", 134 HeadSHAs: []string{"abcd", "ef01"}, 135 }, 136 } 137 // Populate fakeProwYAMLGetter. 138 for _, fakeCacheKeyParts := range fakeCacheKeyPartsSlice { 139 // To make it easier to compare Presubmit values, we only set the 140 // Name field and only compare this field. We also only create a 141 // single Presubmit (singleton slice), again for simplicity. Lastly 142 // we also set the Name field to the same value as the "key", again 143 // for simplicity. 144 fakeCacheKey, err := fakeCacheKeyParts.CacheKey() 145 if err != nil { 146 t.Fatal(err) 147 } 148 fakeProwYAMLGetter[fakeCacheKey] = &ProwYAML{ 149 Presubmits: []Presubmit{ 150 { 151 JobBase: JobBase{Name: string(fakeCacheKey)}, 152 }, 153 }, 154 } 155 } 156 157 // goodValConstructorForInitialState is used for warming up the cache for 158 // tests that need it. 159 goodValConstructorForInitialState := func(val ProwYAML) func() (interface{}, error) { 160 return func() (interface{}, error) { 161 return &val, nil 162 } 163 } 164 165 badValConstructor := func(gc git.ClientFactory, identifier, baseBranch string, baseSHAGetter RefGetter, headSHAGetters ...RefGetter) (*ProwYAML, error) { 166 return nil, fmt.Errorf("unable to construct *ProwYAML value") 167 } 168 169 type expected struct { 170 prowYAML *ProwYAML 171 cacheLen int 172 err string 173 } 174 175 for _, tc := range []struct { 176 name string 177 valConstructor func(git.ClientFactory, string, string, RefGetter, ...RefGetter) (*ProwYAML, error) 178 // We use a slice of CacheKeysParts for simplicity. 179 cacheInitialState []CacheKeyParts 180 cacheCorrupted bool 181 inRepoConfigEnabled bool 182 identifier string 183 baseBranch string 184 baseSHAGetter RefGetter 185 headSHAGetters []RefGetter 186 expected expected 187 }{ 188 { 189 name: "CacheMiss", 190 valConstructor: goodValConstructor, 191 cacheInitialState: nil, 192 cacheCorrupted: false, 193 inRepoConfigEnabled: true, 194 identifier: "foo/bar", 195 baseBranch: "main", 196 baseSHAGetter: goodSHAGetter("ba5e"), 197 headSHAGetters: []RefGetter{ 198 goodSHAGetter("abcd"), 199 goodSHAGetter("ef01")}, 200 expected: expected{ 201 prowYAML: &ProwYAML{ 202 Presubmits: []Presubmit{ 203 { 204 JobBase: JobBase{Name: `{"identifier":"foo/bar","baseSHA":"ba5e","headSHAs":["abcd","ef01"]}`}}, 205 }, 206 }, 207 cacheLen: 1, 208 err: "", 209 }, 210 }, 211 { 212 // If the InRepoConfig is disabled for this repo, then the returned 213 // value should be an empty &ProwYAML{}. Also, the cache miss should 214 // not result in adding this entry into the cache (because the value 215 // will be a meaninless empty &ProwYAML{}). 216 name: "CacheMiss/InRepoConfigDisabled", 217 valConstructor: goodValConstructor, 218 cacheInitialState: nil, 219 cacheCorrupted: false, 220 inRepoConfigEnabled: false, 221 identifier: "foo/bar", 222 baseBranch: "main", 223 baseSHAGetter: goodSHAGetter("ba5e"), 224 headSHAGetters: []RefGetter{ 225 goodSHAGetter("abcd"), 226 goodSHAGetter("ef01")}, 227 expected: expected{ 228 prowYAML: &ProwYAML{}, 229 cacheLen: 0, 230 err: "", 231 }, 232 }, 233 { 234 // If we get a cache hit, the value constructor function doesn't 235 // matter because it will never be called. 236 name: "CacheHit", 237 valConstructor: badValConstructor, 238 cacheInitialState: []CacheKeyParts{ 239 { 240 Identifier: "foo/bar", 241 BaseSHA: "ba5e", 242 HeadSHAs: []string{"abcd", "ef01"}, 243 }, 244 }, 245 cacheCorrupted: false, 246 inRepoConfigEnabled: true, 247 identifier: "foo/bar", 248 baseBranch: "main", 249 baseSHAGetter: goodSHAGetter("ba5e"), 250 headSHAGetters: []RefGetter{ 251 goodSHAGetter("abcd"), 252 goodSHAGetter("ef01")}, 253 expected: expected{ 254 prowYAML: &ProwYAML{ 255 Presubmits: []Presubmit{ 256 { 257 JobBase: JobBase{Name: `{"identifier":"foo/bar","baseSHA":"ba5e","headSHAs":["abcd","ef01"]}`}, 258 }, 259 }, 260 }, 261 cacheLen: 1, 262 err: "", 263 }, 264 }, 265 { 266 name: "BadValConstructorCacheMiss", 267 valConstructor: badValConstructor, 268 cacheInitialState: nil, 269 cacheCorrupted: false, 270 inRepoConfigEnabled: true, 271 identifier: "foo/bar", 272 baseBranch: "main", 273 baseSHAGetter: goodSHAGetter("ba5e"), 274 headSHAGetters: []RefGetter{ 275 goodSHAGetter("abcd"), 276 goodSHAGetter("ef01")}, 277 expected: expected{ 278 prowYAML: nil, 279 err: "unable to construct *ProwYAML value", 280 }, 281 }, 282 { 283 // If we get a cache hit, then it doesn't matter if the state of the 284 // world was such that the value could not have been constructed from 285 // scratch (because we're solely relying on the cache). 286 name: "BadValConstructorCacheHit", 287 valConstructor: badValConstructor, 288 cacheInitialState: []CacheKeyParts{ 289 { 290 Identifier: "foo/bar", 291 BaseSHA: "ba5e", 292 HeadSHAs: []string{"abcd", "ef01"}, 293 }, 294 }, 295 cacheCorrupted: false, 296 inRepoConfigEnabled: true, 297 identifier: "foo/bar", 298 baseBranch: "main", 299 baseSHAGetter: goodSHAGetter("ba5e"), 300 headSHAGetters: []RefGetter{ 301 goodSHAGetter("abcd"), 302 goodSHAGetter("ef01")}, 303 expected: expected{ 304 prowYAML: &ProwYAML{ 305 Presubmits: []Presubmit{ 306 { 307 JobBase: JobBase{Name: `{"identifier":"foo/bar","baseSHA":"ba5e","headSHAs":["abcd","ef01"]}`}}, 308 }, 309 }, 310 cacheLen: 1, 311 err: "", 312 }, 313 }, 314 { 315 // If the cache is corrupted (it holds values of a type that is not 316 // *ProwYAML), then we expect an error. 317 name: "GoodValConstructorCorruptedCacheHit", 318 valConstructor: goodValConstructor, 319 cacheInitialState: []CacheKeyParts{ 320 { 321 Identifier: "foo/bar", 322 BaseSHA: "ba5e", 323 HeadSHAs: []string{"abcd", "ef01"}, 324 }, 325 }, 326 cacheCorrupted: true, 327 inRepoConfigEnabled: true, 328 identifier: "foo/bar", 329 baseBranch: "main", 330 baseSHAGetter: goodSHAGetter("ba5e"), 331 headSHAGetters: []RefGetter{ 332 goodSHAGetter("abcd"), 333 goodSHAGetter("ef01")}, 334 expected: expected{ 335 prowYAML: nil, 336 err: "Programmer error: expected value type '*config.ProwYAML', got 'string'", 337 }, 338 }, 339 { 340 // If the cache is corrupted (it holds values of a type that is not 341 // *ProwYAML), then we expect an error. 342 name: "BadValConstructorCorruptedCacheHit", 343 valConstructor: badValConstructor, 344 cacheInitialState: []CacheKeyParts{ 345 { 346 Identifier: "foo/bar", 347 BaseSHA: "ba5e", 348 HeadSHAs: []string{"abcd", "ef01"}, 349 }, 350 }, 351 cacheCorrupted: true, 352 inRepoConfigEnabled: true, 353 identifier: "foo/bar", 354 baseBranch: "main", 355 baseSHAGetter: goodSHAGetter("ba5e"), 356 headSHAGetters: []RefGetter{ 357 goodSHAGetter("abcd"), 358 goodSHAGetter("ef01")}, 359 expected: expected{ 360 prowYAML: nil, 361 err: "Programmer error: expected value type '*config.ProwYAML', got 'string'", 362 }, 363 }, 364 } { 365 t.Run(tc.name, func(t1 *testing.T) { 366 // Reset test state. 367 maybeEnabled := make(map[string]*bool) 368 maybeEnabled[tc.identifier] = &tc.inRepoConfigEnabled 369 370 fca := &fakeConfigAgent{ 371 c: &Config{ 372 ProwConfig: ProwConfig{ 373 InRepoConfig: InRepoConfig{ 374 Enabled: maybeEnabled, 375 }, 376 }, 377 }, 378 } 379 cf := &testClientFactory{} 380 cache, err := NewInRepoConfigCache(1, fca, cf) 381 if err != nil { 382 t.Fatal("could not initialize cache") 383 } 384 385 for _, kp := range tc.cacheInitialState { 386 k, err := kp.CacheKey() 387 if err != nil { 388 t.Errorf("Expected error 'nil' got '%v'", err.Error()) 389 } 390 _, _, _ = cache.GetOrAdd(k, goodValConstructorForInitialState(ProwYAML{ 391 Presubmits: []Presubmit{ 392 { 393 JobBase: JobBase{Name: string(k)}}, 394 }, 395 })) 396 } 397 398 // Simulate storing a value of the wrong type in the cache (a string 399 // instead of a *ProwYAML). 400 if tc.cacheCorrupted { 401 func() { 402 cache.Lock() 403 defer cache.Unlock() 404 cache.Purge() 405 }() 406 407 for _, kp := range tc.cacheInitialState { 408 k, err := kp.CacheKey() 409 if err != nil { 410 t.Errorf("Expected error 'nil' got '%v'", err.Error()) 411 } 412 _, _, _ = cache.GetOrAdd(k, func() (interface{}, error) { return "<wrong-type>", nil }) 413 } 414 } 415 416 prowYAML, err := cache.getProwYAML(tc.valConstructor, tc.identifier, tc.baseBranch, tc.baseSHAGetter, tc.headSHAGetters...) 417 418 if tc.expected.err == "" { 419 if err != nil { 420 t.Errorf("Expected error 'nil' got '%v'", err.Error()) 421 } 422 } else { 423 if err == nil { 424 t.Fatal("Expected non-nil error, got nil") 425 } 426 427 if tc.expected.err != err.Error() { 428 t.Errorf("Expected error '%v', got '%v'", tc.expected.err, err.Error()) 429 } 430 } 431 432 if tc.expected.prowYAML == nil && prowYAML != nil { 433 t.Fatalf("Expected nil for *ProwYAML, got '%v'", *prowYAML) 434 } 435 436 if tc.expected.prowYAML != nil && prowYAML == nil { 437 t.Fatal("Expected non-nil for *ProwYAML, got nil") 438 } 439 440 // If we got what we expected, there's no need to compare these two. 441 if tc.expected.prowYAML == nil && prowYAML == nil { 442 return 443 } 444 445 // The Presubmit type is not comparable. So instead of checking the 446 // overall type for equality, we only check the Name field of it, 447 // because it is a simple string type. 448 if len(tc.expected.prowYAML.Presubmits) != len(prowYAML.Presubmits) { 449 t.Fatalf("Expected prowYAML length '%d', got '%d'", len(tc.expected.prowYAML.Presubmits), len(prowYAML.Presubmits)) 450 } 451 for i := range tc.expected.prowYAML.Presubmits { 452 if tc.expected.prowYAML.Presubmits[i].Name != prowYAML.Presubmits[i].Name { 453 t.Errorf("Expected presubmits[%d].Name to be '%v', got '%v'", i, tc.expected.prowYAML.Presubmits[i].Name, prowYAML.Presubmits[i].Name) 454 } 455 } 456 457 if tc.expected.cacheLen != cache.Len() { 458 t.Errorf("Expected '%d' cached elements, got '%d'", tc.expected.cacheLen, cache.Len()) 459 } 460 }) 461 } 462 } 463 464 // TestGetProwYAMLCachedAndDefaulted checks that calls to 465 // cache.GetPresubmits() and cache.GetPostsubmits() return 466 // defaulted values from the Config, and that changing (reloading) this Config 467 // and calling it again with the same key (same cached ProwYAML, which has both 468 // []Presubmit and []Postsubmit jobs) results in returning a __differently__ 469 // defaulted ProwYAML object. 470 func TestGetProwYAMLCachedAndDefaulted(t *testing.T) { 471 identifier := "org/repo" 472 baseBranch := "main" 473 baseSHAGetter := goodSHAGetter("ba5e") 474 headSHAGetters := []RefGetter{ 475 goodSHAGetter("abcd"), 476 goodSHAGetter("ef01"), 477 } 478 479 envBefore := []v1.EnvVar{ 480 { 481 Name: "ENV_VAR_FOO", 482 Value: "VALUE", 483 }, 484 } 485 decorationConfigBefore := &prowapi.DecorationConfig{ 486 GCSConfiguration: &prowapi.GCSConfiguration{ 487 PathStrategy: prowapi.PathStrategyExplicit, 488 DefaultOrg: "org", 489 DefaultRepo: "repo", 490 }, 491 GCSCredentialsSecret: pStr("service-account-secret"), 492 UtilityImages: &prowapi.UtilityImages{ 493 CloneRefs: "clonerefs:default-BEFORE", 494 InitUpload: "initupload:default-BEFORE", 495 Entrypoint: "entrypoint:default-BEFORE", 496 Sidecar: "sidecar:default-BEFORE", 497 }, 498 } 499 500 envAfter := ([]v1.EnvVar)(nil) 501 decorationConfigAfter := &prowapi.DecorationConfig{ 502 GCSConfiguration: &prowapi.GCSConfiguration{ 503 PathStrategy: prowapi.PathStrategyExplicit, 504 DefaultOrg: "org", 505 DefaultRepo: "repo", 506 }, 507 GCSCredentialsSecret: pStr("service-account-secret"), 508 UtilityImages: &prowapi.UtilityImages{ 509 CloneRefs: "clonerefs:default-AFTER", 510 InitUpload: "initupload:default-AFTER", 511 Entrypoint: "entrypoint:default-AFTER", 512 Sidecar: "sidecar:default-AFTER", 513 }, 514 } 515 516 type expected struct { 517 presubmits []Presubmit 518 postsubmits []Postsubmit 519 } 520 521 true_ := true 522 523 defaultedPresubmit := func(env []v1.EnvVar, dc *prowapi.DecorationConfig) Presubmit { 524 return Presubmit{ 525 JobBase: JobBase{ 526 Name: "presubmitFoo", 527 Agent: "kubernetes", 528 Cluster: "clusterFoo", 529 Namespace: pStr("default"), 530 ProwJobDefault: &prowapi.ProwJobDefault{TenantID: "GlobalDefaultID"}, 531 Spec: &v1.PodSpec{ 532 Containers: []v1.Container{ 533 { 534 Name: "hello", 535 Image: "there", 536 Command: []string{"earthlings"}, 537 Env: env, 538 }, 539 }, 540 }, 541 UtilityConfig: UtilityConfig{ 542 Decorate: &true_, 543 DecorationConfig: dc, 544 }, 545 }, 546 Trigger: `(?m)^/test( | .* )presubmitFoo,?($|\s.*)`, 547 RerunCommand: "/test presubmitFoo", 548 Reporter: Reporter{ 549 Context: "presubmitFoo", 550 SkipReport: false, 551 }, 552 } 553 } 554 555 defaultedPostsubmit := func(env []v1.EnvVar, dc *prowapi.DecorationConfig) Postsubmit { 556 return Postsubmit{ 557 JobBase: JobBase{ 558 Name: "postsubmitFoo", 559 Agent: "kubernetes", 560 Cluster: "clusterFoo", 561 Namespace: pStr("default"), 562 ProwJobDefault: &prowapi.ProwJobDefault{TenantID: "GlobalDefaultID"}, 563 Spec: &v1.PodSpec{ 564 Containers: []v1.Container{ 565 { 566 Name: "hello", 567 Image: "there", 568 Command: []string{"earthlings"}, 569 Env: env, 570 }, 571 }, 572 }, 573 UtilityConfig: UtilityConfig{ 574 Decorate: &true_, 575 DecorationConfig: dc, 576 }, 577 }, 578 Reporter: Reporter{ 579 Context: "postsubmitFoo", 580 SkipReport: false, 581 }, 582 } 583 } 584 inRepoConfigEnabled := make(map[string]*bool) 585 inRepoConfigEnabled[identifier] = &true_ 586 587 // fakeProwYAMLGetterFunc mocks prowYAMLGetter(). Instead of using the 588 // git.ClientFactory (and other operations), we just use a simple map to get 589 // the *ProwYAML value we want. The point of fakeProwYAMLGetterFunc is to 590 // act as a "source of truth" of authoritative *ProwYAML values for purposes 591 // of the test cases in this unit test. 592 fakeProwYAMLGetterFunc := func() ProwYAMLGetter { 593 presubmitUndecorated := Presubmit{ 594 JobBase: JobBase{ 595 Name: "presubmitFoo", 596 Cluster: "clusterFoo", 597 Namespace: pStr("default"), 598 Spec: &v1.PodSpec{ 599 Containers: []v1.Container{ 600 { 601 Name: "hello", 602 Image: "there", 603 Command: []string{"earthlings"}, 604 }, 605 }, 606 }, 607 }, 608 } 609 postsubmitUndecorated := Postsubmit{ 610 JobBase: JobBase{ 611 Name: "postsubmitFoo", 612 Cluster: "clusterFoo", 613 Namespace: pStr("default"), 614 Spec: &v1.PodSpec{ 615 Containers: []v1.Container{ 616 { 617 Name: "hello", 618 Image: "there", 619 Command: []string{"earthlings"}, 620 }, 621 }, 622 }, 623 }, 624 } 625 return fakeProwYAMLGetterFactory( 626 []Presubmit{presubmitUndecorated}, 627 []Postsubmit{postsubmitUndecorated}) 628 } 629 630 makeConfig := func(env []v1.EnvVar, ddc []*DefaultDecorationConfigEntry) *Config { 631 return &Config{ 632 ProwConfig: ProwConfig{ 633 InRepoConfig: InRepoConfig{ 634 AllowedClusters: map[string][]string{ 635 "org/repo": {"clusterFoo"}, 636 }, 637 Enabled: inRepoConfigEnabled, 638 }, 639 Plank: Plank{ 640 DefaultDecorationConfigs: ddc, 641 }, 642 PodNamespace: "default", 643 }, 644 JobConfig: JobConfig{ 645 DecorateAllJobs: true, 646 ProwYAMLGetter: fakeProwYAMLGetterFunc(), 647 Presets: []Preset{ 648 { 649 Env: env, 650 }, 651 }, 652 }, 653 } 654 } 655 656 presubmitBefore := defaultedPresubmit(envBefore, decorationConfigBefore) 657 postsubmitBefore := defaultedPostsubmit(envBefore, decorationConfigBefore) 658 659 for _, tc := range []struct { 660 name string 661 // Initial state of Config with a particular DefaultDecorationConfigEntry. 662 configBefore *Config 663 expectedBefore expected 664 // Changed state of Config with a possibly __different__ DefaultDecorationConfigEntry. 665 configAfter *Config 666 expectedAfter expected 667 }{ 668 { 669 // Config has not changed between multiple 670 // cache.GetPresubmits() calls. 671 name: "ConfigNotChanged", 672 configBefore: makeConfig(envBefore, []*DefaultDecorationConfigEntry{ 673 { 674 OrgRepo: "*", 675 Cluster: "*", 676 Config: decorationConfigBefore, 677 }, 678 }), 679 // These are the expected []Presubmit and []Postsubmit values when 680 // defaulted with the "decorationConfigBefore" value. Among other 681 // things, the UtilityConfig.DecorationConfig value should reflect 682 // the same settings as "decorationConfigBefore". 683 expectedBefore: expected{ 684 presubmits: []Presubmit{presubmitBefore}, 685 postsubmits: []Postsubmit{postsubmitBefore}, 686 }, 687 // For this test case, we do not change the 688 // DefualtDecorationConfigEntry at all, so we don't expect any 689 // changes. 690 configAfter: makeConfig(envBefore, []*DefaultDecorationConfigEntry{ 691 { 692 OrgRepo: "*", 693 Cluster: "*", 694 Config: decorationConfigBefore, 695 }, 696 }), 697 expectedAfter: expected{ 698 presubmits: []Presubmit{presubmitBefore}, 699 postsubmits: []Postsubmit{postsubmitBefore}, 700 }, 701 }, 702 { 703 // Config has changed between multiple requests to cache. 704 name: "ConfigChanged", 705 configBefore: makeConfig(envBefore, []*DefaultDecorationConfigEntry{ 706 { 707 OrgRepo: "*", 708 Cluster: "*", 709 Config: decorationConfigBefore, 710 }, 711 }), 712 // These are the expected []Presubmit and []Postsubmit values when 713 // defaulted with the "decorationConfigBefore" value. Among other 714 // things, the UtilityConfig.DecorationConfig value should reflect 715 // the same settings as "decorationConfigBefore". 716 expectedBefore: expected{ 717 presubmits: []Presubmit{presubmitBefore}, 718 postsubmits: []Postsubmit{postsubmitBefore}, 719 }, 720 // Change the config to decorationConfigAfter. 721 configAfter: makeConfig(envAfter, []*DefaultDecorationConfigEntry{ 722 { 723 OrgRepo: "*", 724 Cluster: "*", 725 Config: decorationConfigAfter, 726 }, 727 }), 728 // Expect "Env" field to be a nil pointer. 729 expectedAfter: expected{ 730 presubmits: []Presubmit{ 731 { 732 JobBase: JobBase{ 733 Name: "presubmitFoo", 734 Agent: "kubernetes", 735 Cluster: "clusterFoo", 736 Namespace: pStr("default"), 737 ProwJobDefault: &prowapi.ProwJobDefault{TenantID: "GlobalDefaultID"}, 738 Spec: &v1.PodSpec{ 739 Containers: []v1.Container{ 740 { 741 Name: "hello", 742 Image: "there", 743 Command: []string{"earthlings"}, 744 // Env field is a nil pointer! 745 Env: nil, 746 }, 747 }, 748 }, 749 UtilityConfig: UtilityConfig{ 750 Decorate: &true_, 751 DecorationConfig: decorationConfigAfter, 752 }, 753 }, 754 Trigger: `(?m)^/test( | .* )presubmitFoo,?($|\s.*)`, 755 RerunCommand: "/test presubmitFoo", 756 Reporter: Reporter{ 757 Context: "presubmitFoo", 758 SkipReport: false, 759 }, 760 }, 761 }, 762 postsubmits: []Postsubmit{ 763 { 764 JobBase: JobBase{ 765 Name: "postsubmitFoo", 766 Agent: "kubernetes", 767 Cluster: "clusterFoo", 768 Namespace: pStr("default"), 769 ProwJobDefault: &prowapi.ProwJobDefault{TenantID: "GlobalDefaultID"}, 770 Spec: &v1.PodSpec{ 771 Containers: []v1.Container{ 772 { 773 Name: "hello", 774 Image: "there", 775 Command: []string{"earthlings"}, 776 // Env field is a nil pointer! 777 Env: nil, 778 }, 779 }, 780 }, 781 UtilityConfig: UtilityConfig{ 782 Decorate: &true_, 783 DecorationConfig: decorationConfigAfter, 784 }, 785 }, 786 Reporter: Reporter{ 787 Context: "postsubmitFoo", 788 SkipReport: false, 789 }, 790 }, 791 }, 792 }, 793 }, 794 } { 795 t.Run(tc.name, func(t1 *testing.T) { 796 // Set initial Config. 797 fca := &fakeConfigAgent{ 798 c: tc.configBefore, 799 } 800 cf := &testClientFactory{} 801 802 // Initialize cache. Notice that it relies on a snapshot of the Config with configBefore. 803 cache, err := NewInRepoConfigCache(10, fca, cf) 804 if err != nil { 805 t1.Fatal("could not initialize cache") 806 } 807 808 // Get cached values. These cached values should be defaulted by the 809 // initial Config. 810 // Make sure that this runs concurrently without problem. 811 var errGroup errgroup.Group 812 for i := 0; i < 1000; i++ { 813 errGroup.Go(func() error { 814 presubmits, err := cache.GetPresubmits(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 815 if err != nil { 816 return fmt.Errorf("Expected error 'nil' got '%v'", err.Error()) 817 } 818 if diff := cmp.Diff(tc.expectedBefore.presubmits, presubmits, cmpopts.IgnoreUnexported(Presubmit{}, Brancher{}, RegexpChangeMatcher{})); diff != "" { 819 return fmt.Errorf("(before Config reload) presubmits mismatch (-want +got):\n%s", diff) 820 } 821 return nil 822 }) 823 824 errGroup.Go(func() error { 825 postsubmits, err := cache.GetPostsubmits(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 826 if err != nil { 827 return fmt.Errorf("Expected error 'nil' got '%v'", err.Error()) 828 } 829 830 if diff := cmp.Diff(tc.expectedBefore.postsubmits, postsubmits, cmpopts.IgnoreUnexported(Postsubmit{}, Brancher{}, RegexpChangeMatcher{})); diff != "" { 831 return fmt.Errorf("(before Config reload) postsubmits mismatch (-want +got):\n%s", diff) 832 } 833 return nil 834 }) 835 } 836 837 if err := errGroup.Wait(); err != nil { 838 t.Fatalf("Failed processing concurrently: %v", err) 839 } 840 841 // Reload Config. 842 fca.c = tc.configAfter 843 844 presubmits, err := cache.GetPresubmits(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 845 if err != nil { 846 t1.Fatalf("Expected error 'nil' got '%v'", err.Error()) 847 } 848 postsubmits, err := cache.GetPostsubmits(identifier, baseBranch, baseSHAGetter, headSHAGetters...) 849 if err != nil { 850 t1.Fatalf("Expected error 'nil' got '%v'", err.Error()) 851 } 852 853 if diff := cmp.Diff(tc.expectedAfter.presubmits, presubmits, cmpopts.IgnoreUnexported(Presubmit{}, Brancher{}, RegexpChangeMatcher{})); diff != "" { 854 t1.Errorf("(after Config reload) presubmits mismatch (-want +got):\n%s", diff) 855 } 856 if diff := cmp.Diff(tc.expectedAfter.postsubmits, postsubmits, cmpopts.IgnoreUnexported(Postsubmit{}, Brancher{}, RegexpChangeMatcher{})); diff != "" { 857 t1.Errorf("(after Config reload) postsubmits mismatch (-want +got):\n%s", diff) 858 } 859 860 }) 861 } 862 863 }