github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/pjutil/filter_test.go (about) 1 /* 2 Copyright 2019 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 pjutil 18 19 import ( 20 "errors" 21 "fmt" 22 "reflect" 23 "testing" 24 25 "k8s.io/apimachinery/pkg/util/sets" 26 "sigs.k8s.io/prow/pkg/github" 27 28 "github.com/google/go-cmp/cmp" 29 "github.com/sirupsen/logrus" 30 31 "k8s.io/apimachinery/pkg/util/diff" 32 "sigs.k8s.io/prow/pkg/config" 33 ) 34 35 func TestTestAllFilter(t *testing.T) { 36 var testCases = []struct { 37 name string 38 presubmits []config.Presubmit 39 expected [][]bool 40 }{ 41 { 42 name: "test all filter matches jobs which do not require human triggering", 43 presubmits: []config.Presubmit{ 44 { 45 JobBase: config.JobBase{ 46 Name: "always-runs", 47 }, 48 AlwaysRun: true, 49 }, 50 { 51 JobBase: config.JobBase{ 52 Name: "runs-if-changed", 53 }, 54 AlwaysRun: false, 55 RegexpChangeMatcher: config.RegexpChangeMatcher{ 56 RunIfChanged: "sometimes", 57 }, 58 }, 59 { 60 JobBase: config.JobBase{ 61 Name: "runs-if-changed", 62 }, 63 AlwaysRun: false, 64 RegexpChangeMatcher: config.RegexpChangeMatcher{ 65 SkipIfOnlyChanged: "sometimes", 66 }, 67 }, 68 { 69 JobBase: config.JobBase{ 70 Name: "runs-if-triggered", 71 }, 72 Reporter: config.Reporter{ 73 Context: "runs-if-triggered", 74 }, 75 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 76 RerunCommand: "/test trigger", 77 }, 78 { 79 JobBase: config.JobBase{ 80 Name: "literal-test-all-trigger", 81 }, 82 Reporter: config.Reporter{ 83 Context: "runs-if-triggered", 84 }, 85 Trigger: `(?m)^/test (?:.*? )?all(?: .*?)?$`, 86 RerunCommand: "/test all", 87 }, 88 }, 89 expected: [][]bool{{true, false, false}, {true, false, false}, {true, false, false}, {false, false, false}, {false, false, false}}, 90 }, 91 } 92 93 for _, testCase := range testCases { 94 t.Run(testCase.name, func(t *testing.T) { 95 if len(testCase.presubmits) != len(testCase.expected) { 96 t.Fatalf("%s: have %d presubmits but only %d expected filter outputs", testCase.name, len(testCase.presubmits), len(testCase.expected)) 97 } 98 if err := config.SetPresubmitRegexes(testCase.presubmits); err != nil { 99 t.Fatalf("%s: could not set presubmit regexes: %v", testCase.name, err) 100 } 101 filter := NewTestAllFilter() 102 for i, presubmit := range testCase.presubmits { 103 actualFiltered, actualForced, actualDefault := filter.ShouldRun(presubmit) 104 expectedFiltered, expectedForced, expectedDefault := testCase.expected[i][0], testCase.expected[i][1], testCase.expected[i][2] 105 if actualFiltered != expectedFiltered { 106 t.Errorf("%s: filter did not evaluate correctly, expected %v but got %v for %v", testCase.name, expectedFiltered, actualFiltered, presubmit.Name) 107 } 108 if actualForced != expectedForced { 109 t.Errorf("%s: filter did not determine forced correctly, expected %v but got %v for %v", testCase.name, expectedForced, actualForced, presubmit.Name) 110 } 111 if actualDefault != expectedDefault { 112 t.Errorf("%s: filter did not determine default correctly, expected %v but got %v for %v", testCase.name, expectedDefault, actualDefault, presubmit.Name) 113 } 114 } 115 }) 116 } 117 } 118 119 func TestCommandFilter(t *testing.T) { 120 var testCases = []struct { 121 name string 122 body string 123 half int 124 presubmits []config.Presubmit 125 expected [][]bool 126 expectedName string 127 }{ 128 { 129 name: "loose-trigger", 130 body: "something/test job-abcdefg", 131 presubmits: []config.Presubmit{ 132 { 133 JobBase: config.JobBase{ 134 Name: "trigger", 135 }, 136 Trigger: `/test job-a`, 137 RerunCommand: "/test job-a", // rerun command has to be set when trigger is set. 138 }, 139 }, 140 // Loose trigger without `^` and `$` means it would match the text 141 // from anywhere in the message. For example a comment like: 142 // `something/test job-abcdefg` would match this job. 143 expected: [][]bool{{true, true, true}}, 144 expectedName: "command-filter: something/test job-abcdefg", 145 }, 146 { 147 name: "command filter matches jobs whose triggers match the body", 148 body: "/test trigger", 149 presubmits: []config.Presubmit{ 150 { 151 JobBase: config.JobBase{ 152 Name: "trigger", 153 }, 154 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 155 RerunCommand: "/test trigger", 156 }, 157 { 158 JobBase: config.JobBase{ 159 Name: "other-trigger", 160 }, 161 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 162 RerunCommand: "/test other-trigger", 163 }, 164 }, 165 expected: [][]bool{{true, true, true}, {false, false, true}}, 166 expectedName: "command-filter: /test trigger", 167 }, 168 { 169 name: "truncate-name", 170 body: `/test trigger 171 fill in random content so that it exceeds the limit of half*2 chars`, 172 half: 10, 173 presubmits: []config.Presubmit{ 174 { 175 JobBase: config.JobBase{ 176 Name: "trigger", 177 }, 178 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 179 RerunCommand: "/test trigger", 180 }, 181 }, 182 expected: [][]bool{{true, true, true}}, 183 expectedName: `command-filter: /test trig 184 ... 185 lf*2 chars`, 186 }, 187 } 188 189 for _, testCase := range testCases { 190 t.Run(testCase.name, func(t *testing.T) { 191 if len(testCase.presubmits) != len(testCase.expected) { 192 t.Fatalf("%s: have %d presubmits but only %d expected filter outputs", testCase.name, len(testCase.presubmits), len(testCase.expected)) 193 } 194 if err := config.SetPresubmitRegexes(testCase.presubmits); err != nil { 195 t.Fatalf("%s: could not set presubmit regexes: %v", testCase.name, err) 196 } 197 filter := NewCommandFilter(testCase.body) 198 if testCase.half > 0 { 199 filter.half = testCase.half 200 } 201 for i, presubmit := range testCase.presubmits { 202 actualFiltered, actualForced, actualDefault := filter.ShouldRun(presubmit) 203 expectedFiltered, expectedForced, expectedDefault := testCase.expected[i][0], testCase.expected[i][1], testCase.expected[i][2] 204 if actualFiltered != expectedFiltered { 205 t.Errorf("%s: filter did not evaluate correctly, expected %v but got %v for %v", testCase.name, expectedFiltered, actualFiltered, presubmit.Name) 206 } 207 if actualForced != expectedForced { 208 t.Errorf("%s: filter did not determine forced correctly, expected %v but got %v for %v", testCase.name, expectedForced, actualForced, presubmit.Name) 209 } 210 if actualDefault != expectedDefault { 211 t.Errorf("%s: filter did not determine default correctly, expected %v but got %v for %v", testCase.name, expectedDefault, actualDefault, presubmit.Name) 212 } 213 } 214 if diff := cmp.Diff(testCase.expectedName, filter.Name()); diff != "" { 215 t.Errorf("Name mismatch. Want(-), got(+):\n%s", diff) 216 } 217 }) 218 } 219 } 220 221 func fakeChangedFilesProvider(shouldError bool) config.ChangedFilesProvider { 222 return func() ([]string, error) { 223 if shouldError { 224 return nil, errors.New("error getting changes") 225 } 226 return nil, nil 227 } 228 } 229 230 func TestFilterPresubmits(t *testing.T) { 231 var testCases = []struct { 232 name string 233 filter Filter 234 presubmits []config.Presubmit 235 changesError bool 236 expectedToTrigger []config.Presubmit 237 expectErr bool 238 }{ 239 { 240 name: "nothing matches, nothing to run or skip", 241 filter: &ArbitraryFilter{ 242 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 243 return false, false, false 244 }, 245 }, 246 presubmits: []config.Presubmit{{ 247 JobBase: config.JobBase{Name: "ignored"}, 248 Reporter: config.Reporter{Context: "first"}, 249 }, { 250 JobBase: config.JobBase{Name: "ignored"}, 251 Reporter: config.Reporter{Context: "second"}, 252 }}, 253 changesError: false, 254 expectedToTrigger: nil, 255 expectErr: false, 256 }, 257 { 258 name: "everything matches and is forced to run, nothing to skip", 259 filter: &ArbitraryFilter{ 260 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 261 return true, true, true 262 }, 263 }, 264 presubmits: []config.Presubmit{{ 265 JobBase: config.JobBase{Name: "should-trigger"}, 266 Reporter: config.Reporter{Context: "first"}, 267 }, { 268 JobBase: config.JobBase{Name: "should-trigger"}, 269 Reporter: config.Reporter{Context: "second"}, 270 }}, 271 changesError: false, 272 expectedToTrigger: []config.Presubmit{{ 273 JobBase: config.JobBase{Name: "should-trigger"}, 274 Reporter: config.Reporter{Context: "first"}, 275 }, { 276 JobBase: config.JobBase{Name: "should-trigger"}, 277 Reporter: config.Reporter{Context: "second"}, 278 }}, 279 expectErr: false, 280 }, 281 { 282 name: "error detecting if something should run, nothing to run or skip", 283 filter: &ArbitraryFilter{ 284 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 285 return true, false, false 286 }, 287 }, 288 presubmits: []config.Presubmit{{ 289 JobBase: config.JobBase{Name: "errors"}, 290 Reporter: config.Reporter{Context: "first"}, 291 RegexpChangeMatcher: config.RegexpChangeMatcher{RunIfChanged: "oopsie"}, 292 }, { 293 JobBase: config.JobBase{Name: "ignored"}, 294 Reporter: config.Reporter{Context: "second"}, 295 }}, 296 changesError: true, 297 expectedToTrigger: nil, 298 expectErr: true, 299 }, 300 { 301 name: "error detecting if something should run, nothing to skip", 302 filter: &ArbitraryFilter{ 303 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 304 return true, false, false 305 }, 306 }, 307 presubmits: []config.Presubmit{{ 308 JobBase: config.JobBase{Name: "errors"}, 309 Reporter: config.Reporter{Context: "first"}, 310 RegexpChangeMatcher: config.RegexpChangeMatcher{SkipIfOnlyChanged: "oopsie"}, 311 }, { 312 JobBase: config.JobBase{Name: "ignored"}, 313 Reporter: config.Reporter{Context: "second"}, 314 }}, 315 changesError: true, 316 expectedToTrigger: nil, 317 expectErr: true, 318 }, 319 { 320 name: "some things match and are forced to run, nothing to skip", 321 filter: &ArbitraryFilter{ 322 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 323 return p.Name == "should-trigger", true, true 324 }, 325 }, 326 presubmits: []config.Presubmit{{ 327 JobBase: config.JobBase{Name: "should-trigger"}, 328 Reporter: config.Reporter{Context: "first"}, 329 }, { 330 JobBase: config.JobBase{Name: "ignored"}, 331 Reporter: config.Reporter{Context: "second"}, 332 }}, 333 changesError: false, 334 expectedToTrigger: []config.Presubmit{{ 335 JobBase: config.JobBase{Name: "should-trigger"}, 336 Reporter: config.Reporter{Context: "first"}, 337 }}, 338 expectErr: false, 339 }, 340 { 341 name: "everything matches and some things are forced to run, others should be skipped", 342 filter: &ArbitraryFilter{ 343 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 344 return true, p.Name == "should-trigger", p.Name == "should-trigger" 345 }, 346 }, 347 presubmits: []config.Presubmit{{ 348 JobBase: config.JobBase{Name: "should-trigger"}, 349 Reporter: config.Reporter{Context: "first"}, 350 }, { 351 JobBase: config.JobBase{Name: "should-trigger"}, 352 Reporter: config.Reporter{Context: "second"}, 353 }, { 354 JobBase: config.JobBase{Name: "should-skip"}, 355 Reporter: config.Reporter{Context: "third"}, 356 }, { 357 JobBase: config.JobBase{Name: "should-skip"}, 358 Reporter: config.Reporter{Context: "fourth"}, 359 }}, 360 changesError: false, 361 expectedToTrigger: []config.Presubmit{{ 362 JobBase: config.JobBase{Name: "should-trigger"}, 363 Reporter: config.Reporter{Context: "first"}, 364 }, { 365 JobBase: config.JobBase{Name: "should-trigger"}, 366 Reporter: config.Reporter{Context: "second"}, 367 }}, 368 expectErr: false, 369 }, 370 { 371 name: "everything matches and some that are forces to run supercede some that are skipped due to shared contexts", 372 filter: &ArbitraryFilter{ 373 override: func(p config.Presubmit) (shouldRun bool, forcedToRun bool, defaultBehavior bool) { 374 return true, p.Name == "should-trigger", p.Name == "should-trigger" 375 }, 376 }, 377 presubmits: []config.Presubmit{{ 378 JobBase: config.JobBase{Name: "should-trigger"}, 379 Reporter: config.Reporter{Context: "first"}, 380 }, { 381 JobBase: config.JobBase{Name: "should-trigger"}, 382 Reporter: config.Reporter{Context: "second"}, 383 }, { 384 JobBase: config.JobBase{Name: "should-skip"}, 385 Reporter: config.Reporter{Context: "third"}, 386 }, { 387 JobBase: config.JobBase{Name: "should-not-skip"}, 388 Reporter: config.Reporter{Context: "second"}, 389 }}, 390 changesError: false, 391 expectedToTrigger: []config.Presubmit{{ 392 JobBase: config.JobBase{Name: "should-trigger"}, 393 Reporter: config.Reporter{Context: "first"}, 394 }, { 395 JobBase: config.JobBase{Name: "should-trigger"}, 396 Reporter: config.Reporter{Context: "second"}, 397 }}, 398 expectErr: false, 399 }, 400 } 401 402 branch := "foobar" 403 404 for _, testCase := range testCases { 405 t.Run(testCase.name, func(t *testing.T) { 406 actualToTrigger, err := FilterPresubmits(testCase.filter, fakeChangedFilesProvider(testCase.changesError), branch, testCase.presubmits, logrus.WithField("test-case", testCase.name)) 407 if testCase.expectErr && err == nil { 408 t.Errorf("%s: expected an error filtering presubmits, but got none", testCase.name) 409 } 410 if !testCase.expectErr && err != nil { 411 t.Errorf("%s: expected no error filtering presubmits, but got one: %v", testCase.name, err) 412 } 413 if !reflect.DeepEqual(actualToTrigger, testCase.expectedToTrigger) { 414 t.Errorf("%s: incorrect set of presubmits to skip: %s", testCase.name, diff.ObjectReflectDiff(actualToTrigger, testCase.expectedToTrigger)) 415 } 416 }) 417 } 418 } 419 420 type orgRepoRef struct { 421 org, repo, ref string 422 } 423 424 type fakeContextGetter struct { 425 status map[orgRepoRef]*github.CombinedStatus 426 errors map[orgRepoRef]error 427 } 428 429 func (f *fakeContextGetter) getContexts(key orgRepoRef) (sets.Set[string], sets.Set[string], error) { 430 allContexts := sets.New[string]() 431 failedContexts := sets.New[string]() 432 if err, exists := f.errors[key]; exists { 433 return failedContexts, allContexts, err 434 } 435 combinedStatus, exists := f.status[key] 436 if !exists { 437 return failedContexts, allContexts, fmt.Errorf("failed to find status for %s/%s@%s", key.org, key.repo, key.ref) 438 } 439 for _, status := range combinedStatus.Statuses { 440 allContexts.Insert(status.Context) 441 if status.State == github.StatusError || status.State == github.StatusFailure { 442 failedContexts.Insert(status.Context) 443 } 444 } 445 return failedContexts, allContexts, nil 446 } 447 448 func TestPresubmitFilter(t *testing.T) { 449 statuses := &github.CombinedStatus{Statuses: []github.Status{ 450 { 451 Context: "existing-successful", 452 State: github.StatusSuccess, 453 }, 454 { 455 Context: "existing-pending", 456 State: github.StatusPending, 457 }, 458 { 459 Context: "existing-error", 460 State: github.StatusError, 461 }, 462 { 463 Context: "existing-failure", 464 State: github.StatusFailure, 465 }, 466 }} 467 var testCases = []struct { 468 name string 469 honorOkToTest bool 470 body, org, repo, ref string 471 presubmits []config.Presubmit 472 expected [][]bool 473 statusErr, expectErr bool 474 }{ 475 { 476 name: "test all comment selects all tests that don't need an explicit trigger", 477 body: "/test all", 478 org: "org", 479 repo: "repo", 480 ref: "ref", 481 presubmits: []config.Presubmit{ 482 { 483 JobBase: config.JobBase{ 484 Name: "always-runs", 485 }, 486 AlwaysRun: true, 487 Reporter: config.Reporter{ 488 Context: "always-runs", 489 }, 490 }, 491 { 492 JobBase: config.JobBase{ 493 Name: "runs-if-changed", 494 }, 495 Reporter: config.Reporter{ 496 Context: "runs-if-changed", 497 }, 498 RegexpChangeMatcher: config.RegexpChangeMatcher{ 499 RunIfChanged: "sometimes", 500 }, 501 }, 502 { 503 JobBase: config.JobBase{ 504 Name: "runs-if-changed", 505 }, 506 Reporter: config.Reporter{ 507 Context: "runs-if-changed", 508 }, 509 RegexpChangeMatcher: config.RegexpChangeMatcher{ 510 SkipIfOnlyChanged: "sometimes", 511 }, 512 }, 513 { 514 JobBase: config.JobBase{ 515 Name: "runs-if-triggered", 516 }, 517 Reporter: config.Reporter{ 518 Context: "runs-if-triggered", 519 }, 520 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 521 RerunCommand: "/test trigger", 522 }, 523 }, 524 expected: [][]bool{{true, false, false}, {true, false, false}, {true, false, false}, {false, false, false}}, 525 }, 526 { 527 name: "honored ok-to-test comment selects all tests that don't need an explicit trigger", 528 body: "/ok-to-test", 529 honorOkToTest: true, 530 org: "org", 531 repo: "repo", 532 ref: "ref", 533 presubmits: []config.Presubmit{ 534 { 535 JobBase: config.JobBase{ 536 Name: "always-runs", 537 }, 538 AlwaysRun: true, 539 Reporter: config.Reporter{ 540 Context: "always-runs", 541 }, 542 }, 543 { 544 JobBase: config.JobBase{ 545 Name: "runs-if-changed", 546 }, 547 Reporter: config.Reporter{ 548 Context: "runs-if-changed", 549 }, 550 RegexpChangeMatcher: config.RegexpChangeMatcher{ 551 RunIfChanged: "sometimes", 552 }, 553 }, 554 { 555 JobBase: config.JobBase{ 556 Name: "runs-if-changed", 557 }, 558 Reporter: config.Reporter{ 559 Context: "runs-if-changed", 560 }, 561 RegexpChangeMatcher: config.RegexpChangeMatcher{ 562 SkipIfOnlyChanged: "sometimes", 563 }, 564 }, 565 { 566 JobBase: config.JobBase{ 567 Name: "runs-if-triggered", 568 }, 569 Reporter: config.Reporter{ 570 Context: "runs-if-triggered", 571 }, 572 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 573 RerunCommand: "/test trigger", 574 }, 575 }, 576 expected: [][]bool{{true, false, false}, {true, false, false}, {true, false, false}, {false, false, false}}, 577 }, 578 { 579 name: "not honored ok-to-test comment selects no tests", 580 body: "/ok-to-test", 581 honorOkToTest: false, 582 org: "org", 583 repo: "repo", 584 ref: "ref", 585 presubmits: []config.Presubmit{ 586 { 587 JobBase: config.JobBase{ 588 Name: "always-runs", 589 }, 590 AlwaysRun: true, 591 Reporter: config.Reporter{ 592 Context: "always-runs", 593 }, 594 }, 595 { 596 JobBase: config.JobBase{ 597 Name: "runs-if-changed", 598 }, 599 Reporter: config.Reporter{ 600 Context: "runs-if-changed", 601 }, 602 RegexpChangeMatcher: config.RegexpChangeMatcher{ 603 RunIfChanged: "sometimes", 604 }, 605 }, 606 { 607 JobBase: config.JobBase{ 608 Name: "runs-if-changed", 609 }, 610 Reporter: config.Reporter{ 611 Context: "runs-if-changed", 612 }, 613 RegexpChangeMatcher: config.RegexpChangeMatcher{ 614 SkipIfOnlyChanged: "sometimes", 615 }, 616 }, 617 { 618 JobBase: config.JobBase{ 619 Name: "runs-if-triggered", 620 }, 621 Reporter: config.Reporter{ 622 Context: "runs-if-triggered", 623 }, 624 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 625 RerunCommand: "/test trigger", 626 }, 627 }, 628 expected: [][]bool{{false, false, false}, {false, false, false}, {false, false, false}, {false, false, false}}, 629 }, 630 { 631 name: "statuses are not gathered unless retest is specified (will error but we should not see it)", 632 body: "not a command", 633 org: "org", 634 repo: "repo", 635 ref: "ref", 636 presubmits: []config.Presubmit{}, 637 expected: [][]bool{}, 638 statusErr: true, 639 expectErr: false, 640 }, 641 { 642 name: "statuses are gathered when retest is specified and gather error is propagated", 643 body: "/retest", 644 org: "org", 645 repo: "repo", 646 ref: "ref", 647 presubmits: []config.Presubmit{}, 648 expected: [][]bool{}, 649 statusErr: true, 650 expectErr: true, 651 }, 652 { 653 name: "retest command selects for errored or failed contexts and required but missing contexts", 654 body: "/retest", 655 org: "org", 656 repo: "repo", 657 ref: "ref", 658 presubmits: []config.Presubmit{ 659 { 660 JobBase: config.JobBase{ 661 Name: "successful-job", 662 }, 663 Reporter: config.Reporter{ 664 Context: "existing-successful", 665 }, 666 }, 667 { 668 JobBase: config.JobBase{ 669 Name: "pending-job", 670 }, 671 Reporter: config.Reporter{ 672 Context: "existing-pending", 673 }, 674 }, 675 { 676 JobBase: config.JobBase{ 677 Name: "failure-job", 678 }, 679 Reporter: config.Reporter{ 680 Context: "existing-failure", 681 }, 682 }, 683 { 684 JobBase: config.JobBase{ 685 Name: "error-job", 686 }, 687 Reporter: config.Reporter{ 688 Context: "existing-error", 689 }, 690 }, 691 { 692 JobBase: config.JobBase{ 693 Name: "missing-always-runs", 694 }, 695 Reporter: config.Reporter{ 696 Context: "missing-always-runs", 697 }, 698 AlwaysRun: true, 699 }, 700 }, 701 expected: [][]bool{{false, false, false}, {false, false, false}, {true, false, true}, {true, false, true}, {true, false, false}}, 702 }, 703 { 704 name: "retest command selects for errored or failed contexts unless they are optional", 705 body: "/retest-required", 706 org: "org", 707 repo: "repo", 708 ref: "ref", 709 presubmits: []config.Presubmit{ 710 { 711 JobBase: config.JobBase{ 712 Name: "successful-job", 713 }, 714 Reporter: config.Reporter{ 715 Context: "existing-successful", 716 }, 717 }, 718 { 719 JobBase: config.JobBase{ 720 Name: "pending-job", 721 }, 722 Reporter: config.Reporter{ 723 Context: "existing-pending", 724 }, 725 }, 726 { 727 JobBase: config.JobBase{ 728 Name: "failure-job", 729 }, 730 Reporter: config.Reporter{ 731 Context: "existing-failure", 732 }, 733 Optional: true, 734 }, 735 { 736 JobBase: config.JobBase{ 737 Name: "error-job", 738 }, 739 Reporter: config.Reporter{ 740 Context: "existing-error", 741 }, 742 }, 743 { 744 JobBase: config.JobBase{ 745 Name: "missing-always-runs", 746 }, 747 Reporter: config.Reporter{ 748 Context: "missing-always-runs", 749 }, 750 AlwaysRun: true, 751 }, 752 }, 753 expected: [][]bool{{false, false, false}, {false, false, false}, {false, false, false}, {true, false, true}, {true, false, false}}, 754 }, 755 { 756 name: "explicit test command filters for jobs that match", 757 body: "/test trigger", 758 org: "org", 759 repo: "repo", 760 ref: "ref", 761 presubmits: []config.Presubmit{ 762 { 763 JobBase: config.JobBase{ 764 Name: "always-runs", 765 }, 766 AlwaysRun: true, 767 Reporter: config.Reporter{ 768 Context: "always-runs", 769 }, 770 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 771 RerunCommand: "/test trigger", 772 }, 773 { 774 JobBase: config.JobBase{ 775 Name: "runs-if-changed", 776 }, 777 Reporter: config.Reporter{ 778 Context: "runs-if-changed", 779 }, 780 RegexpChangeMatcher: config.RegexpChangeMatcher{ 781 RunIfChanged: "sometimes", 782 }, 783 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 784 RerunCommand: "/test trigger", 785 }, 786 { 787 JobBase: config.JobBase{ 788 Name: "runs-if-changed", 789 }, 790 Reporter: config.Reporter{ 791 Context: "runs-if-changed", 792 }, 793 RegexpChangeMatcher: config.RegexpChangeMatcher{ 794 SkipIfOnlyChanged: "sometimes", 795 }, 796 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 797 RerunCommand: "/test trigger", 798 }, 799 { 800 JobBase: config.JobBase{ 801 Name: "runs-if-triggered", 802 }, 803 Reporter: config.Reporter{ 804 Context: "runs-if-triggered", 805 }, 806 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 807 RerunCommand: "/test trigger", 808 }, 809 { 810 JobBase: config.JobBase{ 811 Name: "always-runs", 812 }, 813 AlwaysRun: true, 814 Reporter: config.Reporter{ 815 Context: "always-runs", 816 }, 817 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 818 RerunCommand: "/test other-trigger", 819 }, 820 { 821 JobBase: config.JobBase{ 822 Name: "runs-if-changed", 823 }, 824 Reporter: config.Reporter{ 825 Context: "runs-if-changed", 826 }, 827 RegexpChangeMatcher: config.RegexpChangeMatcher{ 828 RunIfChanged: "sometimes", 829 }, 830 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 831 RerunCommand: "/test other-trigger", 832 }, 833 { 834 JobBase: config.JobBase{ 835 Name: "runs-if-changed", 836 }, 837 Reporter: config.Reporter{ 838 Context: "runs-if-changed", 839 }, 840 RegexpChangeMatcher: config.RegexpChangeMatcher{ 841 SkipIfOnlyChanged: "sometimes", 842 }, 843 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 844 RerunCommand: "/test other-trigger", 845 }, 846 { 847 JobBase: config.JobBase{ 848 Name: "runs-if-triggered", 849 }, 850 Reporter: config.Reporter{ 851 Context: "runs-if-triggered", 852 }, 853 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 854 RerunCommand: "/test other-trigger", 855 }, 856 }, 857 expected: [][]bool{ 858 {true, true, true}, 859 {true, true, true}, 860 {true, true, true}, 861 {true, true, true}, 862 {false, false, false}, 863 {false, false, false}, 864 {false, false, false}, 865 {false, false, false}, 866 }, 867 }, 868 { 869 name: "comments matching more than one case will select the union of presubmits", 870 body: `/test trigger 871 /test all 872 /retest`, 873 org: "org", 874 repo: "repo", 875 ref: "ref", 876 presubmits: []config.Presubmit{ 877 { 878 JobBase: config.JobBase{ 879 Name: "always-runs", 880 }, 881 AlwaysRun: true, 882 Reporter: config.Reporter{ 883 Context: "existing-successful", 884 }, 885 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 886 RerunCommand: "/test other-trigger", 887 }, 888 { 889 JobBase: config.JobBase{ 890 Name: "runs-if-changed", 891 }, 892 Reporter: config.Reporter{ 893 Context: "existing-successful", 894 }, 895 RegexpChangeMatcher: config.RegexpChangeMatcher{ 896 RunIfChanged: "sometimes", 897 }, 898 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 899 RerunCommand: "/test other-trigger", 900 }, 901 { 902 JobBase: config.JobBase{ 903 Name: "runs-if-changed", 904 }, 905 Reporter: config.Reporter{ 906 Context: "existing-successful", 907 }, 908 RegexpChangeMatcher: config.RegexpChangeMatcher{ 909 SkipIfOnlyChanged: "sometimes", 910 }, 911 Trigger: `(?m)^/test (?:.*? )?other-trigger(?: .*?)?$`, 912 RerunCommand: "/test other-trigger", 913 }, 914 { 915 JobBase: config.JobBase{ 916 Name: "runs-if-triggered", 917 }, 918 Reporter: config.Reporter{ 919 Context: "runs-if-triggered", 920 }, 921 Trigger: `(?m)^/test (?:.*? )?trigger(?: .*?)?$`, 922 RerunCommand: "/test trigger", 923 }, 924 { 925 JobBase: config.JobBase{ 926 Name: "successful-job", 927 }, 928 Reporter: config.Reporter{ 929 Context: "existing-successful", 930 }, 931 }, 932 { 933 JobBase: config.JobBase{ 934 Name: "pending-job", 935 }, 936 Reporter: config.Reporter{ 937 Context: "existing-pending", 938 }, 939 }, 940 { 941 JobBase: config.JobBase{ 942 Name: "failure-job", 943 }, 944 Reporter: config.Reporter{ 945 Context: "existing-failure", 946 }, 947 }, 948 { 949 JobBase: config.JobBase{ 950 Name: "error-job", 951 }, 952 Reporter: config.Reporter{ 953 Context: "existing-error", 954 }, 955 }, 956 { 957 JobBase: config.JobBase{ 958 Name: "missing-always-runs", 959 }, 960 AlwaysRun: true, 961 Reporter: config.Reporter{ 962 Context: "missing-always-runs", 963 }, 964 }, 965 }, 966 expected: [][]bool{ 967 {true, false, false}, 968 {true, false, false}, 969 {true, false, false}, 970 {true, true, true}, 971 {false, false, false}, 972 {false, false, false}, 973 {true, false, true}, 974 {true, false, true}, 975 {true, false, false}, 976 }, 977 }, 978 } 979 980 for _, testCase := range testCases { 981 t.Run(testCase.name, func(t *testing.T) { 982 if len(testCase.presubmits) != len(testCase.expected) { 983 t.Fatalf("%s: have %d presubmits but only %d expected filter outputs", testCase.name, len(testCase.presubmits), len(testCase.expected)) 984 } 985 if err := config.SetPresubmitRegexes(testCase.presubmits); err != nil { 986 t.Fatalf("%s: could not set presubmit regexes: %v", testCase.name, err) 987 } 988 fsg := &fakeContextGetter{ 989 errors: map[orgRepoRef]error{}, 990 status: map[orgRepoRef]*github.CombinedStatus{}, 991 } 992 key := orgRepoRef{org: testCase.org, repo: testCase.repo, ref: testCase.ref} 993 if testCase.statusErr { 994 fsg.errors[key] = errors.New("failure") 995 } else { 996 fsg.status[key] = statuses 997 } 998 999 fakeContextGetter := func() (sets.Set[string], sets.Set[string], error) { 1000 1001 return fsg.getContexts(key) 1002 } 1003 1004 filter, err := PresubmitFilter(testCase.honorOkToTest, fakeContextGetter, testCase.body, logrus.WithField("test-case", testCase.name)) 1005 1006 if testCase.expectErr && err == nil { 1007 t.Errorf("%s: expected an error creating the filter, but got none", testCase.name) 1008 } 1009 if !testCase.expectErr && err != nil { 1010 t.Errorf("%s: expected no error creating the filter, but got one: %v", testCase.name, err) 1011 } 1012 for i, presubmit := range testCase.presubmits { 1013 actualFiltered, actualForced, actualDefault := filter.ShouldRun(presubmit) 1014 expectedFiltered, expectedForced, expectedDefault := testCase.expected[i][0], testCase.expected[i][1], testCase.expected[i][2] 1015 if actualFiltered != expectedFiltered { 1016 t.Errorf("%s: filter did not evaluate correctly, expected %v but got %v for %v", testCase.name, expectedFiltered, actualFiltered, presubmit.Name) 1017 } 1018 if actualForced != expectedForced { 1019 t.Errorf("%s: filter did not determine forced correctly, expected %v but got %v for %v", testCase.name, expectedForced, actualForced, presubmit.Name) 1020 } 1021 if actualDefault != expectedDefault { 1022 t.Errorf("%s: filter did not determine default correctly, expected %v but got %v for %v", testCase.name, expectedDefault, actualDefault, presubmit.Name) 1023 } 1024 } 1025 }) 1026 } 1027 }