github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/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 "reflect" 22 "testing" 23 24 "github.com/sirupsen/logrus" 25 26 "k8s.io/apimachinery/pkg/util/sets" 27 "k8s.io/test-infra/prow/config" 28 "k8s.io/test-infra/prow/github" 29 "k8s.io/test-infra/prow/github/fakegithub" 30 "k8s.io/test-infra/prow/kube" 31 "k8s.io/test-infra/prow/labels" 32 "k8s.io/test-infra/prow/plugins" 33 ) 34 35 type fkc struct { 36 started []string 37 } 38 39 func (c *fkc) CreateProwJob(pj kube.ProwJob) (kube.ProwJob, error) { 40 if !sets.NewString(c.started...).Has(pj.Spec.Context) { 41 c.started = append(c.started, pj.Spec.Context) 42 } 43 return pj, nil 44 } 45 46 func issueLabels(labels ...string) []string { 47 var ls []string 48 for _, label := range labels { 49 ls = append(ls, fmt.Sprintf("org/repo#0:%s", label)) 50 } 51 return ls 52 } 53 54 type testcase struct { 55 name string 56 57 Author string 58 PRAuthor string 59 Body string 60 State string 61 IsPR bool 62 Branch string 63 ShouldBuild bool 64 ShouldReport bool 65 AddedLabels []string 66 RemovedLabels []string 67 StartsExactly string 68 Presubmits map[string][]config.Presubmit 69 IssueLabels []string 70 IgnoreOkToTest bool 71 } 72 73 func TestHandleGenericComment(t *testing.T) { 74 var testcases = []testcase{ 75 { 76 name: "Not a PR.", 77 78 Author: "t", 79 Body: "/ok-to-test", 80 State: "open", 81 IsPR: false, 82 ShouldBuild: false, 83 }, 84 { 85 name: "Closed PR.", 86 87 Author: "t", 88 Body: "/ok-to-test", 89 State: "closed", 90 IsPR: true, 91 ShouldBuild: false, 92 }, 93 { 94 name: "Comment by a bot.", 95 96 Author: "k8s-bot", 97 Body: "/ok-to-test", 98 State: "open", 99 IsPR: true, 100 ShouldBuild: false, 101 }, 102 { 103 name: "Non-trusted member's ok to test.", 104 105 Author: "u", 106 Body: "/ok-to-test", 107 State: "open", 108 IsPR: true, 109 ShouldBuild: false, 110 }, 111 { 112 name: "accept /test from non-trusted member if PR author is trusted", 113 Author: "u", 114 PRAuthor: "t", 115 Body: "/test all", 116 State: "open", 117 IsPR: true, 118 ShouldBuild: true, 119 }, 120 { 121 name: "reject /test from non-trusted member when PR author is untrusted", 122 Author: "u", 123 PRAuthor: "u", 124 Body: "/test all", 125 State: "open", 126 IsPR: true, 127 ShouldBuild: false, 128 }, 129 { 130 name: `Non-trusted member after "/ok-to-test".`, 131 132 Author: "u", 133 Body: "/test all", 134 State: "open", 135 IsPR: true, 136 ShouldBuild: true, 137 IssueLabels: issueLabels(labels.OkToTest), 138 }, 139 { 140 name: `Non-trusted member after "/ok-to-test", needs-ok-to-test label wasn't deleted.`, 141 142 Author: "u", 143 Body: "/test all", 144 State: "open", 145 IsPR: true, 146 ShouldBuild: true, 147 IssueLabels: issueLabels(labels.NeedsOkToTest, labels.OkToTest), 148 RemovedLabels: issueLabels(labels.NeedsOkToTest), 149 }, 150 { 151 name: "Trusted member's ok to test, IgnoreOkToTest", 152 153 Author: "t", 154 Body: "/ok-to-test", 155 State: "open", 156 IsPR: true, 157 ShouldBuild: false, 158 IgnoreOkToTest: true, 159 }, 160 { 161 name: "Trusted member's ok to test", 162 163 Author: "t", 164 Body: "looks great, thanks!\n/ok-to-test", 165 State: "open", 166 IsPR: true, 167 ShouldBuild: true, 168 AddedLabels: issueLabels(labels.OkToTest), 169 }, 170 { 171 name: "Trusted member's ok to test, trailing space.", 172 173 Author: "t", 174 Body: "looks great, thanks!\n/ok-to-test \r", 175 State: "open", 176 IsPR: true, 177 ShouldBuild: true, 178 AddedLabels: issueLabels(labels.OkToTest), 179 }, 180 { 181 name: "Trusted member's not ok to test.", 182 183 Author: "t", 184 Body: "not /ok-to-test", 185 State: "open", 186 IsPR: true, 187 ShouldBuild: false, 188 }, 189 { 190 name: "Trusted member's test this.", 191 192 Author: "t", 193 Body: "/test all", 194 State: "open", 195 IsPR: true, 196 ShouldBuild: true, 197 }, 198 { 199 name: "Wrong branch.", 200 201 Author: "t", 202 Body: "/test all", 203 State: "open", 204 IsPR: true, 205 Branch: "other", 206 ShouldBuild: false, 207 ShouldReport: true, 208 }, 209 { 210 name: "Retest with one running and one failed", 211 212 Author: "t", 213 Body: "/retest", 214 State: "open", 215 IsPR: true, 216 ShouldBuild: true, 217 StartsExactly: "pull-jib", 218 }, 219 { 220 name: "Retest with one running and one failed, trailing space.", 221 222 Author: "t", 223 Body: "/retest \r", 224 State: "open", 225 IsPR: true, 226 ShouldBuild: true, 227 StartsExactly: "pull-jib", 228 }, 229 { 230 name: "needs-ok-to-test label is removed when no presubmit runs by default", 231 232 Author: "t", 233 Body: "/ok-to-test", 234 State: "open", 235 IsPR: true, 236 ShouldBuild: false, 237 Presubmits: map[string][]config.Presubmit{ 238 "org/repo": { 239 { 240 JobBase: config.JobBase{ 241 Name: "job", 242 }, 243 AlwaysRun: false, 244 Context: "pull-job", 245 Trigger: `/test all`, 246 RerunCommand: `/test all`, 247 }, 248 { 249 JobBase: config.JobBase{ 250 Name: "jib", 251 }, 252 AlwaysRun: false, 253 Context: "pull-jib", 254 Trigger: `/test jib`, 255 RerunCommand: `/test jib`, 256 }, 257 }, 258 }, 259 IssueLabels: issueLabels(labels.NeedsOkToTest), 260 AddedLabels: issueLabels(labels.OkToTest), 261 RemovedLabels: issueLabels(labels.NeedsOkToTest), 262 }, 263 { 264 name: "Wrong branch w/ SkipReport", 265 Author: "t", 266 Body: "/test all", 267 Branch: "other", 268 State: "open", 269 IsPR: true, 270 Presubmits: map[string][]config.Presubmit{ 271 "org/repo": { 272 { 273 JobBase: config.JobBase{ 274 Name: "job", 275 }, 276 AlwaysRun: true, 277 SkipReport: true, 278 Context: "pull-job", 279 Trigger: `/test all`, 280 RerunCommand: `/test all`, 281 Brancher: config.Brancher{Branches: []string{"master"}}, 282 }, 283 }, 284 }, 285 }, 286 { 287 name: "Retest of run_if_changed job that hasn't run. Changes require job", 288 Author: "t", 289 Body: "/retest", 290 State: "open", 291 IsPR: true, 292 Presubmits: map[string][]config.Presubmit{ 293 "org/repo": { 294 { 295 JobBase: config.JobBase{ 296 Name: "jab", 297 }, 298 RegexpChangeMatcher: config.RegexpChangeMatcher{ 299 RunIfChanged: "CHANGED", 300 }, 301 SkipReport: true, 302 Context: "pull-jab", 303 Trigger: `/test all`, 304 RerunCommand: `/test all`, 305 }, 306 }, 307 }, 308 ShouldBuild: true, 309 StartsExactly: "pull-jab", 310 }, 311 { 312 name: "Retest of run_if_changed job that failed. Changes require job", 313 Author: "t", 314 Body: "/retest", 315 State: "open", 316 IsPR: true, 317 Presubmits: map[string][]config.Presubmit{ 318 "org/repo": { 319 { 320 JobBase: config.JobBase{ 321 Name: "jib", 322 }, 323 RegexpChangeMatcher: config.RegexpChangeMatcher{ 324 RunIfChanged: "CHANGED", 325 }, 326 Context: "pull-jib", 327 Trigger: `/test all`, 328 RerunCommand: `/test all`, 329 }, 330 }, 331 }, 332 ShouldBuild: true, 333 StartsExactly: "pull-jib", 334 }, 335 { 336 name: "/test of run_if_changed job that has passed", 337 Author: "t", 338 Body: "/test jub", 339 State: "open", 340 IsPR: true, 341 Presubmits: map[string][]config.Presubmit{ 342 "org/repo": { 343 { 344 JobBase: config.JobBase{ 345 Name: "jub", 346 }, 347 RegexpChangeMatcher: config.RegexpChangeMatcher{ 348 RunIfChanged: "CHANGED", 349 }, 350 Context: "pull-jub", 351 Trigger: `/test jub`, 352 RerunCommand: `/test jub`, 353 }, 354 }, 355 }, 356 ShouldBuild: true, 357 StartsExactly: "pull-jub", 358 }, 359 { 360 name: "Retest of run_if_changed job that failed. Changes do not require the job", 361 Author: "t", 362 Body: "/retest", 363 State: "open", 364 IsPR: true, 365 Presubmits: map[string][]config.Presubmit{ 366 "org/repo": { 367 { 368 JobBase: config.JobBase{ 369 Name: "jib", 370 }, 371 RegexpChangeMatcher: config.RegexpChangeMatcher{ 372 RunIfChanged: "CHANGED2", 373 }, 374 Context: "pull-jib", 375 Trigger: `/test all`, 376 RerunCommand: `/test all`, 377 }, 378 }, 379 }, 380 ShouldBuild: true, 381 }, 382 { 383 name: "Run if changed job triggered by /ok-to-test", 384 Author: "t", 385 Body: "/ok-to-test", 386 State: "open", 387 IsPR: true, 388 Presubmits: map[string][]config.Presubmit{ 389 "org/repo": { 390 { 391 JobBase: config.JobBase{ 392 Name: "jab", 393 }, 394 RegexpChangeMatcher: config.RegexpChangeMatcher{ 395 RunIfChanged: "CHANGED", 396 }, 397 Context: "pull-jab", 398 Trigger: `/test all`, 399 RerunCommand: `/test all`, 400 }, 401 }, 402 }, 403 ShouldBuild: true, 404 StartsExactly: "pull-jab", 405 IssueLabels: issueLabels(labels.NeedsOkToTest), 406 AddedLabels: issueLabels(labels.OkToTest), 407 RemovedLabels: issueLabels(labels.NeedsOkToTest), 408 }, 409 { 410 name: "/test of branch-sharded job", 411 Author: "t", 412 Body: "/test jab", 413 State: "open", 414 IsPR: true, 415 Presubmits: map[string][]config.Presubmit{ 416 "org/repo": { 417 { 418 JobBase: config.JobBase{ 419 Name: "jab", 420 }, 421 Brancher: config.Brancher{Branches: []string{"master"}}, 422 Context: "pull-jab", 423 Trigger: `/test jab`, 424 RerunCommand: `/test jab`, 425 }, 426 { 427 JobBase: config.JobBase{ 428 Name: "jab", 429 }, 430 Brancher: config.Brancher{Branches: []string{"release"}}, 431 Context: "pull-jab", 432 Trigger: `/test jab`, 433 RerunCommand: `/test jab`, 434 }, 435 }, 436 }, 437 ShouldBuild: true, 438 StartsExactly: "pull-jab", 439 }, 440 { 441 name: "branch-sharded job. no shard matches base branch", 442 Author: "t", 443 Branch: "branch", 444 Body: "/test jab", 445 State: "open", 446 IsPR: true, 447 Presubmits: map[string][]config.Presubmit{ 448 "org/repo": { 449 { 450 JobBase: config.JobBase{ 451 Name: "jab", 452 }, 453 Brancher: config.Brancher{Branches: []string{"master"}}, 454 Context: "pull-jab", 455 Trigger: `/test jab`, 456 RerunCommand: `/test jab`, 457 }, 458 { 459 JobBase: config.JobBase{ 460 Name: "jab", 461 }, 462 Brancher: config.Brancher{Branches: []string{"release"}}, 463 Context: "pull-jab", 464 Trigger: `/test jab`, 465 RerunCommand: `/test jab`, 466 }, 467 }, 468 }, 469 ShouldReport: true, 470 }, 471 { 472 name: "/retest of RunIfChanged job that doesn't need to run and hasn't run", 473 474 Author: "t", 475 Body: "/retest", 476 State: "open", 477 IsPR: true, 478 Presubmits: map[string][]config.Presubmit{ 479 "org/repo": { 480 { 481 JobBase: config.JobBase{ 482 Name: "jeb", 483 }, 484 RegexpChangeMatcher: config.RegexpChangeMatcher{ 485 RunIfChanged: "CHANGED2", 486 }, 487 Context: "pull-jeb", 488 Trigger: `/test all`, 489 RerunCommand: `/test all`, 490 }, 491 }, 492 }, 493 ShouldReport: true, 494 }, 495 { 496 name: "explicit /test for RunIfChanged job that doesn't need to run", 497 498 Author: "t", 499 Body: "/test pull-jeb", 500 State: "open", 501 IsPR: true, 502 Presubmits: map[string][]config.Presubmit{ 503 "org/repo": { 504 { 505 JobBase: config.JobBase{ 506 Name: "jeb", 507 }, 508 RegexpChangeMatcher: config.RegexpChangeMatcher{ 509 RunIfChanged: "CHANGED2", 510 }, 511 Context: "pull-jib", 512 Trigger: `/test (all|pull-jeb)`, 513 RerunCommand: `/test pull-jeb`, 514 }, 515 }, 516 }, 517 ShouldBuild: true, 518 }, 519 { 520 name: "/test all of run_if_changed job that has passed and needs to run", 521 Author: "t", 522 Body: "/test all", 523 State: "open", 524 IsPR: true, 525 Presubmits: map[string][]config.Presubmit{ 526 "org/repo": { 527 { 528 JobBase: config.JobBase{ 529 Name: "jub", 530 }, 531 RegexpChangeMatcher: config.RegexpChangeMatcher{ 532 RunIfChanged: "CHANGED", 533 }, 534 Context: "pull-jub", 535 Trigger: `/test jub`, 536 RerunCommand: `/test jub`, 537 }, 538 }, 539 }, 540 ShouldBuild: true, 541 StartsExactly: "pull-jub", 542 }, 543 { 544 name: "/test all of run_if_changed job that has passed and doesn't need to run", 545 Author: "t", 546 Body: "/test all", 547 State: "open", 548 IsPR: true, 549 Presubmits: map[string][]config.Presubmit{ 550 "org/repo": { 551 { 552 JobBase: config.JobBase{ 553 Name: "jub", 554 }, 555 RegexpChangeMatcher: config.RegexpChangeMatcher{ 556 RunIfChanged: "CHANGED2", 557 }, 558 Context: "pull-jub", 559 Trigger: `/test jub`, 560 RerunCommand: `/test jub`, 561 }, 562 }, 563 }, 564 ShouldReport: true, 565 }, 566 { 567 name: "accept /test all from trusted user", 568 Author: "t", 569 PRAuthor: "t", 570 Body: "/test all", 571 State: "open", 572 IsPR: true, 573 ShouldBuild: true, 574 }, 575 { 576 name: `Non-trusted member after "/lgtm" and "/approve"`, 577 Author: "u", 578 PRAuthor: "u", 579 Body: "/retest", 580 State: "open", 581 IsPR: true, 582 ShouldBuild: false, 583 IssueLabels: issueLabels(labels.LGTM, labels.Approved), 584 }, 585 } 586 for _, tc := range testcases { 587 t.Logf("running scenario %q", tc.name) 588 if tc.Branch == "" { 589 tc.Branch = "master" 590 } 591 g := &fakegithub.FakeClient{ 592 CreatedStatuses: map[string][]github.Status{}, 593 IssueComments: map[int][]github.IssueComment{}, 594 OrgMembers: map[string][]string{"org": {"t"}}, 595 PullRequests: map[int]*github.PullRequest{ 596 0: { 597 User: github.User{Login: tc.PRAuthor}, 598 Number: 0, 599 Head: github.PullRequestBranch{ 600 SHA: "cafe", 601 }, 602 Base: github.PullRequestBranch{ 603 Ref: tc.Branch, 604 Repo: github.Repo{ 605 Owner: github.User{Login: "org"}, 606 Name: "repo", 607 }, 608 }, 609 }, 610 }, 611 IssueLabelsExisting: tc.IssueLabels, 612 PullRequestChanges: map[int][]github.PullRequestChange{0: {{Filename: "CHANGED"}}}, 613 CombinedStatuses: map[string]*github.CombinedStatus{ 614 "cafe": { 615 Statuses: []github.Status{ 616 {State: "pending", Context: "pull-job"}, 617 {State: "failure", Context: "pull-jib"}, 618 {State: "success", Context: "pull-jub"}, 619 }, 620 }, 621 }, 622 } 623 kc := &fkc{} 624 c := Client{ 625 GitHubClient: g, 626 KubeClient: kc, 627 Config: &config.Config{}, 628 Logger: logrus.WithField("plugin", pluginName), 629 } 630 presubmits := tc.Presubmits 631 if presubmits == nil { 632 presubmits = map[string][]config.Presubmit{ 633 "org/repo": { 634 { 635 JobBase: config.JobBase{ 636 Name: "job", 637 }, 638 AlwaysRun: true, 639 Context: "pull-job", 640 Trigger: `/test all`, 641 RerunCommand: `/test all`, 642 Brancher: config.Brancher{Branches: []string{"master"}}, 643 }, 644 { 645 JobBase: config.JobBase{ 646 Name: "jib", 647 }, 648 AlwaysRun: false, 649 Context: "pull-jib", 650 Trigger: `/test jib`, 651 RerunCommand: `/test jib`, 652 }, 653 }, 654 } 655 } 656 if err := c.Config.SetPresubmits(presubmits); err != nil { 657 t.Fatalf("failed to set presubmits: %v", err) 658 } 659 660 event := github.GenericCommentEvent{ 661 Action: github.GenericCommentActionCreated, 662 Repo: github.Repo{ 663 Owner: github.User{Login: "org"}, 664 Name: "repo", 665 FullName: "org/repo", 666 }, 667 Body: tc.Body, 668 User: github.User{Login: tc.Author}, 669 IssueAuthor: github.User{Login: tc.PRAuthor}, 670 IssueState: tc.State, 671 IsPR: tc.IsPR, 672 } 673 674 trigger := plugins.Trigger{ 675 IgnoreOkToTest: tc.IgnoreOkToTest, 676 } 677 678 // In some cases handleGenericComment can be called twice for the same event. 679 // For instance on Issue/PR creation and modification. 680 // Let's call it twice to ensure idempotency. 681 if err := handleGenericComment(c, &trigger, event); err != nil { 682 t.Fatalf("Didn't expect error: %s", err) 683 } 684 validate(kc, g, tc, t) 685 if err := handleGenericComment(c, &trigger, event); err != nil { 686 t.Fatalf("Didn't expect error: %s", err) 687 } 688 validate(kc, g, tc, t) 689 } 690 } 691 692 func validate(kc *fkc, g *fakegithub.FakeClient, tc testcase, t *testing.T) { 693 if len(kc.started) > 0 && !tc.ShouldBuild { 694 t.Errorf("Built but should not have: %+v", tc) 695 } else if len(kc.started) == 0 && tc.ShouldBuild { 696 t.Errorf("Not built but should have: %+v", tc) 697 } 698 if tc.StartsExactly != "" && (len(kc.started) != 1 || kc.started[0] != tc.StartsExactly) { 699 t.Errorf("Didn't build expected context %v, instead built %v", tc.StartsExactly, kc.started) 700 } 701 if tc.ShouldReport && len(g.CreatedStatuses) == 0 { 702 t.Error("Expected report to github") 703 } else if !tc.ShouldReport && len(g.CreatedStatuses) > 0 { 704 t.Errorf("Expected no reports to github, but got %d", len(g.CreatedStatuses)) 705 } 706 if !reflect.DeepEqual(g.IssueLabelsAdded, tc.AddedLabels) { 707 t.Errorf("expected %q to be added, got %q", tc.AddedLabels, g.IssueLabelsAdded) 708 } 709 if !reflect.DeepEqual(g.IssueLabelsRemoved, tc.RemovedLabels) { 710 t.Errorf("expected %q to be removed, got %q", tc.RemovedLabels, g.IssueLabelsRemoved) 711 } 712 }