github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/trigger/generic-comment_test.go (about) 1 /* 2 Copyright 2016 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 trigger 18 19 import ( 20 "fmt" 21 "log" 22 "reflect" 23 "strings" 24 "testing" 25 26 "github.com/sirupsen/logrus" 27 "k8s.io/apimachinery/pkg/util/sets" 28 clienttesting "k8s.io/client-go/testing" 29 30 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 31 "sigs.k8s.io/prow/pkg/client/clientset/versioned/fake" 32 "sigs.k8s.io/prow/pkg/config" 33 "sigs.k8s.io/prow/pkg/github" 34 "sigs.k8s.io/prow/pkg/github/fakegithub" 35 "sigs.k8s.io/prow/pkg/labels" 36 "sigs.k8s.io/prow/pkg/pjutil" 37 "sigs.k8s.io/prow/pkg/plugins" 38 ) 39 40 func issueLabels(labels ...string) []string { 41 var ls []string 42 for _, label := range labels { 43 ls = append(ls, fmt.Sprintf("org/repo#0:%s", label)) 44 } 45 return ls 46 } 47 48 type testcase struct { 49 name string 50 51 Author string 52 PRAuthor string 53 Body string 54 State string 55 IsPR bool 56 Branch string 57 ShouldBuild bool 58 AddedLabels []string 59 RemovedLabels []string 60 StartsExactly string 61 Presubmits map[string][]config.Presubmit 62 IssueLabels []string 63 IgnoreOkToTest bool 64 AddedComment string 65 } 66 67 func TestHandleGenericComment(t *testing.T) { 68 helpComment := "The following commands are available to trigger required jobs:\n* `/test jib`\n* `/test job`\n\n" 69 helpTestAllWithJobsComment := fmt.Sprintf("Use `/test all` to run the following jobs that were automatically triggered:%s\n\n", "\n* `job`") 70 var testcases = []testcase{ 71 { 72 name: "Not a PR.", 73 74 Author: "trusted-member", 75 Body: "/ok-to-test", 76 State: "open", 77 IsPR: false, 78 ShouldBuild: false, 79 }, 80 { 81 name: "Closed PR.", 82 83 Author: "trusted-member", 84 Body: "/ok-to-test", 85 State: "closed", 86 IsPR: true, 87 ShouldBuild: false, 88 }, 89 { 90 name: "Comment by a bot.", 91 92 Author: "k8s-ci-robot", 93 Body: "/ok-to-test", 94 State: "open", 95 IsPR: true, 96 ShouldBuild: false, 97 }, 98 { 99 name: "Irrelevant comment leads to no action.", 100 101 Author: "trusted-member", 102 Body: "Nice weather outside, right?", 103 State: "open", 104 IsPR: true, 105 ShouldBuild: false, 106 }, 107 { 108 name: "Non-trusted member's ok to test.", 109 110 Author: "untrusted-member", 111 Body: "/ok-to-test", 112 State: "open", 113 IsPR: true, 114 ShouldBuild: false, 115 }, 116 { 117 name: "accept /test from non-trusted member if PR author is trusted", 118 Author: "untrusted-member", 119 PRAuthor: "trusted-member", 120 Body: "/test all", 121 State: "open", 122 IsPR: true, 123 ShouldBuild: true, 124 }, 125 { 126 name: "reject /test from non-trusted member when PR author is untrusted", 127 Author: "untrusted-member", 128 PRAuthor: "untrusted-member", 129 Body: "/test all", 130 State: "open", 131 IsPR: true, 132 ShouldBuild: false, 133 }, 134 { 135 name: `Non-trusted member after "/ok-to-test".`, 136 137 Author: "untrusted-member", 138 Body: "/test all", 139 State: "open", 140 IsPR: true, 141 ShouldBuild: true, 142 IssueLabels: issueLabels(labels.OkToTest), 143 }, 144 { 145 name: `Non-trusted member after "/ok-to-test", needs-ok-to-test label wasn't deleted.`, 146 147 Author: "untrusted-member", 148 Body: "/test all", 149 State: "open", 150 IsPR: true, 151 ShouldBuild: true, 152 IssueLabels: issueLabels(labels.NeedsOkToTest, labels.OkToTest), 153 RemovedLabels: issueLabels(labels.NeedsOkToTest), 154 }, 155 { 156 name: "Trusted member's ok to test, IgnoreOkToTest", 157 158 Author: "trusted-member", 159 Body: "/ok-to-test", 160 State: "open", 161 IsPR: true, 162 ShouldBuild: false, 163 IgnoreOkToTest: true, 164 }, 165 { 166 name: "Trusted member's ok to test", 167 168 Author: "trusted-member", 169 Body: "looks great, thanks!\n/ok-to-test", 170 State: "open", 171 IsPR: true, 172 ShouldBuild: true, 173 AddedLabels: issueLabels(labels.OkToTest), 174 }, 175 { 176 name: "Trusted member's ok to test, trailing space.", 177 178 Author: "trusted-member", 179 Body: "looks great, thanks!\n/ok-to-test \r", 180 State: "open", 181 IsPR: true, 182 ShouldBuild: true, 183 AddedLabels: issueLabels(labels.OkToTest), 184 }, 185 { 186 name: "Trusted member's not ok to test.", 187 188 Author: "trusted-member", 189 Body: "not /ok-to-test", 190 State: "open", 191 IsPR: true, 192 ShouldBuild: false, 193 }, 194 { 195 name: "Trusted member's test this.", 196 197 Author: "trusted-member", 198 Body: "/test all", 199 State: "open", 200 IsPR: true, 201 ShouldBuild: true, 202 }, 203 { 204 name: "Wrong branch", 205 206 Author: "trusted-member", 207 Body: "/test all", 208 State: "open", 209 IsPR: true, 210 Branch: "other", 211 ShouldBuild: false, 212 }, 213 { 214 name: "Retest with one running and one failed", 215 216 Author: "trusted-member", 217 Body: "/retest", 218 State: "open", 219 IsPR: true, 220 ShouldBuild: true, 221 StartsExactly: "pull-jib", 222 }, 223 { 224 name: "Retest with one running and one failed, trailing space.", 225 226 Author: "trusted-member", 227 Body: "/retest \r", 228 State: "open", 229 IsPR: true, 230 ShouldBuild: true, 231 StartsExactly: "pull-jib", 232 }, 233 { 234 name: "test of silly regex job", 235 Author: "trusted-member", 236 Body: "Nice weather outside, right?", 237 State: "open", 238 IsPR: true, 239 Presubmits: map[string][]config.Presubmit{ 240 "org/repo": { 241 { 242 JobBase: config.JobBase{ 243 Name: "jab", 244 }, 245 Brancher: config.Brancher{Branches: []string{"master"}}, 246 Reporter: config.Reporter{ 247 Context: "pull-jab", 248 }, 249 Trigger: "Nice weather outside, right?", 250 RerunCommand: "Nice weather outside, right?", 251 }, 252 }, 253 }, 254 ShouldBuild: true, 255 StartsExactly: "pull-jab", 256 }, 257 { 258 name: "needs-ok-to-test label is removed when no presubmit runs by default", 259 260 Author: "trusted-member", 261 Body: "/ok-to-test", 262 State: "open", 263 IsPR: true, 264 ShouldBuild: false, 265 Presubmits: map[string][]config.Presubmit{ 266 "org/repo": { 267 { 268 JobBase: config.JobBase{ 269 Name: "job", 270 }, 271 AlwaysRun: false, 272 Reporter: config.Reporter{ 273 Context: "pull-job", 274 }, 275 Trigger: `(?m)^/test (?:.*? )?job(?: .*?)?$`, 276 RerunCommand: `/test job`, 277 }, 278 { 279 JobBase: config.JobBase{ 280 Name: "jib", 281 }, 282 AlwaysRun: false, 283 Reporter: config.Reporter{ 284 Context: "pull-jib", 285 }, 286 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 287 RerunCommand: `/test jib`, 288 }, 289 }, 290 }, 291 IssueLabels: issueLabels(labels.NeedsOkToTest), 292 AddedLabels: issueLabels(labels.OkToTest), 293 RemovedLabels: issueLabels(labels.NeedsOkToTest), 294 }, 295 { 296 name: "Wrong branch w/ SkipReport", 297 Author: "trusted-member", 298 Body: "/test all", 299 Branch: "other", 300 State: "open", 301 IsPR: true, 302 Presubmits: map[string][]config.Presubmit{ 303 "org/repo": { 304 { 305 JobBase: config.JobBase{ 306 Name: "job", 307 }, 308 AlwaysRun: true, 309 Reporter: config.Reporter{ 310 SkipReport: true, 311 Context: "pull-job", 312 }, 313 Trigger: `(?m)^/test (?:.*? )?job(?: .*?)?$`, 314 RerunCommand: `/test job`, 315 Brancher: config.Brancher{Branches: []string{"master"}}, 316 }, 317 }, 318 }, 319 }, 320 { 321 name: "Retest of run_if_changed job that hasn't run. Changes require job", 322 Author: "trusted-member", 323 Body: "/retest", 324 State: "open", 325 IsPR: true, 326 Presubmits: map[string][]config.Presubmit{ 327 "org/repo": { 328 { 329 JobBase: config.JobBase{ 330 Name: "jab", 331 }, 332 RegexpChangeMatcher: config.RegexpChangeMatcher{ 333 RunIfChanged: "CHANGED", 334 }, 335 Reporter: config.Reporter{ 336 SkipReport: true, 337 Context: "pull-jab", 338 }, 339 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 340 RerunCommand: `/test jab`, 341 }, 342 }, 343 }, 344 ShouldBuild: true, 345 StartsExactly: "pull-jab", 346 }, 347 { 348 name: "Retest of skip_if_only_changed job that hasn't run. Changes require job", 349 Author: "trusted-member", 350 Body: "/retest", 351 State: "open", 352 IsPR: true, 353 Presubmits: map[string][]config.Presubmit{ 354 "org/repo": { 355 { 356 JobBase: config.JobBase{ 357 Name: "jab", 358 }, 359 RegexpChangeMatcher: config.RegexpChangeMatcher{ 360 SkipIfOnlyChanged: "CHANGED2", 361 }, 362 Reporter: config.Reporter{ 363 SkipReport: true, 364 Context: "pull-jab", 365 }, 366 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 367 RerunCommand: `/test jab`, 368 }, 369 }, 370 }, 371 ShouldBuild: true, 372 StartsExactly: "pull-jab", 373 }, 374 { 375 name: "Retest of run_if_changed job that failed. Changes require job", 376 Author: "trusted-member", 377 Body: "/retest", 378 State: "open", 379 IsPR: true, 380 Presubmits: map[string][]config.Presubmit{ 381 "org/repo": { 382 { 383 JobBase: config.JobBase{ 384 Name: "jib", 385 }, 386 RegexpChangeMatcher: config.RegexpChangeMatcher{ 387 RunIfChanged: "CHANGED", 388 }, 389 Reporter: config.Reporter{ 390 Context: "pull-jib", 391 }, 392 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 393 RerunCommand: `/test jib`, 394 }, 395 }, 396 }, 397 ShouldBuild: true, 398 StartsExactly: "pull-jib", 399 }, 400 { 401 name: "Retest of skip_if_only_changed job that failed. Changes require job", 402 Author: "trusted-member", 403 Body: "/retest", 404 State: "open", 405 IsPR: true, 406 Presubmits: map[string][]config.Presubmit{ 407 "org/repo": { 408 { 409 JobBase: config.JobBase{ 410 Name: "jib", 411 }, 412 RegexpChangeMatcher: config.RegexpChangeMatcher{ 413 SkipIfOnlyChanged: "CHANGED2", 414 }, 415 Reporter: config.Reporter{ 416 Context: "pull-jib", 417 }, 418 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 419 RerunCommand: `/test jib`, 420 }, 421 }, 422 }, 423 ShouldBuild: true, 424 StartsExactly: "pull-jib", 425 }, 426 { 427 name: "/test of run_if_changed job that has passed", 428 Author: "trusted-member", 429 Body: "/test jub", 430 State: "open", 431 IsPR: true, 432 Presubmits: map[string][]config.Presubmit{ 433 "org/repo": { 434 { 435 JobBase: config.JobBase{ 436 Name: "jub", 437 }, 438 RegexpChangeMatcher: config.RegexpChangeMatcher{ 439 RunIfChanged: "CHANGED", 440 }, 441 Reporter: config.Reporter{ 442 Context: "pull-jub", 443 }, 444 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 445 RerunCommand: `/test jub`, 446 }, 447 }, 448 }, 449 ShouldBuild: true, 450 StartsExactly: "pull-jub", 451 }, 452 { 453 name: "/test of skip_if_only_changed job that has passed", 454 Author: "trusted-member", 455 Body: "/test jub", 456 State: "open", 457 IsPR: true, 458 Presubmits: map[string][]config.Presubmit{ 459 "org/repo": { 460 { 461 JobBase: config.JobBase{ 462 Name: "jub", 463 }, 464 RegexpChangeMatcher: config.RegexpChangeMatcher{ 465 SkipIfOnlyChanged: "CHANGED2", 466 }, 467 Reporter: config.Reporter{ 468 Context: "pull-jub", 469 }, 470 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 471 RerunCommand: `/test jub`, 472 }, 473 }, 474 }, 475 ShouldBuild: true, 476 StartsExactly: "pull-jub", 477 }, 478 { 479 name: "Retest triggers failed job", 480 Author: "trusted-member", 481 Body: "/retest", 482 State: "open", 483 IsPR: true, 484 Presubmits: map[string][]config.Presubmit{ 485 "org/repo": { 486 { 487 JobBase: config.JobBase{ 488 Name: "jib", 489 }, 490 Reporter: config.Reporter{ 491 Context: "pull-jib", 492 }, 493 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 494 RerunCommand: `/test jib`, 495 }, 496 }, 497 }, 498 ShouldBuild: true, 499 }, 500 { 501 name: "Retest triggers failed job that is optional", 502 Author: "trusted-member", 503 Body: "/retest", 504 State: "open", 505 IsPR: true, 506 Presubmits: map[string][]config.Presubmit{ 507 "org/repo": { 508 { 509 JobBase: config.JobBase{ 510 Name: "jib", 511 }, 512 Reporter: config.Reporter{ 513 Context: "pull-jib", 514 }, 515 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 516 RerunCommand: `/test jib`, 517 Optional: true, 518 }, 519 }, 520 }, 521 ShouldBuild: true, 522 }, 523 { 524 name: "Retest-Required doesn't triggers failed job", 525 Author: "trusted-member", 526 Body: "/retest-required", 527 State: "open", 528 IsPR: true, 529 Presubmits: map[string][]config.Presubmit{ 530 "org/repo": { 531 { 532 JobBase: config.JobBase{ 533 Name: "jib", 534 }, 535 Reporter: config.Reporter{ 536 Context: "pull-jib", 537 }, 538 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 539 RerunCommand: `/test jib`, 540 }, 541 }, 542 }, 543 ShouldBuild: true, 544 }, 545 { 546 name: "Retest-Required doesn't trigger failed job that is optional", 547 Author: "trusted-member", 548 Body: "/retest-required", 549 State: "open", 550 IsPR: true, 551 Presubmits: map[string][]config.Presubmit{ 552 "org/repo": { 553 { 554 JobBase: config.JobBase{ 555 Name: "jib", 556 }, 557 Reporter: config.Reporter{ 558 Context: "pull-jib", 559 }, 560 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 561 RerunCommand: `/test jib`, 562 Optional: true, 563 }, 564 }, 565 }, 566 }, 567 { 568 name: "Retest of run_if_changed job that failed. Changes do not require the job", 569 Author: "trusted-member", 570 Body: "/retest", 571 State: "open", 572 IsPR: true, 573 Presubmits: map[string][]config.Presubmit{ 574 "org/repo": { 575 { 576 JobBase: config.JobBase{ 577 Name: "jib", 578 }, 579 RegexpChangeMatcher: config.RegexpChangeMatcher{ 580 RunIfChanged: "CHANGED2", 581 }, 582 Reporter: config.Reporter{ 583 Context: "pull-jib", 584 }, 585 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 586 RerunCommand: `/test jib`, 587 }, 588 }, 589 }, 590 ShouldBuild: true, 591 }, 592 { 593 name: "Retest of skip_if_only_changed job that failed. Changes do not require the job", 594 Author: "trusted-member", 595 Body: "/retest", 596 State: "open", 597 IsPR: true, 598 Presubmits: map[string][]config.Presubmit{ 599 "org/repo": { 600 { 601 JobBase: config.JobBase{ 602 Name: "jib", 603 }, 604 RegexpChangeMatcher: config.RegexpChangeMatcher{ 605 SkipIfOnlyChanged: "CHANGED", 606 }, 607 Reporter: config.Reporter{ 608 Context: "pull-jib", 609 }, 610 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 611 RerunCommand: `/test jib`, 612 }, 613 }, 614 }, 615 ShouldBuild: true, 616 }, 617 { 618 name: "Run if changed job triggered by /ok-to-test", 619 Author: "trusted-member", 620 Body: "/ok-to-test", 621 State: "open", 622 IsPR: true, 623 Presubmits: map[string][]config.Presubmit{ 624 "org/repo": { 625 { 626 JobBase: config.JobBase{ 627 Name: "jab", 628 }, 629 RegexpChangeMatcher: config.RegexpChangeMatcher{ 630 RunIfChanged: "CHANGED", 631 }, 632 Reporter: config.Reporter{ 633 Context: "pull-jab", 634 }, 635 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 636 RerunCommand: `/test jab`, 637 }, 638 }, 639 }, 640 ShouldBuild: true, 641 StartsExactly: "pull-jab", 642 IssueLabels: issueLabels(labels.NeedsOkToTest), 643 AddedLabels: issueLabels(labels.OkToTest), 644 RemovedLabels: issueLabels(labels.NeedsOkToTest), 645 }, 646 { 647 name: "Run if non-skipped job triggered by /ok-to-test", 648 Author: "trusted-member", 649 Body: "/ok-to-test", 650 State: "open", 651 IsPR: true, 652 Presubmits: map[string][]config.Presubmit{ 653 "org/repo": { 654 { 655 JobBase: config.JobBase{ 656 Name: "jab", 657 }, 658 RegexpChangeMatcher: config.RegexpChangeMatcher{ 659 SkipIfOnlyChanged: "CHANGED2", 660 }, 661 Reporter: config.Reporter{ 662 Context: "pull-jab", 663 }, 664 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 665 RerunCommand: `/test jab`, 666 }, 667 }, 668 }, 669 ShouldBuild: true, 670 StartsExactly: "pull-jab", 671 IssueLabels: issueLabels(labels.NeedsOkToTest), 672 AddedLabels: issueLabels(labels.OkToTest), 673 RemovedLabels: issueLabels(labels.NeedsOkToTest), 674 }, 675 { 676 name: "/test of branch-sharded job", 677 Author: "trusted-member", 678 Body: "/test jab", 679 State: "open", 680 IsPR: true, 681 Presubmits: map[string][]config.Presubmit{ 682 "org/repo": { 683 { 684 JobBase: config.JobBase{ 685 Name: "jab", 686 }, 687 Brancher: config.Brancher{Branches: []string{"master"}}, 688 Reporter: config.Reporter{ 689 Context: "pull-jab", 690 }, 691 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 692 RerunCommand: `/test jab`, 693 }, 694 { 695 JobBase: config.JobBase{ 696 Name: "jab", 697 }, 698 Brancher: config.Brancher{Branches: []string{"release"}}, 699 Reporter: config.Reporter{ 700 Context: "pull-jab", 701 }, 702 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 703 RerunCommand: `/test jab`, 704 }, 705 }, 706 }, 707 ShouldBuild: true, 708 StartsExactly: "pull-jab", 709 }, 710 { 711 name: "branch-sharded job. no shard matches base branch", 712 Author: "trusted-member", 713 Branch: "branch", 714 Body: "/test jab", 715 State: "open", 716 IsPR: true, 717 Presubmits: map[string][]config.Presubmit{ 718 "org/repo": { 719 { 720 JobBase: config.JobBase{ 721 Name: "jab", 722 }, 723 Brancher: config.Brancher{Branches: []string{"master"}}, 724 Reporter: config.Reporter{ 725 Context: "pull-jab", 726 }, 727 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 728 RerunCommand: `/test jab`, 729 }, 730 { 731 JobBase: config.JobBase{ 732 Name: "jab", 733 }, 734 Brancher: config.Brancher{Branches: []string{"release"}}, 735 Reporter: config.Reporter{ 736 Context: "pull-jab", 737 }, 738 Trigger: `(?m)^/test (?:.*? )?jab(?: .*?)?$`, 739 RerunCommand: `/test jab`, 740 }, 741 }, 742 }, 743 }, 744 { 745 name: "/retest of RunIfChanged job that doesn't need to run and hasn't run", 746 747 Author: "trusted-member", 748 Body: "/retest", 749 State: "open", 750 IsPR: true, 751 Presubmits: map[string][]config.Presubmit{ 752 "org/repo": { 753 { 754 JobBase: config.JobBase{ 755 Name: "jeb", 756 }, 757 RegexpChangeMatcher: config.RegexpChangeMatcher{ 758 RunIfChanged: "CHANGED2", 759 }, 760 Reporter: config.Reporter{ 761 Context: "pull-jeb", 762 }, 763 Trigger: `(?m)^/test (?:.*? )?jeb(?: .*?)?$`, 764 RerunCommand: `/test jeb`, 765 }, 766 }, 767 }, 768 }, 769 { 770 name: "/retest of SkipIfOnlyChanged job that doesn't need to run and hasn't run", 771 772 Author: "trusted-member", 773 Body: "/retest", 774 State: "open", 775 IsPR: true, 776 Presubmits: map[string][]config.Presubmit{ 777 "org/repo": { 778 { 779 JobBase: config.JobBase{ 780 Name: "jeb", 781 }, 782 RegexpChangeMatcher: config.RegexpChangeMatcher{ 783 SkipIfOnlyChanged: "CHANGED", 784 }, 785 Reporter: config.Reporter{ 786 Context: "pull-jeb", 787 }, 788 Trigger: `(?m)^/test (?:.*? )?jeb(?: .*?)?$`, 789 RerunCommand: `/test jeb`, 790 }, 791 }, 792 }, 793 }, 794 { 795 name: "explicit /test for RunIfChanged job that doesn't need to run", 796 797 Author: "trusted-member", 798 Body: "/test pull-jeb", 799 State: "open", 800 IsPR: true, 801 Presubmits: map[string][]config.Presubmit{ 802 "org/repo": { 803 { 804 JobBase: config.JobBase{ 805 Name: "jeb", 806 }, 807 RegexpChangeMatcher: config.RegexpChangeMatcher{ 808 RunIfChanged: "CHANGED2", 809 }, 810 Reporter: config.Reporter{ 811 Context: "pull-jeb", 812 }, 813 Trigger: `(?m)^/test (?:.*? )?jeb(?: .*?)?$`, 814 RerunCommand: `/test jeb`, 815 }, 816 }, 817 }, 818 ShouldBuild: false, 819 }, 820 { 821 name: "explicit /test for SkipIfOnlyChanged job that doesn't need to run", 822 823 Author: "trusted-member", 824 Body: "/test pull-jeb", 825 State: "open", 826 IsPR: true, 827 Presubmits: map[string][]config.Presubmit{ 828 "org/repo": { 829 { 830 JobBase: config.JobBase{ 831 Name: "jeb", 832 }, 833 RegexpChangeMatcher: config.RegexpChangeMatcher{ 834 SkipIfOnlyChanged: "CHANGED", 835 }, 836 Reporter: config.Reporter{ 837 Context: "pull-jeb", 838 }, 839 Trigger: `(?m)^/test (?:.*? )?jeb(?: .*?)?$`, 840 RerunCommand: `/test jeb`, 841 }, 842 }, 843 }, 844 ShouldBuild: false, 845 }, 846 { 847 name: "/test all of run_if_changed job that has passed and needs to run", 848 Author: "trusted-member", 849 Body: "/test all", 850 State: "open", 851 IsPR: true, 852 Presubmits: map[string][]config.Presubmit{ 853 "org/repo": { 854 { 855 JobBase: config.JobBase{ 856 Name: "jub", 857 }, 858 RegexpChangeMatcher: config.RegexpChangeMatcher{ 859 RunIfChanged: "CHANGED", 860 }, 861 Reporter: config.Reporter{ 862 Context: "pull-jub", 863 }, 864 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 865 RerunCommand: `/test jub`, 866 }, 867 }, 868 }, 869 ShouldBuild: true, 870 StartsExactly: "pull-jub", 871 }, 872 { 873 name: "/test all of skip_if_only_changed job that has passed and needs to run", 874 Author: "trusted-member", 875 Body: "/test all", 876 State: "open", 877 IsPR: true, 878 Presubmits: map[string][]config.Presubmit{ 879 "org/repo": { 880 { 881 JobBase: config.JobBase{ 882 Name: "jub", 883 }, 884 RegexpChangeMatcher: config.RegexpChangeMatcher{ 885 SkipIfOnlyChanged: "CHANGED2", 886 }, 887 Reporter: config.Reporter{ 888 Context: "pull-jub", 889 }, 890 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 891 RerunCommand: `/test jub`, 892 }, 893 }, 894 }, 895 ShouldBuild: true, 896 StartsExactly: "pull-jub", 897 }, 898 { 899 name: "/test all of run_if_changed job that has passed and doesn't need to run", 900 Author: "trusted-member", 901 Body: "/test all", 902 State: "open", 903 IsPR: true, 904 Presubmits: map[string][]config.Presubmit{ 905 "org/repo": { 906 { 907 JobBase: config.JobBase{ 908 Name: "jub", 909 }, 910 RegexpChangeMatcher: config.RegexpChangeMatcher{ 911 RunIfChanged: "CHANGED2", 912 }, 913 Reporter: config.Reporter{ 914 Context: "pull-jub", 915 }, 916 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 917 RerunCommand: `/test jub`, 918 }, 919 }, 920 }, 921 }, 922 { 923 name: "/test all of skip_if_only_changed job that has passed and doesn't need to run", 924 Author: "trusted-member", 925 Body: "/test all", 926 State: "open", 927 IsPR: true, 928 Presubmits: map[string][]config.Presubmit{ 929 "org/repo": { 930 { 931 JobBase: config.JobBase{ 932 Name: "jub", 933 }, 934 RegexpChangeMatcher: config.RegexpChangeMatcher{ 935 SkipIfOnlyChanged: "CHANGED", 936 }, 937 Reporter: config.Reporter{ 938 Context: "pull-jub", 939 }, 940 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 941 RerunCommand: `/test jub`, 942 }, 943 }, 944 }, 945 }, 946 { 947 name: "accept /test all from trusted user", 948 Author: "trusted-member", 949 PRAuthor: "trusted-member", 950 Body: "/test all", 951 State: "open", 952 IsPR: true, 953 ShouldBuild: true, 954 }, 955 { 956 name: `Non-trusted member after "/lgtm" and "/approve"`, 957 Author: "untrusted-member", 958 PRAuthor: "untrusted-member", 959 Body: "/retest", 960 State: "open", 961 IsPR: true, 962 ShouldBuild: false, 963 IssueLabels: issueLabels(labels.LGTM, labels.Approved), 964 }, 965 { 966 name: `help command "/test ?" lists available presubmits`, 967 Author: "trusted-member", 968 Body: "/test ?", 969 State: "open", 970 IsPR: true, 971 Presubmits: map[string][]config.Presubmit{ 972 "org/repo": { 973 { 974 JobBase: config.JobBase{ 975 Name: "job", 976 }, 977 AlwaysRun: true, 978 Reporter: config.Reporter{ 979 Context: "pull-job", 980 }, 981 Trigger: `(?m)^/test (?:.*? )?job(?: .*?)?$`, 982 RerunCommand: `/test job`, 983 }, 984 { 985 JobBase: config.JobBase{ 986 Name: "jib", 987 }, 988 AlwaysRun: true, 989 Reporter: config.Reporter{ 990 Context: "pull-jib", 991 }, 992 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 993 RerunCommand: `/test jib`, 994 }, 995 }, 996 }, 997 AddedComment: helpComment + "Use `/test all` to run all jobs.", 998 }, 999 { 1000 name: `help command "/test ?" uses unique RerunCommand field of presubmits`, 1001 Author: "trusted-member", 1002 Body: "/test ?", 1003 State: "open", 1004 IsPR: true, 1005 Presubmits: map[string][]config.Presubmit{ 1006 "org/repo": { 1007 { 1008 JobBase: config.JobBase{ 1009 Name: "jub", 1010 }, 1011 AlwaysRun: true, 1012 Reporter: config.Reporter{ 1013 Context: "pull-jub", 1014 }, 1015 Trigger: `/rerun_command`, 1016 RerunCommand: `/rerun_command`, 1017 }, 1018 { 1019 JobBase: config.JobBase{ 1020 Name: "jib", 1021 }, 1022 AlwaysRun: true, 1023 Reporter: config.Reporter{ 1024 Context: "pull-jib", 1025 }, 1026 Trigger: `/command_foo`, 1027 RerunCommand: `/command_foo`, 1028 }, 1029 { 1030 JobBase: config.JobBase{ 1031 Name: "jab", 1032 }, 1033 Reporter: config.Reporter{ 1034 Context: "pull-jab", 1035 }, 1036 Trigger: `/rerun_command`, 1037 RerunCommand: `/rerun_command`, 1038 }, 1039 }, 1040 }, 1041 AddedComment: "@trusted-member: The following commands are available to trigger required jobs:\n" + 1042 "* `/command_foo`\n* `/rerun_command`\n\n" + 1043 "Use `/test all` to run all jobs.", 1044 }, 1045 { 1046 name: "/test with no target results in a help message", 1047 Author: "trusted-member", 1048 Body: "/test", 1049 State: "open", 1050 IsPR: true, 1051 AddedComment: pjutil.TestWithoutTargetNote + helpComment + helpTestAllWithJobsComment, 1052 }, 1053 { 1054 name: "/test with no target but ? in the next line results in an invalid test command message", 1055 Author: "trusted-member", 1056 Body: "/test \r\n?", 1057 State: "open", 1058 IsPR: true, 1059 AddedComment: pjutil.TestWithoutTargetNote + helpComment + helpTestAllWithJobsComment, 1060 }, 1061 { 1062 name: "/retest with trailing words results in a help message", 1063 Author: "trusted-member", 1064 Body: "/retest FOO", 1065 State: "open", 1066 IsPR: true, 1067 AddedComment: pjutil.RetestWithTargetNote + helpComment + helpTestAllWithJobsComment, 1068 }, 1069 { 1070 name: "/retest without target but with lines following it, is valid", 1071 Author: "trusted-member", 1072 Body: "/retest \r\n/other-command", 1073 State: "open", 1074 IsPR: true, 1075 ShouldBuild: true, 1076 StartsExactly: "pull-jib", 1077 }, 1078 { 1079 name: "/test with unknown target results in a help message", 1080 Author: "trusted-member", 1081 Body: "/test FOO", 1082 State: "open", 1083 IsPR: true, 1084 AddedComment: pjutil.TargetNotFoundNote + helpComment + helpTestAllWithJobsComment, 1085 }, 1086 { 1087 name: "help comment should list only eligible jobs under '/test all'", 1088 Author: "trusted-member", 1089 Body: "/test ?", 1090 State: "open", 1091 IsPR: true, 1092 Presubmits: map[string][]config.Presubmit{ 1093 "org/repo": { 1094 { 1095 JobBase: config.JobBase{ 1096 Name: "job", 1097 }, 1098 AlwaysRun: true, 1099 Reporter: config.Reporter{ 1100 Context: "pull-job", 1101 }, 1102 Trigger: `(?m)^/test job$`, 1103 RerunCommand: `/test job`, 1104 }, 1105 { 1106 JobBase: config.JobBase{ 1107 Name: "jib", 1108 }, 1109 Reporter: config.Reporter{ 1110 Context: "pull-jib", 1111 }, 1112 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 1113 RerunCommand: `/test jib`, 1114 }, 1115 }, 1116 }, 1117 AddedComment: helpComment + helpTestAllWithJobsComment, 1118 }, 1119 { 1120 name: "when no jobs can be run with /test all, respond accordingly", 1121 Author: "trusted-member", 1122 Body: "/test all", 1123 State: "open", 1124 IsPR: true, 1125 Presubmits: map[string][]config.Presubmit{ 1126 "org/repo": { 1127 { 1128 JobBase: config.JobBase{ 1129 Name: "job", 1130 }, 1131 AlwaysRun: false, 1132 Reporter: config.Reporter{ 1133 Context: "pull-job", 1134 }, 1135 Trigger: `(?m)^/test job$`, 1136 RerunCommand: `/test job`, 1137 }, 1138 { 1139 JobBase: config.JobBase{ 1140 Name: "jib", 1141 }, 1142 AlwaysRun: false, 1143 Reporter: config.Reporter{ 1144 Context: "pull-jib", 1145 }, 1146 Trigger: `(?m)^/test jib$`, 1147 RerunCommand: `/test jib`, 1148 }, 1149 }, 1150 }, 1151 AddedComment: pjutil.ThereAreNoTestAllJobsNote + helpComment, 1152 }, 1153 { 1154 name: "available presubmits should not list those excluded by branch", 1155 Author: "trusted-member", 1156 Body: "/test ?", 1157 State: "open", 1158 IsPR: true, 1159 1160 Presubmits: map[string][]config.Presubmit{ 1161 "org/repo": { 1162 { 1163 JobBase: config.JobBase{ 1164 Name: "job-excluded-by-brancher", 1165 }, 1166 Brancher: config.Brancher{ 1167 SkipBranches: []string{"master"}, 1168 }, 1169 AlwaysRun: true, 1170 Reporter: config.Reporter{ 1171 Context: "pull-job-excluded-by-brancher", 1172 }, 1173 Trigger: `(?m)^/test job-excluded$`, 1174 RerunCommand: `/test job-excluded`, 1175 }, 1176 { 1177 JobBase: config.JobBase{ 1178 Name: "job", 1179 }, 1180 AlwaysRun: true, 1181 Reporter: config.Reporter{ 1182 Context: "pull-job", 1183 }, 1184 Trigger: `(?m)^/test job$`, 1185 RerunCommand: `/test job`, 1186 }, 1187 { 1188 JobBase: config.JobBase{ 1189 Name: "jib", 1190 }, 1191 Reporter: config.Reporter{ 1192 Context: "pull-jib", 1193 }, 1194 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 1195 RerunCommand: `/test jib`, 1196 }, 1197 }, 1198 }, 1199 AddedComment: helpComment + helpTestAllWithJobsComment, 1200 }, 1201 { 1202 name: `help command "/test ?" differs between optional and required jobs`, 1203 Author: "trusted-member", 1204 Body: "/test ?", 1205 State: "open", 1206 IsPR: true, 1207 Presubmits: map[string][]config.Presubmit{ 1208 "org/repo": { 1209 { 1210 JobBase: config.JobBase{ 1211 Name: "job", 1212 }, 1213 AlwaysRun: true, 1214 Reporter: config.Reporter{ 1215 Context: "pull-job", 1216 }, 1217 Trigger: `(?m)^/test (?:.*? )?job(?: .*?)?$`, 1218 RerunCommand: `/test job`, 1219 }, 1220 { 1221 JobBase: config.JobBase{ 1222 Name: "jib", 1223 }, 1224 AlwaysRun: true, 1225 Reporter: config.Reporter{ 1226 Context: "pull-jib", 1227 }, 1228 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 1229 RerunCommand: `/test jib`, 1230 }, 1231 { 1232 JobBase: config.JobBase{ 1233 Name: "jub", 1234 }, 1235 AlwaysRun: true, 1236 Optional: true, 1237 Reporter: config.Reporter{ 1238 Context: "pull-jub", 1239 }, 1240 Trigger: `(?m)^/test (?:.*? )?jub(?: .*?)?$`, 1241 RerunCommand: `/test jub`, 1242 }, 1243 }, 1244 }, 1245 AddedComment: helpComment + 1246 "The following commands are available to trigger optional jobs:\n* `/test jub`\n\n" + 1247 "Use `/test all` to run all jobs.", 1248 }, 1249 } 1250 for _, tc := range testcases { 1251 t.Run(tc.name, func(t *testing.T) { 1252 if tc.Branch == "" { 1253 tc.Branch = "master" 1254 } 1255 g := fakegithub.NewFakeClient() 1256 g.IssueComments = map[int][]github.IssueComment{} 1257 g.OrgMembers = map[string][]string{"org": {"trusted-member"}} 1258 g.PullRequests = map[int]*github.PullRequest{ 1259 0: { 1260 User: github.User{Login: tc.PRAuthor}, 1261 Number: 0, 1262 Head: github.PullRequestBranch{ 1263 SHA: "cafe", 1264 }, 1265 Base: github.PullRequestBranch{ 1266 Ref: tc.Branch, 1267 Repo: github.Repo{ 1268 Owner: github.User{Login: "org"}, 1269 Name: "repo", 1270 }, 1271 }, 1272 }, 1273 } 1274 g.IssueLabelsExisting = tc.IssueLabels 1275 g.PullRequestChanges = map[int][]github.PullRequestChange{0: {{Filename: "CHANGED"}}} 1276 g.CombinedStatuses = map[string]*github.CombinedStatus{ 1277 "cafe": { 1278 Statuses: []github.Status{ 1279 {State: github.StatusPending, Context: "pull-job"}, 1280 {State: github.StatusFailure, Context: "pull-jib"}, 1281 {State: github.StatusSuccess, Context: "pull-jub"}, 1282 }, 1283 }, 1284 } 1285 g.Collaborators = []string{"k8s-ci-robot"} 1286 fakeConfig := &config.Config{ProwConfig: config.ProwConfig{ProwJobNamespace: "prowjobs"}} 1287 fakeProwJobClient := fake.NewSimpleClientset() 1288 c := Client{ 1289 GitHubClient: g, 1290 ProwJobClient: fakeProwJobClient.ProwV1().ProwJobs(fakeConfig.ProwJobNamespace), 1291 Config: fakeConfig, 1292 Logger: logrus.WithField("plugin", PluginName), 1293 GitClient: nil, 1294 } 1295 presubmits := tc.Presubmits 1296 if presubmits == nil { 1297 presubmits = map[string][]config.Presubmit{ 1298 "org/repo": { 1299 { 1300 JobBase: config.JobBase{ 1301 Name: "job", 1302 }, 1303 AlwaysRun: true, 1304 Reporter: config.Reporter{ 1305 Context: "pull-job", 1306 }, 1307 Trigger: `(?m)^/test (?:.*? )?job(?: .*?)?$`, 1308 RerunCommand: `/test job`, 1309 Brancher: config.Brancher{Branches: []string{"master"}}, 1310 }, 1311 { 1312 JobBase: config.JobBase{ 1313 Name: "jib", 1314 }, 1315 AlwaysRun: false, 1316 Reporter: config.Reporter{ 1317 Context: "pull-jib", 1318 }, 1319 Trigger: `(?m)^/test (?:.*? )?jib(?: .*?)?$`, 1320 RerunCommand: `/test jib`, 1321 }, 1322 }, 1323 } 1324 } 1325 if err := c.Config.SetPresubmits(presubmits); err != nil { 1326 t.Fatalf("%s: failed to set presubmits: %v", tc.name, err) 1327 } 1328 1329 event := github.GenericCommentEvent{ 1330 Action: github.GenericCommentActionCreated, 1331 Repo: github.Repo{ 1332 Owner: github.User{Login: "org"}, 1333 Name: "repo", 1334 FullName: "org/repo", 1335 }, 1336 Body: tc.Body, 1337 User: github.User{Login: tc.Author}, 1338 IssueAuthor: github.User{Login: tc.PRAuthor}, 1339 IssueState: tc.State, 1340 IsPR: tc.IsPR, 1341 } 1342 1343 trigger := plugins.Trigger{ 1344 IgnoreOkToTest: tc.IgnoreOkToTest, 1345 } 1346 trigger.SetDefaults() 1347 1348 log.Printf("running case %s", tc.name) 1349 // In some cases handleGenericComment can be called twice for the same event. 1350 // For instance on Issue/PR creation and modification. 1351 // Let's call it twice to ensure idempotency. 1352 if err := handleGenericComment(c, trigger, event); err != nil { 1353 t.Fatalf("%s: didn't expect error: %s", tc.name, err) 1354 } 1355 validate(t, fakeProwJobClient.Fake.Actions(), g, tc) 1356 if err := handleGenericComment(c, trigger, event); err != nil { 1357 t.Fatalf("%s: didn't expect error: %s", tc.name, err) 1358 } 1359 validate(t, fakeProwJobClient.Fake.Actions(), g, tc) 1360 }) 1361 } 1362 } 1363 1364 func validate(t *testing.T, actions []clienttesting.Action, g *fakegithub.FakeClient, tc testcase) { 1365 startedContexts := sets.New[string]() 1366 for _, action := range actions { 1367 switch action := action.(type) { 1368 case clienttesting.CreateActionImpl: 1369 if prowJob, ok := action.Object.(*prowapi.ProwJob); ok { 1370 startedContexts.Insert(prowJob.Spec.Context) 1371 } 1372 } 1373 } 1374 if len(startedContexts) > 0 && !tc.ShouldBuild { 1375 t.Errorf("Built but should not have: %+v", tc) 1376 } else if len(startedContexts) == 0 && tc.ShouldBuild { 1377 t.Errorf("Not built but should have: %+v", tc) 1378 } 1379 if tc.StartsExactly != "" && (startedContexts.Len() != 1 || !startedContexts.Has(tc.StartsExactly)) { 1380 t.Errorf("didn't build expected context %v, instead built %v", tc.StartsExactly, startedContexts) 1381 } 1382 if !reflect.DeepEqual(g.IssueLabelsAdded, tc.AddedLabels) { 1383 t.Errorf("expected %q to be added, got %q", tc.AddedLabels, g.IssueLabelsAdded) 1384 } 1385 if !reflect.DeepEqual(g.IssueLabelsRemoved, tc.RemovedLabels) { 1386 t.Errorf("expected %q to be removed, got %q", tc.RemovedLabels, g.IssueLabelsRemoved) 1387 } 1388 if tc.AddedComment != "" { 1389 if len(g.IssueComments[0]) == 0 { 1390 t.Errorf("expected the comments to contain %s, got no comments", tc.AddedComment) 1391 } 1392 for _, c := range g.IssueComments[0] { 1393 if !strings.Contains(c.Body, tc.AddedComment) { 1394 t.Errorf("expected the comment to contain %s, got %s", tc.AddedComment, c.Body) 1395 } 1396 } 1397 } 1398 } 1399 1400 func TestRetestFilter(t *testing.T) { 1401 var testCases = []struct { 1402 name string 1403 failedContexts sets.Set[string] 1404 allContexts sets.Set[string] 1405 presubmits []config.Presubmit 1406 expected [][]bool 1407 }{ 1408 { 1409 name: "retest filter matches jobs that produce contexts which have failed", 1410 failedContexts: sets.New[string]("failed"), 1411 allContexts: sets.New[string]("failed", "succeeded"), 1412 presubmits: []config.Presubmit{ 1413 { 1414 JobBase: config.JobBase{ 1415 Name: "failed", 1416 }, 1417 Reporter: config.Reporter{ 1418 Context: "failed", 1419 }, 1420 }, 1421 { 1422 JobBase: config.JobBase{ 1423 Name: "succeeded", 1424 }, 1425 Reporter: config.Reporter{ 1426 Context: "succeeded", 1427 }, 1428 }, 1429 }, 1430 expected: [][]bool{{true, false, true}, {false, false, false}}, 1431 }, 1432 { 1433 name: "retest filter matches jobs that would run automatically and haven't yet ", 1434 failedContexts: sets.New[string](), 1435 allContexts: sets.New[string]("finished"), 1436 presubmits: []config.Presubmit{ 1437 { 1438 JobBase: config.JobBase{ 1439 Name: "finished", 1440 }, 1441 Reporter: config.Reporter{ 1442 Context: "finished", 1443 }, 1444 }, 1445 { 1446 JobBase: config.JobBase{ 1447 Name: "not-yet-run", 1448 }, 1449 AlwaysRun: true, 1450 Reporter: config.Reporter{ 1451 Context: "not-yet-run", 1452 }, 1453 }, 1454 }, 1455 expected: [][]bool{{false, false, false}, {true, false, false}}, 1456 }, 1457 } 1458 1459 for _, testCase := range testCases { 1460 t.Run(testCase.name, func(t *testing.T) { 1461 if len(testCase.presubmits) != len(testCase.expected) { 1462 t.Fatalf("%s: have %d presubmits but only %d expected filter outputs", testCase.name, len(testCase.presubmits), len(testCase.expected)) 1463 } 1464 if err := config.SetPresubmitRegexes(testCase.presubmits); err != nil { 1465 t.Fatalf("%s: could not set presubmit regexes: %v", testCase.name, err) 1466 } 1467 filter := pjutil.NewRetestFilter(testCase.failedContexts, testCase.allContexts) 1468 for i, presubmit := range testCase.presubmits { 1469 actualFiltered, actualForced, actualDefault := filter.ShouldRun(presubmit) 1470 expectedFiltered, expectedForced, expectedDefault := testCase.expected[i][0], testCase.expected[i][1], testCase.expected[i][2] 1471 if actualFiltered != expectedFiltered { 1472 t.Errorf("%s: filter did not evaluate correctly, expected %v but got %v for %v", testCase.name, expectedFiltered, actualFiltered, presubmit.Name) 1473 } 1474 if actualForced != expectedForced { 1475 t.Errorf("%s: filter did not determine forced correctly, expected %v but got %v for %v", testCase.name, expectedForced, actualForced, presubmit.Name) 1476 } 1477 if actualDefault != expectedDefault { 1478 t.Errorf("%s: filter did not determine default correctly, expected %v but got %v for %v", testCase.name, expectedDefault, actualDefault, presubmit.Name) 1479 } 1480 } 1481 }) 1482 } 1483 }