sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/config/branch_protection_test.go (about) 1 /* 2 Copyright 2018 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 "reflect" 21 "sort" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "k8s.io/apimachinery/pkg/util/diff" 26 utilpointer "k8s.io/utils/pointer" 27 ) 28 29 var ( 30 y = true 31 n = false 32 yes = &y 33 no = &n 34 ) 35 36 func normalize(policy *Policy) { 37 if policy == nil || policy.RequiredStatusChecks == nil { 38 return 39 } 40 sort.Strings(policy.RequiredStatusChecks.Contexts) 41 sort.Strings(policy.Exclude) 42 } 43 44 func TestSelectBool(t *testing.T) { 45 cases := []struct { 46 name string 47 parent *bool 48 child *bool 49 expected *bool 50 }{ 51 { 52 name: "default is nil", 53 }, 54 { 55 name: "use child if set", 56 child: yes, 57 expected: yes, 58 }, 59 { 60 name: "child overrides parent", 61 child: yes, 62 parent: no, 63 expected: yes, 64 }, 65 { 66 name: "use parent if child unset", 67 parent: no, 68 expected: no, 69 }, 70 } 71 for _, tc := range cases { 72 t.Run(tc.name, func(t *testing.T) { 73 actual := selectBool(tc.parent, tc.child) 74 if !reflect.DeepEqual(actual, tc.expected) { 75 t.Errorf("actual %v != expected %v", actual, tc.expected) 76 } 77 }) 78 } 79 } 80 81 func TestSelectInt(t *testing.T) { 82 one := 1 83 two := 2 84 cases := []struct { 85 name string 86 parent *int 87 child *int 88 expected *int 89 }{ 90 { 91 name: "default is nil", 92 }, 93 { 94 name: "use child if set", 95 child: &one, 96 expected: &one, 97 }, 98 { 99 name: "child overrides parent", 100 child: &one, 101 parent: &two, 102 expected: &one, 103 }, 104 { 105 name: "use parent if child unset", 106 parent: &two, 107 expected: &two, 108 }, 109 } 110 for _, tc := range cases { 111 t.Run(tc.name, func(t *testing.T) { 112 actual := selectInt(tc.parent, tc.child) 113 if !reflect.DeepEqual(actual, tc.expected) { 114 t.Errorf("actual %v != expected %v", actual, tc.expected) 115 } 116 }) 117 } 118 } 119 120 func TestUnionStrings(t *testing.T) { 121 cases := []struct { 122 name string 123 parent []string 124 child []string 125 expected []string 126 }{ 127 { 128 name: "empty list", 129 }, 130 { 131 name: "all parent items", 132 parent: []string{"hi", "there"}, 133 expected: []string{"hi", "there"}, 134 }, 135 { 136 name: "all child items", 137 child: []string{"hi", "there"}, 138 expected: []string{"hi", "there"}, 139 }, 140 { 141 name: "both child and parent items, no duplicates", 142 child: []string{"hi", "world"}, 143 parent: []string{"hi", "there"}, 144 expected: []string{"hi", "there", "world"}, 145 }, 146 } 147 for _, tc := range cases { 148 t.Run(tc.name, func(t *testing.T) { 149 actual := unionStrings(tc.parent, tc.child) 150 sort.Strings(actual) 151 sort.Strings(tc.expected) 152 if !reflect.DeepEqual(actual, tc.expected) { 153 t.Errorf("actual %v != expected %v", actual, tc.expected) 154 } 155 }) 156 } 157 } 158 159 func TestApply(test *testing.T) { 160 t := true 161 f := false 162 basic := Policy{ 163 Protect: &t, 164 } 165 ebasic := Policy{ 166 Protect: &t, 167 } 168 cases := []struct { 169 name string 170 parent Policy 171 child Policy 172 expected Policy 173 }{ 174 { 175 name: "nil child", 176 parent: basic, 177 expected: ebasic, 178 }, 179 { 180 name: "merge parent and child", 181 parent: Policy{ 182 Protect: &t, 183 }, 184 child: Policy{ 185 Admins: &f, 186 RequiredLinearHistory: &t, 187 AllowForcePushes: &t, 188 AllowDeletions: &t, 189 }, 190 expected: Policy{ 191 Protect: &t, 192 Admins: &f, 193 RequiredLinearHistory: &t, 194 AllowForcePushes: &t, 195 AllowDeletions: &t, 196 }, 197 }, 198 { 199 name: "child overrides parent", 200 parent: Policy{ 201 Protect: &t, 202 }, 203 child: Policy{ 204 Protect: &f, 205 }, 206 expected: Policy{ 207 Protect: &f, 208 }, 209 }, 210 { 211 name: "append strings", 212 parent: Policy{ 213 RequiredStatusChecks: &ContextPolicy{ 214 Contexts: []string{"hello", "world"}, 215 }, 216 }, 217 child: Policy{ 218 RequiredStatusChecks: &ContextPolicy{ 219 Contexts: []string{"world", "of", "thrones"}, 220 }, 221 }, 222 expected: Policy{ 223 RequiredStatusChecks: &ContextPolicy{ 224 Contexts: []string{"hello", "of", "thrones", "world"}, 225 }, 226 }, 227 }, 228 { 229 name: "merge struct", 230 parent: Policy{ 231 RequiredStatusChecks: &ContextPolicy{ 232 Contexts: []string{"hi"}, 233 }, 234 }, 235 child: Policy{ 236 RequiredStatusChecks: &ContextPolicy{ 237 Strict: &t, 238 }, 239 }, 240 expected: Policy{ 241 RequiredStatusChecks: &ContextPolicy{ 242 Contexts: []string{"hi"}, 243 Strict: &t, 244 }, 245 }, 246 }, 247 { 248 name: "nil child struct", 249 parent: Policy{ 250 RequiredStatusChecks: &ContextPolicy{ 251 Strict: &f, 252 }, 253 }, 254 child: Policy{ 255 Protect: &t, 256 }, 257 expected: Policy{ 258 RequiredStatusChecks: &ContextPolicy{ 259 Strict: &f, 260 }, 261 Protect: &t, 262 }, 263 }, 264 { 265 name: "nil parent struct", 266 child: Policy{ 267 RequiredStatusChecks: &ContextPolicy{ 268 Strict: &f, 269 }, 270 }, 271 parent: Policy{ 272 Protect: &t, 273 }, 274 expected: Policy{ 275 RequiredStatusChecks: &ContextPolicy{ 276 Strict: &f, 277 }, 278 Protect: &t, 279 }, 280 }, 281 { 282 name: "merge exclusion strings", 283 child: Policy{ 284 Exclude: []string{"foo*"}, 285 }, 286 parent: Policy{ 287 Exclude: []string{"bar*"}, 288 }, 289 expected: Policy{ 290 Exclude: []string{"bar*", "foo*"}, 291 }, 292 }, 293 { 294 name: "merge inclusion strings", 295 child: Policy{ 296 Include: []string{"foo*"}, 297 }, 298 parent: Policy{ 299 Include: []string{"bar*"}, 300 }, 301 expected: Policy{ 302 Include: []string{"bar*", "foo*"}, 303 }, 304 }, 305 } 306 307 for _, tc := range cases { 308 test.Run(tc.name, func(test *testing.T) { 309 defer func() { 310 if r := recover(); r != nil { 311 test.Errorf("unexpected panic: %s", r) 312 } 313 }() 314 actual := tc.parent.Apply(tc.child) 315 normalize(&actual) 316 normalize(&tc.expected) 317 if !reflect.DeepEqual(actual, tc.expected) { 318 test.Errorf("bad merged policy:\n%s", diff.ObjectReflectDiff(tc.expected, actual)) 319 } 320 }) 321 } 322 } 323 324 func TestBranchRequirements(t *testing.T) { 325 cases := []struct { 326 name string 327 requireManuallyTriggeredJobs bool 328 config []Presubmit 329 masterExpected, otherExpected []string 330 masterOptional, otherOptional []string 331 masterIfPresent, otherIfPresent []string 332 }{ 333 { 334 name: "basic", 335 config: []Presubmit{ 336 { 337 AlwaysRun: true, 338 Reporter: Reporter{ 339 Context: "always-run", 340 SkipReport: false, 341 }, 342 }, 343 { 344 RegexpChangeMatcher: RegexpChangeMatcher{ 345 RunIfChanged: "foo", 346 }, 347 AlwaysRun: false, 348 Reporter: Reporter{ 349 Context: "run-if-changed", 350 SkipReport: false, 351 }, 352 }, 353 { 354 RegexpChangeMatcher: RegexpChangeMatcher{ 355 SkipIfOnlyChanged: "foo", 356 }, 357 AlwaysRun: false, 358 Reporter: Reporter{ 359 Context: "skip-if-only-changed", 360 SkipReport: false, 361 }, 362 }, 363 { 364 AlwaysRun: false, 365 Reporter: Reporter{ 366 Context: "not-always", 367 SkipReport: false, 368 }, 369 }, 370 { 371 AlwaysRun: true, 372 Reporter: Reporter{ 373 Context: "skip-report", 374 SkipReport: true, 375 }, 376 Brancher: Brancher{ 377 SkipBranches: []string{"master"}, 378 }, 379 }, 380 { 381 AlwaysRun: true, 382 Reporter: Reporter{ 383 Context: "optional", 384 SkipReport: false, 385 }, 386 Optional: true, 387 }, 388 }, 389 masterExpected: []string{"always-run"}, 390 masterIfPresent: []string{"run-if-changed", "skip-if-only-changed", "not-always"}, 391 masterOptional: []string{"optional"}, 392 otherExpected: []string{"always-run"}, 393 otherIfPresent: []string{"run-if-changed", "skip-if-only-changed", "not-always"}, 394 otherOptional: []string{"skip-report", "optional"}, 395 }, 396 { 397 name: "require non optional jobs", 398 requireManuallyTriggeredJobs: true, 399 config: []Presubmit{ 400 { 401 AlwaysRun: false, 402 Reporter: Reporter{ 403 Context: "always-run false", 404 SkipReport: false, 405 }, 406 }, 407 { 408 AlwaysRun: false, 409 Reporter: Reporter{ 410 Context: "always-run false skip master", 411 SkipReport: false, 412 }, 413 Brancher: Brancher{ 414 SkipBranches: []string{"master"}, 415 }, 416 }, 417 { 418 RegexpChangeMatcher: RegexpChangeMatcher{ 419 RunIfChanged: "foo", 420 }, 421 AlwaysRun: false, 422 Reporter: Reporter{ 423 Context: "run-if-changed", 424 SkipReport: false, 425 }, 426 }, 427 { 428 RegexpChangeMatcher: RegexpChangeMatcher{ 429 SkipIfOnlyChanged: "foo", 430 }, 431 AlwaysRun: false, 432 Reporter: Reporter{ 433 Context: "skip-if-only-changed", 434 SkipReport: false, 435 }, 436 }, 437 { 438 AlwaysRun: true, 439 Reporter: Reporter{ 440 Context: "skip-report", 441 SkipReport: true, 442 }, 443 Brancher: Brancher{ 444 SkipBranches: []string{"master"}, 445 }, 446 }, 447 { 448 AlwaysRun: true, 449 Reporter: Reporter{ 450 Context: "optional", 451 SkipReport: false, 452 }, 453 Optional: true, 454 }, 455 }, 456 masterExpected: []string{"always-run false"}, 457 masterIfPresent: []string{"run-if-changed", "skip-if-only-changed"}, 458 masterOptional: []string{"optional"}, 459 otherExpected: []string{"always-run false", "always-run false skip master"}, 460 otherIfPresent: []string{"run-if-changed", "skip-if-only-changed"}, 461 otherOptional: []string{"skip-report", "optional"}, 462 }, 463 } 464 465 for _, tc := range cases { 466 if err := SetPresubmitRegexes(tc.config); err != nil { 467 t.Fatalf("could not set regexes: %v", err) 468 } 469 presubmits := map[string][]Presubmit{ 470 "o/r": tc.config, 471 } 472 masterActual, masterActualIfPresent, masterOptional := BranchRequirements("master", presubmits["o/r"], &tc.requireManuallyTriggeredJobs) 473 if !reflect.DeepEqual(masterActual, tc.masterExpected) { 474 t.Errorf("%s: identified incorrect required contexts on branch master: %s", tc.name, diff.ObjectReflectDiff(masterActual, tc.masterExpected)) 475 } 476 if !reflect.DeepEqual(masterOptional, tc.masterOptional) { 477 t.Errorf("%s: identified incorrect optional contexts on branch master: %s", tc.name, diff.ObjectReflectDiff(masterOptional, tc.masterOptional)) 478 } 479 if !reflect.DeepEqual(masterActualIfPresent, tc.masterIfPresent) { 480 t.Errorf("%s: identified incorrect if-present contexts on branch master: %s", tc.name, diff.ObjectReflectDiff(masterActualIfPresent, tc.masterIfPresent)) 481 } 482 otherActual, otherActualIfPresent, otherOptional := BranchRequirements("other", presubmits["o/r"], &tc.requireManuallyTriggeredJobs) 483 if !reflect.DeepEqual(masterActual, tc.masterExpected) { 484 t.Errorf("%s: identified incorrect required contexts on branch other: : %s", tc.name, diff.ObjectReflectDiff(otherActual, tc.otherExpected)) 485 } 486 if !reflect.DeepEqual(otherOptional, tc.otherOptional) { 487 t.Errorf("%s: identified incorrect optional contexts on branch other: %s", tc.name, diff.ObjectReflectDiff(otherOptional, tc.otherOptional)) 488 } 489 if !reflect.DeepEqual(otherActualIfPresent, tc.otherIfPresent) { 490 t.Errorf("%s: identified incorrect if-present contexts on branch other: %s", tc.name, diff.ObjectReflectDiff(otherActualIfPresent, tc.otherIfPresent)) 491 } 492 } 493 } 494 495 func TestConfig_GetBranchProtection(t *testing.T) { 496 testCases := []struct { 497 name string 498 config Config 499 err bool 500 expected *Policy 501 }{ 502 { 503 name: "unprotected by default", 504 }, 505 { 506 name: "undefined org not protected", 507 config: Config{ 508 ProwConfig: ProwConfig{ 509 BranchProtection: BranchProtection{ 510 Policy: Policy{ 511 Protect: yes, 512 }, 513 Orgs: map[string]Org{ 514 "unknown": {}, 515 }, 516 }, 517 }, 518 }, 519 }, 520 { 521 name: "protect via config default", 522 config: Config{ 523 ProwConfig: ProwConfig{ 524 BranchProtection: BranchProtection{ 525 Policy: Policy{ 526 Protect: yes, 527 }, 528 Orgs: map[string]Org{ 529 "org": {}, 530 }, 531 }, 532 }, 533 }, 534 expected: &Policy{Protect: yes}, 535 }, 536 { 537 name: "protect via org default", 538 config: Config{ 539 ProwConfig: ProwConfig{ 540 BranchProtection: BranchProtection{ 541 Orgs: map[string]Org{ 542 "org": { 543 Policy: Policy{ 544 Protect: yes, 545 }, 546 }, 547 }, 548 }, 549 }, 550 }, 551 expected: &Policy{Protect: yes}, 552 }, 553 { 554 name: "protect via repo default", 555 config: Config{ 556 ProwConfig: ProwConfig{ 557 BranchProtection: BranchProtection{ 558 Orgs: map[string]Org{ 559 "org": { 560 Repos: map[string]Repo{ 561 "repo": { 562 Policy: Policy{ 563 Protect: yes, 564 }, 565 }, 566 }, 567 }, 568 }, 569 }, 570 }, 571 }, 572 expected: &Policy{Protect: yes}, 573 }, 574 { 575 name: "protect specific branch", 576 config: Config{ 577 ProwConfig: ProwConfig{ 578 BranchProtection: BranchProtection{ 579 Orgs: map[string]Org{ 580 "org": { 581 Repos: map[string]Repo{ 582 "repo": { 583 Branches: map[string]Branch{ 584 "branch": { 585 Policy: Policy{ 586 Protect: yes, 587 }, 588 }, 589 }, 590 }, 591 }, 592 }, 593 }, 594 }, 595 }, 596 }, 597 expected: &Policy{Protect: yes}, 598 }, 599 { 600 name: "ignore other org settings", 601 config: Config{ 602 ProwConfig: ProwConfig{ 603 BranchProtection: BranchProtection{ 604 Policy: Policy{ 605 Protect: no, 606 }, 607 Orgs: map[string]Org{ 608 "other": { 609 Policy: Policy{Protect: yes}, 610 }, 611 "org": {}, 612 }, 613 }, 614 }, 615 }, 616 expected: &Policy{Protect: no}, 617 }, 618 { 619 name: "defined branches must make a protection decision", 620 config: Config{ 621 ProwConfig: ProwConfig{ 622 BranchProtection: BranchProtection{ 623 Orgs: map[string]Org{ 624 "org": { 625 Repos: map[string]Repo{ 626 "repo": { 627 Branches: map[string]Branch{ 628 "branch": {}, 629 }, 630 }, 631 }, 632 }, 633 }, 634 }, 635 }, 636 }, 637 err: true, 638 }, 639 { 640 name: "pushers require protection", 641 config: Config{ 642 ProwConfig: ProwConfig{ 643 BranchProtection: BranchProtection{ 644 Policy: Policy{ 645 Protect: no, 646 Restrictions: &Restrictions{ 647 Teams: []string{"oncall"}, 648 }, 649 }, 650 Orgs: map[string]Org{ 651 "org": {}, 652 }, 653 }, 654 }, 655 }, 656 err: true, 657 }, 658 { 659 name: "required contexts require protection", 660 config: Config{ 661 ProwConfig: ProwConfig{ 662 BranchProtection: BranchProtection{ 663 Policy: Policy{ 664 Protect: no, 665 RequiredStatusChecks: &ContextPolicy{ 666 Contexts: []string{"test-foo"}, 667 }, 668 }, 669 Orgs: map[string]Org{ 670 "org": {}, 671 }, 672 }, 673 }, 674 }, 675 err: true, 676 }, 677 { 678 name: "child policy with defined parent can disable protection", 679 config: Config{ 680 ProwConfig: ProwConfig{ 681 BranchProtection: BranchProtection{ 682 AllowDisabledPolicies: utilpointer.Bool(true), 683 Policy: Policy{ 684 Protect: yes, 685 Restrictions: &Restrictions{ 686 Teams: []string{"oncall"}, 687 }, 688 }, 689 Orgs: map[string]Org{ 690 "org": { 691 Policy: Policy{ 692 Protect: no, 693 }, 694 }, 695 }, 696 }, 697 }, 698 }, 699 expected: &Policy{ 700 Protect: no, 701 Restrictions: &Restrictions{ 702 Teams: []string{"oncall"}, 703 }, 704 }, 705 }, 706 { 707 name: "Make required presubmits required", 708 config: Config{ 709 ProwConfig: ProwConfig{ 710 BranchProtection: BranchProtection{ 711 Policy: Policy{ 712 Protect: yes, 713 RequiredStatusChecks: &ContextPolicy{ 714 Contexts: []string{"cla"}, 715 }, 716 }, 717 Orgs: map[string]Org{ 718 "org": {}, 719 }, 720 }, 721 }, 722 JobConfig: JobConfig{ 723 PresubmitsStatic: map[string][]Presubmit{ 724 "org/repo": { 725 { 726 JobBase: JobBase{ 727 Name: "required presubmit", 728 }, 729 Reporter: Reporter{ 730 Context: "required presubmit", 731 }, 732 AlwaysRun: true, 733 }, 734 }, 735 }, 736 }, 737 }, 738 expected: &Policy{ 739 Protect: yes, 740 RequiredStatusChecks: &ContextPolicy{ 741 Contexts: []string{"required presubmit", "cla"}, 742 }, 743 }, 744 }, 745 { 746 name: "ProtectTested opts into protection", 747 config: Config{ 748 ProwConfig: ProwConfig{ 749 BranchProtection: BranchProtection{ 750 ProtectTested: utilpointer.Bool(true), 751 Orgs: map[string]Org{ 752 "org": {}, 753 }, 754 }, 755 }, 756 JobConfig: JobConfig{ 757 PresubmitsStatic: map[string][]Presubmit{ 758 "org/repo": { 759 { 760 JobBase: JobBase{ 761 Name: "required presubmit", 762 }, 763 Reporter: Reporter{ 764 Context: "required presubmit", 765 }, 766 AlwaysRun: true, 767 }, 768 }, 769 }, 770 }, 771 }, 772 expected: &Policy{ 773 Protect: yes, 774 RequiredStatusChecks: &ContextPolicy{ 775 Contexts: []string{"required presubmit"}, 776 }, 777 }, 778 }, 779 { 780 name: "required presubmits require protection", 781 config: Config{ 782 ProwConfig: ProwConfig{ 783 BranchProtection: BranchProtection{ 784 Policy: Policy{ 785 Protect: no, 786 }, 787 Orgs: map[string]Org{ 788 "org": {}, 789 }, 790 }, 791 }, 792 JobConfig: JobConfig{ 793 PresubmitsStatic: map[string][]Presubmit{ 794 "org/repo": { 795 { 796 JobBase: JobBase{ 797 Name: "required presubmit", 798 }, 799 Reporter: Reporter{ 800 Context: "required presubmit", 801 }, 802 AlwaysRun: true, 803 }, 804 }, 805 }, 806 }, 807 }, 808 err: true, 809 }, 810 { 811 name: "Optional presubmits do not force protection", 812 config: Config{ 813 ProwConfig: ProwConfig{ 814 BranchProtection: BranchProtection{ 815 ProtectTested: utilpointer.Bool(true), 816 Orgs: map[string]Org{ 817 "org": {}, 818 }, 819 }, 820 }, 821 JobConfig: JobConfig{ 822 PresubmitsStatic: map[string][]Presubmit{ 823 "org/repo": { 824 { 825 JobBase: JobBase{ 826 Name: "optional presubmit", 827 }, 828 Reporter: Reporter{ 829 Context: "optional presubmit", 830 }, 831 AlwaysRun: true, 832 Optional: true, 833 }, 834 }, 835 }, 836 }, 837 }, 838 }, 839 { 840 name: "Optional presubmits force protection if ProtectReposWithOptionalJobs is true", 841 config: Config{ 842 ProwConfig: ProwConfig{ 843 BranchProtection: BranchProtection{ 844 ProtectTested: utilpointer.Bool(true), 845 ProtectReposWithOptionalJobs: utilpointer.Bool(true), 846 Orgs: map[string]Org{ 847 "org": {}, 848 }, 849 }, 850 }, 851 JobConfig: JobConfig{ 852 PresubmitsStatic: map[string][]Presubmit{ 853 "org/repo": { 854 { 855 JobBase: JobBase{ 856 Name: "optional presubmit", 857 }, 858 Reporter: Reporter{ 859 Context: "optional presubmit", 860 }, 861 AlwaysRun: true, 862 Optional: true, 863 }, 864 }, 865 }, 866 }, 867 }, 868 expected: &Policy{ 869 Protect: yes, 870 RequiredStatusChecks: &ContextPolicy{}, 871 }, 872 }, 873 { 874 name: "Explicit configuration takes precedence over ProtectTested", 875 config: Config{ 876 ProwConfig: ProwConfig{ 877 BranchProtection: BranchProtection{ 878 ProtectTested: utilpointer.Bool(true), 879 Orgs: map[string]Org{ 880 "org": { 881 Policy: Policy{ 882 Protect: yes, 883 }, 884 }, 885 }, 886 }, 887 }, 888 JobConfig: JobConfig{ 889 PresubmitsStatic: map[string][]Presubmit{ 890 "org/repo": { 891 { 892 JobBase: JobBase{ 893 Name: "optional presubmit", 894 }, 895 Reporter: Reporter{ 896 Context: "optional presubmit", 897 }, 898 AlwaysRun: true, 899 Optional: true, 900 }, 901 }, 902 }, 903 }, 904 }, 905 expected: &Policy{Protect: yes}, 906 }, 907 { 908 name: "Explicit non-configuration takes precedence over ProtectTested", 909 config: Config{ 910 ProwConfig: ProwConfig{ 911 BranchProtection: BranchProtection{ 912 AllowDisabledJobPolicies: utilpointer.Bool(true), 913 ProtectTested: utilpointer.Bool(true), 914 Orgs: map[string]Org{ 915 "org": { 916 Repos: map[string]Repo{ 917 "repo": { 918 Policy: Policy{ 919 Protect: no, 920 }, 921 }, 922 }, 923 }, 924 }, 925 }, 926 }, 927 JobConfig: JobConfig{ 928 PresubmitsStatic: map[string][]Presubmit{ 929 "org/repo": { 930 { 931 JobBase: JobBase{ 932 Name: "required presubmit", 933 }, 934 Reporter: Reporter{ 935 Context: "required presubmit", 936 }, 937 AlwaysRun: true, 938 }, 939 }, 940 }, 941 }, 942 }, 943 expected: nil, 944 }, 945 } 946 947 for _, tc := range testCases { 948 t.Run(tc.name, func(t *testing.T) { 949 actual, err := tc.config.GetBranchProtection("org", "repo", "branch", tc.config.PresubmitsStatic["org/repo"]) 950 switch { 951 case err != nil: 952 if !tc.err { 953 t.Errorf("unexpected error: %v", err) 954 } 955 case tc.err: 956 t.Errorf("failed to receive an error") 957 default: 958 normalize(actual) 959 normalize(tc.expected) 960 if diff := cmp.Diff(actual, tc.expected); diff != "" { 961 t.Errorf("actual differs from expected: %s", diff) 962 } 963 } 964 }) 965 } 966 } 967 968 func TestReposWithDisabledPolicy(t *testing.T) { 969 testCases := []struct { 970 name string 971 config Config 972 expectedRepoWarns []string 973 }{ 974 { 975 name: "Warning is generated for repos with disabled policies", 976 config: Config{ 977 ProwConfig: ProwConfig{ 978 BranchProtection: BranchProtection{ 979 Policy: Policy{ 980 Protect: no, 981 RequiredStatusChecks: &ContextPolicy{ 982 Contexts: []string{"hello", "world"}, 983 }, 984 }, 985 AllowDisabledPolicies: utilpointer.Bool(true), 986 Orgs: map[string]Org{ 987 "org1": { 988 Repos: map[string]Repo{ 989 "repo1": {}, 990 "repo2": {}, 991 }, 992 }, 993 }, 994 }, 995 }, 996 }, 997 expectedRepoWarns: []string{"org1/repo1", "org1/repo2"}, 998 }, 999 { 1000 name: "No warnings if disabled policies are not allowed", 1001 config: Config{ 1002 ProwConfig: ProwConfig{ 1003 BranchProtection: BranchProtection{ 1004 Policy: Policy{ 1005 Protect: no, 1006 RequiredStatusChecks: &ContextPolicy{ 1007 Contexts: []string{"hello", "world"}, 1008 }, 1009 }, 1010 Orgs: map[string]Org{ 1011 "org1": { 1012 Repos: map[string]Repo{ 1013 "repo1": {}, 1014 }, 1015 }, 1016 }, 1017 }, 1018 }, 1019 }, 1020 expectedRepoWarns: []string{}, 1021 }, 1022 { 1023 name: "No warnings if repo has no policies", 1024 config: Config{ 1025 ProwConfig: ProwConfig{ 1026 BranchProtection: BranchProtection{ 1027 Orgs: map[string]Org{ 1028 "org1": { 1029 Repos: map[string]Repo{ 1030 "repo1": {}, 1031 }, 1032 }, 1033 }, 1034 }, 1035 }, 1036 }, 1037 expectedRepoWarns: []string{}, 1038 }, 1039 { 1040 name: "No warnings if repo's defined policy is protected", 1041 config: Config{ 1042 ProwConfig: ProwConfig{ 1043 BranchProtection: BranchProtection{ 1044 Orgs: map[string]Org{ 1045 "org1": { 1046 Repos: map[string]Repo{ 1047 "repo1": { 1048 Policy: Policy{ 1049 Protect: yes, 1050 RequiredStatusChecks: &ContextPolicy{ 1051 Contexts: []string{"hello", "world"}, 1052 }, 1053 }, 1054 }, 1055 }, 1056 }, 1057 }, 1058 }, 1059 }, 1060 }, 1061 expectedRepoWarns: []string{}, 1062 }, 1063 } 1064 1065 for _, tc := range testCases { 1066 t.Run(tc.name, func(t *testing.T) { 1067 repoWarns := tc.config.reposWithDisabledPolicy() 1068 if !reflect.DeepEqual(repoWarns, tc.expectedRepoWarns) { 1069 t.Errorf("actual repo warnings %+v != expected %+v", repoWarns, tc.expectedRepoWarns) 1070 } 1071 }) 1072 } 1073 } 1074 1075 func TestUnprotectedBranches(t *testing.T) { 1076 testCases := []struct { 1077 name string 1078 config Config 1079 presubmits map[string][]Presubmit 1080 expectedBranchWarns []string 1081 }{ 1082 { 1083 name: "Repos with unprotected branches are added to the warning list", 1084 config: Config{ 1085 ProwConfig: ProwConfig{ 1086 BranchProtection: BranchProtection{ 1087 Policy: Policy{ 1088 RequiredStatusChecks: &ContextPolicy{ 1089 Contexts: []string{"hello", "world"}, 1090 }, 1091 }, 1092 AllowDisabledPolicies: utilpointer.Bool(true), 1093 Orgs: map[string]Org{ 1094 "org1": { 1095 Repos: map[string]Repo{ 1096 "repo1": { 1097 Branches: map[string]Branch{ 1098 "branch1": { 1099 Policy{ 1100 Protect: no, 1101 }, 1102 }, 1103 }, 1104 }, 1105 "repo2": { 1106 Branches: map[string]Branch{ 1107 "branch1": { 1108 Policy{ 1109 Protect: no, 1110 }, 1111 }, 1112 }, 1113 }, 1114 }, 1115 }, 1116 }, 1117 }, 1118 }, 1119 }, 1120 expectedBranchWarns: []string{"org1/repo1=branch1", "org1/repo2=branch1"}, 1121 }, 1122 { 1123 name: "Warn only once about repos with multiple unprotected branches", 1124 config: Config{ 1125 ProwConfig: ProwConfig{ 1126 BranchProtection: BranchProtection{ 1127 Policy: Policy{ 1128 RequiredStatusChecks: &ContextPolicy{ 1129 Contexts: []string{"hello", "world"}, 1130 }, 1131 }, 1132 AllowDisabledPolicies: utilpointer.Bool(true), 1133 Orgs: map[string]Org{ 1134 "org1": { 1135 Repos: map[string]Repo{ 1136 "repo1": { 1137 Branches: map[string]Branch{ 1138 "branch1": { 1139 Policy{ 1140 Protect: no, 1141 }, 1142 }, 1143 "branch2": { 1144 Policy{ 1145 Protect: no, 1146 }, 1147 }, 1148 }, 1149 }, 1150 "repo2": { 1151 Branches: map[string]Branch{ 1152 "branch1": { 1153 Policy{ 1154 Protect: no, 1155 }, 1156 }, 1157 }, 1158 }, 1159 }, 1160 }, 1161 }, 1162 }, 1163 }, 1164 }, 1165 expectedBranchWarns: []string{"org1/repo1=branch1,branch2", "org1/repo2=branch1"}, 1166 }, 1167 { 1168 name: "No warnings if repo has no policies", 1169 config: Config{ 1170 ProwConfig: ProwConfig{ 1171 BranchProtection: BranchProtection{ 1172 Orgs: map[string]Org{ 1173 "org1": { 1174 Repos: map[string]Repo{ 1175 "repo1": { 1176 Branches: map[string]Branch{ 1177 "branch1": { 1178 Policy{ 1179 Protect: no, 1180 }, 1181 }, 1182 }, 1183 }, 1184 }, 1185 }, 1186 }, 1187 }, 1188 }, 1189 }, 1190 expectedBranchWarns: []string{}, 1191 }, 1192 { 1193 name: "No warnings if repo's defined policy is protected", 1194 config: Config{ 1195 ProwConfig: ProwConfig{ 1196 BranchProtection: BranchProtection{ 1197 Orgs: map[string]Org{ 1198 "org1": { 1199 Repos: map[string]Repo{ 1200 "repo1": { 1201 Policy: Policy{ 1202 Protect: yes, 1203 RequiredStatusChecks: &ContextPolicy{ 1204 Contexts: []string{"hello", "world"}, 1205 }, 1206 }, 1207 Branches: map[string]Branch{ 1208 "branch1": { 1209 Policy{ 1210 Protect: no, 1211 }, 1212 }, 1213 }, 1214 }, 1215 }, 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, 1221 expectedBranchWarns: []string{}, 1222 }, 1223 { 1224 name: "Warning if a branch has a required context but has protect: false", 1225 config: Config{ 1226 ProwConfig: ProwConfig{ 1227 BranchProtection: BranchProtection{ 1228 AllowDisabledJobPolicies: utilpointer.Bool(true), 1229 Orgs: map[string]Org{ 1230 "org1": { 1231 Repos: map[string]Repo{ 1232 "repo1": { 1233 Branches: map[string]Branch{ 1234 "branch1": { 1235 Policy{ 1236 Protect: no, 1237 }, 1238 }, 1239 }, 1240 }, 1241 }, 1242 }, 1243 }, 1244 }, 1245 }, 1246 }, 1247 presubmits: map[string][]Presubmit{ 1248 "org1/repo1": { 1249 { 1250 JobBase: JobBase{ 1251 Name: "always-run", 1252 }, 1253 AlwaysRun: true, 1254 }, 1255 }, 1256 }, 1257 expectedBranchWarns: []string{"org1/repo1=branch1"}, 1258 }, 1259 { 1260 name: "No warnings for a branch with no required context and protect: false", 1261 config: Config{ 1262 ProwConfig: ProwConfig{ 1263 BranchProtection: BranchProtection{ 1264 AllowDisabledJobPolicies: utilpointer.Bool(true), 1265 Orgs: map[string]Org{ 1266 "org1": { 1267 Repos: map[string]Repo{ 1268 "repo1": { 1269 Branches: map[string]Branch{ 1270 "branch1": { 1271 Policy{ 1272 Protect: no, 1273 }, 1274 }, 1275 }, 1276 }, 1277 }, 1278 }, 1279 }, 1280 }, 1281 }, 1282 }, 1283 presubmits: map[string][]Presubmit{ 1284 "org1/repo1": { 1285 { 1286 JobBase: JobBase{ 1287 Name: "optional", 1288 }, 1289 Optional: true, 1290 }, 1291 }, 1292 }, 1293 expectedBranchWarns: []string{}, 1294 }, 1295 { 1296 name: "No warnings if allow_disabled_job_policies is not set", 1297 config: Config{ 1298 ProwConfig: ProwConfig{ 1299 BranchProtection: BranchProtection{ 1300 Orgs: map[string]Org{ 1301 "org1": { 1302 Repos: map[string]Repo{ 1303 "repo1": { 1304 Branches: map[string]Branch{ 1305 "branch1": { 1306 Policy{ 1307 Protect: no, 1308 }, 1309 }, 1310 }, 1311 }, 1312 }, 1313 }, 1314 }, 1315 }, 1316 }, 1317 }, 1318 presubmits: map[string][]Presubmit{ 1319 "org1/repo1": { 1320 { 1321 JobBase: JobBase{ 1322 Name: "always-run", 1323 }, 1324 AlwaysRun: true, 1325 }, 1326 }, 1327 }, 1328 expectedBranchWarns: []string{}, 1329 }, 1330 } 1331 1332 for _, tc := range testCases { 1333 t.Run(tc.name, func(t *testing.T) { 1334 branchWarns := tc.config.unprotectedBranches(tc.presubmits) 1335 if !reflect.DeepEqual(branchWarns, tc.expectedBranchWarns) { 1336 t.Errorf("actual branch warnings %+v != expected %+v", branchWarns, tc.expectedBranchWarns) 1337 } 1338 }) 1339 } 1340 }