github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/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 "k8s.io/apimachinery/pkg/util/diff" 25 ) 26 27 var ( 28 y = true 29 n = false 30 yes = &y 31 no = &n 32 ) 33 34 func normalize(policy *Policy) { 35 if policy == nil || policy.RequiredStatusChecks == nil { 36 return 37 } 38 sort.Strings(policy.RequiredStatusChecks.Contexts) 39 } 40 41 func TestSelectBool(t *testing.T) { 42 cases := []struct { 43 name string 44 parent *bool 45 child *bool 46 expected *bool 47 }{ 48 { 49 name: "default is nil", 50 }, 51 { 52 name: "use child if set", 53 child: yes, 54 expected: yes, 55 }, 56 { 57 name: "child overrides parent", 58 child: yes, 59 parent: no, 60 expected: yes, 61 }, 62 { 63 name: "use parent if child unset", 64 parent: no, 65 expected: no, 66 }, 67 } 68 for _, tc := range cases { 69 t.Run(tc.name, func(t *testing.T) { 70 actual := selectBool(tc.parent, tc.child) 71 if !reflect.DeepEqual(actual, tc.expected) { 72 t.Errorf("actual %v != expected %v", actual, tc.expected) 73 } 74 }) 75 } 76 } 77 78 func TestSelectInt(t *testing.T) { 79 one := 1 80 two := 2 81 cases := []struct { 82 name string 83 parent *int 84 child *int 85 expected *int 86 }{ 87 { 88 name: "default is nil", 89 }, 90 { 91 name: "use child if set", 92 child: &one, 93 expected: &one, 94 }, 95 { 96 name: "child overrides parent", 97 child: &one, 98 parent: &two, 99 expected: &one, 100 }, 101 { 102 name: "use parent if child unset", 103 parent: &two, 104 expected: &two, 105 }, 106 } 107 for _, tc := range cases { 108 t.Run(tc.name, func(t *testing.T) { 109 actual := selectInt(tc.parent, tc.child) 110 if !reflect.DeepEqual(actual, tc.expected) { 111 t.Errorf("actual %v != expected %v", actual, tc.expected) 112 } 113 }) 114 } 115 } 116 117 func TestUnionStrings(t *testing.T) { 118 cases := []struct { 119 name string 120 parent []string 121 child []string 122 expected []string 123 }{ 124 { 125 name: "empty list", 126 }, 127 { 128 name: "all parent items", 129 parent: []string{"hi", "there"}, 130 expected: []string{"hi", "there"}, 131 }, 132 { 133 name: "all child items", 134 child: []string{"hi", "there"}, 135 expected: []string{"hi", "there"}, 136 }, 137 { 138 name: "both child and parent items, no duplicates", 139 child: []string{"hi", "world"}, 140 parent: []string{"hi", "there"}, 141 expected: []string{"hi", "there", "world"}, 142 }, 143 } 144 for _, tc := range cases { 145 t.Run(tc.name, func(t *testing.T) { 146 actual := unionStrings(tc.parent, tc.child) 147 sort.Strings(actual) 148 sort.Strings(tc.expected) 149 if !reflect.DeepEqual(actual, tc.expected) { 150 t.Errorf("actual %v != expected %v", actual, tc.expected) 151 } 152 }) 153 } 154 } 155 156 func TestApply(test *testing.T) { 157 t := true 158 f := false 159 basic := Policy{ 160 Protect: &t, 161 } 162 ebasic := Policy{ 163 Protect: &t, 164 } 165 cases := []struct { 166 name string 167 parent Policy 168 child Policy 169 expected Policy 170 }{ 171 { 172 name: "nil child", 173 parent: basic, 174 expected: ebasic, 175 }, 176 { 177 name: "merge parent and child", 178 parent: Policy{ 179 Protect: &t, 180 }, 181 child: Policy{ 182 Admins: &f, 183 }, 184 expected: Policy{ 185 Protect: &t, 186 Admins: &f, 187 }, 188 }, 189 { 190 name: "child overrides parent", 191 parent: Policy{ 192 Protect: &t, 193 }, 194 child: Policy{ 195 Protect: &f, 196 }, 197 expected: Policy{ 198 Protect: &f, 199 }, 200 }, 201 { 202 name: "append strings", 203 parent: Policy{ 204 RequiredStatusChecks: &ContextPolicy{ 205 Contexts: []string{"hello", "world"}, 206 }, 207 }, 208 child: Policy{ 209 RequiredStatusChecks: &ContextPolicy{ 210 Contexts: []string{"world", "of", "thrones"}, 211 }, 212 }, 213 expected: Policy{ 214 RequiredStatusChecks: &ContextPolicy{ 215 Contexts: []string{"hello", "of", "thrones", "world"}, 216 }, 217 }, 218 }, 219 { 220 name: "merge struct", 221 parent: Policy{ 222 RequiredStatusChecks: &ContextPolicy{ 223 Contexts: []string{"hi"}, 224 }, 225 }, 226 child: Policy{ 227 RequiredStatusChecks: &ContextPolicy{ 228 Strict: &t, 229 }, 230 }, 231 expected: Policy{ 232 RequiredStatusChecks: &ContextPolicy{ 233 Contexts: []string{"hi"}, 234 Strict: &t, 235 }, 236 }, 237 }, 238 { 239 name: "nil child struct", 240 parent: Policy{ 241 RequiredStatusChecks: &ContextPolicy{ 242 Strict: &f, 243 }, 244 }, 245 child: Policy{ 246 Protect: &t, 247 }, 248 expected: Policy{ 249 RequiredStatusChecks: &ContextPolicy{ 250 Strict: &f, 251 }, 252 Protect: &t, 253 }, 254 }, 255 { 256 name: "nil parent struct", 257 child: Policy{ 258 RequiredStatusChecks: &ContextPolicy{ 259 Strict: &f, 260 }, 261 }, 262 parent: Policy{ 263 Protect: &t, 264 }, 265 expected: Policy{ 266 RequiredStatusChecks: &ContextPolicy{ 267 Strict: &f, 268 }, 269 Protect: &t, 270 }, 271 }, 272 } 273 274 for _, tc := range cases { 275 test.Run(tc.name, func(test *testing.T) { 276 defer func() { 277 if r := recover(); r != nil { 278 test.Errorf("unexpected panic: %s", r) 279 } 280 }() 281 actual, err := tc.parent.Apply(tc.child) 282 if err != nil { 283 test.Fatalf("unexpected error: %v", err) 284 } 285 normalize(&actual) 286 normalize(&tc.expected) 287 if !reflect.DeepEqual(actual, tc.expected) { 288 test.Errorf("bad merged policy:\n%s", diff.ObjectReflectDiff(tc.expected, actual)) 289 } 290 }) 291 } 292 } 293 294 func TestJobRequirements(t *testing.T) { 295 cases := []struct { 296 name string 297 config []Presubmit 298 masterExpected, otherExpected []string 299 masterOptional, otherOptional []string 300 }{ 301 { 302 name: "basic", 303 config: []Presubmit{ 304 { 305 Context: "always-run", 306 AlwaysRun: true, 307 SkipReport: false, 308 }, 309 { 310 Context: "run-if-changed", 311 RunIfChanged: "foo", 312 AlwaysRun: false, 313 SkipReport: false, 314 }, 315 { 316 Context: "not-always", 317 AlwaysRun: false, 318 SkipReport: false, 319 }, 320 { 321 Context: "skip-report", 322 AlwaysRun: true, 323 SkipReport: true, 324 Brancher: Brancher{ 325 SkipBranches: []string{"master"}, 326 }, 327 }, 328 { 329 Context: "optional", 330 AlwaysRun: true, 331 SkipReport: false, 332 Optional: true, 333 }, 334 }, 335 masterExpected: []string{"always-run", "run-if-changed"}, 336 masterOptional: []string{"optional"}, 337 otherExpected: []string{"always-run", "run-if-changed"}, 338 otherOptional: []string{"skip-report", "optional"}, 339 }, 340 { 341 name: "children", 342 config: []Presubmit{ 343 { 344 Context: "always-run", 345 AlwaysRun: true, 346 SkipReport: false, 347 RunAfterSuccess: []Presubmit{ 348 { 349 Context: "include-me", 350 }, 351 }, 352 }, 353 { 354 Context: "run-if-changed", 355 RunIfChanged: "foo", 356 SkipReport: true, 357 AlwaysRun: false, 358 RunAfterSuccess: []Presubmit{ 359 { 360 Context: "me2", 361 }, 362 }, 363 }, 364 { 365 Context: "run-and-skip", 366 AlwaysRun: true, 367 SkipReport: true, 368 RunAfterSuccess: []Presubmit{ 369 { 370 Context: "also-me-3", 371 }, 372 }, 373 }, 374 { 375 Context: "optional", 376 AlwaysRun: false, 377 SkipReport: false, 378 RunAfterSuccess: []Presubmit{ 379 { 380 Context: "no thanks", 381 }, 382 }, 383 }, 384 { 385 Context: "hidden-grandpa", 386 AlwaysRun: true, 387 SkipReport: true, 388 RunAfterSuccess: []Presubmit{ 389 { 390 Context: "hidden-parent", 391 Optional: true, 392 AlwaysRun: false, 393 Brancher: Brancher{ 394 Branches: []string{"master"}, 395 }, 396 RunAfterSuccess: []Presubmit{ 397 { 398 Context: "visible-kid", 399 Brancher: Brancher{ 400 Branches: []string{"master"}, 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 masterExpected: []string{ 409 "always-run", "include-me", 410 "me2", 411 "also-me-3", 412 "visible-kid", 413 }, 414 masterOptional: []string{ 415 "run-if-changed", 416 "run-and-skip", 417 "hidden-grandpa", 418 "hidden-parent"}, 419 otherExpected: []string{ 420 "always-run", "include-me", 421 "me2", 422 "also-me-3", 423 }, 424 otherOptional: []string{ 425 "run-if-changed", 426 "run-and-skip", 427 "hidden-grandpa"}, 428 }, 429 } 430 431 for _, tc := range cases { 432 if err := SetPresubmitRegexes(tc.config); err != nil { 433 t.Fatalf("could not set regexes: %v", err) 434 } 435 masterActual, masterOptional := jobRequirements(tc.config, "master", false) 436 if !reflect.DeepEqual(masterActual, tc.masterExpected) { 437 t.Errorf("branch: master - %s: actual %v != expected %v", tc.name, masterActual, tc.masterExpected) 438 } 439 if !reflect.DeepEqual(masterOptional, tc.masterOptional) { 440 t.Errorf("branch: master - optional - %s: actual %v != expected %v", tc.name, masterOptional, tc.masterOptional) 441 } 442 otherActual, otherOptional := jobRequirements(tc.config, "other", false) 443 if !reflect.DeepEqual(masterActual, tc.masterExpected) { 444 t.Errorf("branch: other - %s: actual %v != expected %v", tc.name, otherActual, tc.otherExpected) 445 } 446 if !reflect.DeepEqual(otherOptional, tc.otherOptional) { 447 t.Errorf("branch: other - optional - %s: actual %v != expected %v", tc.name, otherOptional, tc.otherOptional) 448 } 449 } 450 } 451 452 func TestConfig_GetBranchProtection(t *testing.T) { 453 testCases := []struct { 454 name string 455 config Config 456 org, repo, branch string 457 err bool 458 expected *Policy 459 }{ 460 { 461 name: "unprotected by default", 462 }, 463 { 464 name: "undefined org not protected", 465 config: Config{ 466 ProwConfig: ProwConfig{ 467 BranchProtection: BranchProtection{ 468 Policy: Policy{ 469 Protect: yes, 470 }, 471 Orgs: map[string]Org{ 472 "unknown": {}, 473 }, 474 }, 475 }, 476 }, 477 }, 478 { 479 name: "protect via config default", 480 config: Config{ 481 ProwConfig: ProwConfig{ 482 BranchProtection: BranchProtection{ 483 Policy: Policy{ 484 Protect: yes, 485 }, 486 Orgs: map[string]Org{ 487 "org": {}, 488 }, 489 }, 490 }, 491 }, 492 expected: &Policy{Protect: yes}, 493 }, 494 { 495 name: "protect via org default", 496 config: Config{ 497 ProwConfig: ProwConfig{ 498 BranchProtection: BranchProtection{ 499 Orgs: map[string]Org{ 500 "org": { 501 Policy: Policy{ 502 Protect: yes, 503 }, 504 }, 505 }, 506 }, 507 }, 508 }, 509 expected: &Policy{Protect: yes}, 510 }, 511 { 512 name: "protect via repo default", 513 config: Config{ 514 ProwConfig: ProwConfig{ 515 BranchProtection: BranchProtection{ 516 Orgs: map[string]Org{ 517 "org": { 518 Repos: map[string]Repo{ 519 "repo": { 520 Policy: Policy{ 521 Protect: yes, 522 }, 523 }, 524 }, 525 }, 526 }, 527 }, 528 }, 529 }, 530 expected: &Policy{Protect: yes}, 531 }, 532 { 533 name: "protect specific branch", 534 config: Config{ 535 ProwConfig: ProwConfig{ 536 BranchProtection: BranchProtection{ 537 Orgs: map[string]Org{ 538 "org": { 539 Repos: map[string]Repo{ 540 "repo": { 541 Branches: map[string]Branch{ 542 "branch": { 543 Policy: Policy{ 544 Protect: yes, 545 }, 546 }, 547 }, 548 }, 549 }, 550 }, 551 }, 552 }, 553 }, 554 }, 555 expected: &Policy{Protect: yes}, 556 }, 557 { 558 name: "ignore other org settings", 559 config: Config{ 560 ProwConfig: ProwConfig{ 561 BranchProtection: BranchProtection{ 562 Policy: Policy{ 563 Protect: no, 564 }, 565 Orgs: map[string]Org{ 566 "other": { 567 Policy: Policy{Protect: yes}, 568 }, 569 "org": {}, 570 }, 571 }, 572 }, 573 }, 574 expected: &Policy{Protect: no}, 575 }, 576 { 577 name: "defined branches must make a protection decision", 578 config: Config{ 579 ProwConfig: ProwConfig{ 580 BranchProtection: BranchProtection{ 581 Orgs: map[string]Org{ 582 "org": { 583 Repos: map[string]Repo{ 584 "repo": { 585 Branches: map[string]Branch{ 586 "branch": {}, 587 }, 588 }, 589 }, 590 }, 591 }, 592 }, 593 }, 594 }, 595 err: true, 596 }, 597 { 598 name: "pushers require protection", 599 config: Config{ 600 ProwConfig: ProwConfig{ 601 BranchProtection: BranchProtection{ 602 Policy: Policy{ 603 Protect: no, 604 Restrictions: &Restrictions{ 605 Teams: []string{"oncall"}, 606 }, 607 }, 608 Orgs: map[string]Org{ 609 "org": {}, 610 }, 611 }, 612 }, 613 }, 614 err: true, 615 }, 616 { 617 name: "required contexts require protection", 618 config: Config{ 619 ProwConfig: ProwConfig{ 620 BranchProtection: BranchProtection{ 621 Policy: Policy{ 622 Protect: no, 623 RequiredStatusChecks: &ContextPolicy{ 624 Contexts: []string{"test-foo"}, 625 }, 626 }, 627 Orgs: map[string]Org{ 628 "org": {}, 629 }, 630 }, 631 }, 632 }, 633 err: true, 634 }, 635 { 636 name: "child policy with defined parent can disable protection", 637 config: Config{ 638 ProwConfig: ProwConfig{ 639 BranchProtection: BranchProtection{ 640 AllowDisabledPolicies: true, 641 Policy: Policy{ 642 Protect: yes, 643 Restrictions: &Restrictions{ 644 Teams: []string{"oncall"}, 645 }, 646 }, 647 Orgs: map[string]Org{ 648 "org": { 649 Policy: Policy{ 650 Protect: no, 651 }, 652 }, 653 }, 654 }, 655 }, 656 }, 657 expected: &Policy{ 658 Protect: no, 659 }, 660 }, 661 { 662 name: "Make required presubmits required", 663 config: Config{ 664 ProwConfig: ProwConfig{ 665 BranchProtection: BranchProtection{ 666 Policy: Policy{ 667 Protect: yes, 668 RequiredStatusChecks: &ContextPolicy{ 669 Contexts: []string{"cla"}, 670 }, 671 }, 672 Orgs: map[string]Org{ 673 "org": {}, 674 }, 675 }, 676 }, 677 JobConfig: JobConfig{ 678 Presubmits: map[string][]Presubmit{ 679 "org/repo": { 680 { 681 Name: "required presubmit", 682 Context: "required presubmit", 683 AlwaysRun: true, 684 }, 685 }, 686 }, 687 }, 688 }, 689 expected: &Policy{ 690 Protect: yes, 691 RequiredStatusChecks: &ContextPolicy{ 692 Contexts: []string{"required presubmit", "cla"}, 693 }, 694 }, 695 }, 696 { 697 name: "ProtectTested opts into protection", 698 config: Config{ 699 ProwConfig: ProwConfig{ 700 BranchProtection: BranchProtection{ 701 ProtectTested: true, 702 Orgs: map[string]Org{ 703 "org": {}, 704 }, 705 }, 706 }, 707 JobConfig: JobConfig{ 708 Presubmits: map[string][]Presubmit{ 709 "org/repo": { 710 { 711 Name: "required presubmit", 712 Context: "required presubmit", 713 AlwaysRun: true, 714 }, 715 }, 716 }, 717 }, 718 }, 719 expected: &Policy{ 720 Protect: yes, 721 RequiredStatusChecks: &ContextPolicy{ 722 Contexts: []string{"required presubmit"}, 723 }, 724 }, 725 }, 726 { 727 name: "required presubmits require protection", 728 config: Config{ 729 ProwConfig: ProwConfig{ 730 BranchProtection: BranchProtection{ 731 Policy: Policy{ 732 Protect: no, 733 }, 734 Orgs: map[string]Org{ 735 "org": {}, 736 }, 737 }, 738 }, 739 JobConfig: JobConfig{ 740 Presubmits: map[string][]Presubmit{ 741 "org/repo": { 742 { 743 Name: "required presubmit", 744 Context: "required presubmit", 745 AlwaysRun: true, 746 }, 747 }, 748 }, 749 }, 750 }, 751 err: true, 752 }, 753 { 754 name: "Optional presubmits do not force protection", 755 config: Config{ 756 ProwConfig: ProwConfig{ 757 BranchProtection: BranchProtection{ 758 ProtectTested: true, 759 Orgs: map[string]Org{ 760 "org": {}, 761 }, 762 }, 763 }, 764 JobConfig: JobConfig{ 765 Presubmits: map[string][]Presubmit{ 766 "org/repo": { 767 { 768 Name: "optional presubmit", 769 Context: "optional presubmit", 770 AlwaysRun: true, 771 Optional: true, 772 }, 773 }, 774 }, 775 }, 776 }, 777 }, 778 { 779 name: "Explicit configuration takes precedence over ProtectTested", 780 config: Config{ 781 ProwConfig: ProwConfig{ 782 BranchProtection: BranchProtection{ 783 ProtectTested: true, 784 Orgs: map[string]Org{ 785 "org": { 786 Policy: Policy{ 787 Protect: yes, 788 }, 789 }, 790 }, 791 }, 792 }, 793 JobConfig: JobConfig{ 794 Presubmits: map[string][]Presubmit{ 795 "org/repo": { 796 { 797 Name: "optional presubmit", 798 Context: "optional presubmit", 799 AlwaysRun: true, 800 Optional: true, 801 }, 802 }, 803 }, 804 }, 805 }, 806 expected: &Policy{Protect: yes}, 807 }, 808 } 809 810 for _, tc := range testCases { 811 t.Run(tc.name, func(t *testing.T) { 812 actual, err := tc.config.GetBranchProtection("org", "repo", "branch") 813 switch { 814 case err != nil: 815 if !tc.err { 816 t.Errorf("unexpected error: %v", err) 817 } 818 case err == nil && tc.err: 819 t.Errorf("failed to receive an error") 820 default: 821 normalize(actual) 822 normalize(tc.expected) 823 if !reflect.DeepEqual(actual, tc.expected) { 824 t.Errorf("actual %+v != expected %+v", actual, tc.expected) 825 } 826 } 827 }) 828 } 829 }