github.com/jenkins-x/test-infra@v0.0.7/prow/config/jobs_test.go (about) 1 /* 2 Copyright 2017 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 "flag" 21 "fmt" 22 "os" 23 "regexp" 24 "testing" 25 26 "k8s.io/test-infra/prow/kube" 27 ) 28 29 var c *Config 30 var configPath = flag.String("config", "../config.yaml", "Path to prow config") 31 var jobConfigPath = flag.String("job-config", "../../config/jobs", "Path to prow job config") 32 var podRe = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`) 33 34 // Consistent but meaningless order. 35 func flattenJobs(jobs []Presubmit) []Presubmit { 36 ret := jobs 37 for _, job := range jobs { 38 if len(job.RunAfterSuccess) > 0 { 39 ret = append(ret, flattenJobs(job.RunAfterSuccess)...) 40 } 41 } 42 return ret 43 } 44 45 // Returns if two brancher has overlapping branches 46 func checkOverlapBrancher(b1, b2 Brancher) bool { 47 if b1.RunsAgainstAllBranch() || b2.RunsAgainstAllBranch() { 48 return true 49 } 50 51 for _, run1 := range b1.Branches { 52 if b2.RunsAgainstBranch(run1) { 53 return true 54 } 55 } 56 57 for _, run2 := range b2.Branches { 58 if b1.RunsAgainstBranch(run2) { 59 return true 60 } 61 } 62 63 return false 64 } 65 66 func TestMain(m *testing.M) { 67 flag.Parse() 68 if *configPath == "" { 69 fmt.Println("--config must set") 70 os.Exit(1) 71 } 72 73 conf, err := Load(*configPath, *jobConfigPath) 74 if err != nil { 75 fmt.Printf("Could not load config: %v", err) 76 os.Exit(1) 77 } 78 c = conf 79 80 os.Exit(m.Run()) 81 } 82 83 func TestPresubmits(t *testing.T) { 84 if len(c.Presubmits) == 0 { 85 t.Fatalf("No jobs found in presubmit.yaml.") 86 } 87 88 for _, rootJobs := range c.Presubmits { 89 jobs := flattenJobs(rootJobs) 90 for i, job := range jobs { 91 if job.Name == "" { 92 t.Errorf("Job %v needs a name.", job) 93 continue 94 } 95 if !job.SkipReport && job.Context == "" { 96 t.Errorf("Job %s needs a context.", job.Name) 97 } 98 if job.RerunCommand == "" || job.Trigger == "" { 99 t.Errorf("Job %s needs a trigger and a rerun command.", job.Name) 100 continue 101 } 102 103 if len(job.Brancher.Branches) > 0 && len(job.Brancher.SkipBranches) > 0 { 104 t.Errorf("Job %s : Cannot have both branches and skip_branches set", job.Name) 105 } 106 // Next check that the rerun command doesn't run any other jobs. 107 for j, job2 := range jobs[i+1:] { 108 if job.Name == job2.Name { 109 // Make sure max_concurrency are the same 110 if job.MaxConcurrency != job2.MaxConcurrency { 111 t.Errorf("Jobs %s share same name but has different max_concurrency", job.Name) 112 } 113 // Make sure branches are not overlapping 114 if checkOverlapBrancher(job.Brancher, job2.Brancher) { 115 t.Errorf("Two jobs have the same name: %s, and have conflicting branches", job.Name) 116 } 117 } else { 118 if job.Context == job2.Context { 119 t.Errorf("Jobs %s and %s have the same context: %s", job.Name, job2.Name, job.Context) 120 } 121 if job2.re.MatchString(job.RerunCommand) { 122 t.Errorf("%d, %d, RerunCommand \"%s\" from job %s matches \"%v\" from job %s but shouldn't.", i, j, job.RerunCommand, job.Name, job2.Trigger, job2.Name) 123 } 124 } 125 } 126 } 127 } 128 } 129 130 func TestCommentBodyMatches(t *testing.T) { 131 var testcases = []struct { 132 repo string 133 body string 134 expectedJobs []string 135 }{ 136 { 137 "org/repo", 138 "this is a random comment", 139 []string{}, 140 }, 141 { 142 "org/repo", 143 "/ok-to-test", 144 []string{"gce", "unit"}, 145 }, 146 { 147 "org/repo", 148 "/test all", 149 []string{"gce", "unit", "gke"}, 150 }, 151 { 152 "org/repo", 153 "/test unit", 154 []string{"unit"}, 155 }, 156 { 157 "org/repo", 158 "/test federation", 159 []string{"federation"}, 160 }, 161 { 162 "org/repo2", 163 "/test all", 164 []string{"cadveapster", "after-cadveapster", "after-after-cadveapster"}, 165 }, 166 { 167 "org/repo2", 168 "/test really", 169 []string{"after-cadveapster"}, 170 }, 171 { 172 "org/repo2", 173 "/test again really", 174 []string{"after-after-cadveapster"}, 175 }, 176 { 177 "org/repo3", 178 "/test all", 179 []string{}, 180 }, 181 } 182 c := &Config{ 183 JobConfig: JobConfig{ 184 Presubmits: map[string][]Presubmit{ 185 "org/repo": { 186 { 187 JobBase: JobBase{ 188 Name: "gce", 189 }, 190 re: regexp.MustCompile(`/test (gce|all)`), 191 AlwaysRun: true, 192 }, 193 { 194 JobBase: JobBase{ 195 Name: "unit", 196 }, 197 re: regexp.MustCompile(`/test (unit|all)`), 198 AlwaysRun: true, 199 }, 200 { 201 JobBase: JobBase{ 202 Name: "gke", 203 }, 204 re: regexp.MustCompile(`/test (gke|all)`), 205 AlwaysRun: false, 206 }, 207 { 208 JobBase: JobBase{ 209 Name: "federation", 210 }, 211 re: regexp.MustCompile(`/test federation`), 212 AlwaysRun: false, 213 }, 214 }, 215 "org/repo2": { 216 { 217 JobBase: JobBase{ 218 Name: "cadveapster", 219 }, 220 re: regexp.MustCompile(`/test all`), 221 AlwaysRun: true, 222 RunAfterSuccess: []Presubmit{ 223 { 224 JobBase: JobBase{ 225 Name: "after-cadveapster", 226 }, 227 re: regexp.MustCompile(`/test (really|all)`), 228 AlwaysRun: true, 229 RunAfterSuccess: []Presubmit{ 230 { 231 JobBase: JobBase{ 232 Name: "after-after-cadveapster", 233 }, 234 re: regexp.MustCompile(`/test (again really|all)`), 235 AlwaysRun: true, 236 }, 237 }, 238 }, 239 { 240 JobBase: JobBase{ 241 Name: "another-after-cadveapster", 242 }, 243 re: regexp.MustCompile(`@k8s-bot don't test this`), 244 AlwaysRun: true, 245 }, 246 }, 247 }, 248 }, 249 }, 250 }, 251 } 252 for _, tc := range testcases { 253 actualJobs := c.MatchingPresubmits(tc.repo, tc.body, regexp.MustCompile(`/ok-to-test`).MatchString(tc.body)) 254 match := true 255 if len(actualJobs) != len(tc.expectedJobs) { 256 match = false 257 } else { 258 for _, actualJob := range actualJobs { 259 found := false 260 for _, expectedJob := range tc.expectedJobs { 261 if expectedJob == actualJob.Name { 262 found = true 263 break 264 } 265 } 266 if !found { 267 match = false 268 break 269 } 270 } 271 } 272 if !match { 273 t.Errorf("Wrong jobs for body %s. Got %v, expected %v.", tc.body, actualJobs, tc.expectedJobs) 274 } 275 } 276 } 277 278 func TestRetestPresubmits(t *testing.T) { 279 var testcases = []struct { 280 skipContexts map[string]bool 281 runContexts map[string]bool 282 expectedContexts []string 283 }{ 284 { 285 map[string]bool{}, 286 map[string]bool{}, 287 []string{"gce", "unit"}, 288 }, 289 { 290 map[string]bool{"gce": true}, 291 map[string]bool{}, 292 []string{"unit"}, 293 }, 294 { 295 map[string]bool{}, 296 map[string]bool{"federation": true, "nonexistent": true}, 297 []string{"gce", "unit", "federation"}, 298 }, 299 { 300 map[string]bool{}, 301 map[string]bool{"gke": true}, 302 []string{"gce", "unit", "gke"}, 303 }, 304 { 305 map[string]bool{"gce": true}, 306 map[string]bool{"gce": true}, // should never happen 307 []string{"unit"}, 308 }, 309 } 310 c := &Config{ 311 JobConfig: JobConfig{ 312 Presubmits: map[string][]Presubmit{ 313 "org/repo": { 314 { 315 Context: "gce", 316 AlwaysRun: true, 317 }, 318 { 319 Context: "unit", 320 AlwaysRun: true, 321 }, 322 { 323 Context: "gke", 324 AlwaysRun: false, 325 }, 326 { 327 Context: "federation", 328 AlwaysRun: false, 329 }, 330 }, 331 "org/repo2": { 332 { 333 Context: "shouldneverrun", 334 AlwaysRun: true, 335 }, 336 }, 337 }, 338 }, 339 } 340 for _, tc := range testcases { 341 actualContexts := c.RetestPresubmits("org/repo", tc.skipContexts, tc.runContexts) 342 match := true 343 if len(actualContexts) != len(tc.expectedContexts) { 344 match = false 345 } else { 346 for _, actualJob := range actualContexts { 347 found := false 348 for _, expectedContext := range tc.expectedContexts { 349 if expectedContext == actualJob.Context { 350 found = true 351 break 352 } 353 } 354 if !found { 355 match = false 356 break 357 } 358 } 359 } 360 if !match { 361 t.Errorf("Wrong contexts for skip %v run %v. Got %v, expected %v.", tc.runContexts, tc.skipContexts, actualContexts, tc.expectedContexts) 362 } 363 } 364 365 } 366 367 func TestConditionalPresubmits(t *testing.T) { 368 presubmits := []Presubmit{ 369 { 370 JobBase: JobBase{ 371 Name: "cross build", 372 }, 373 RegexpChangeMatcher: RegexpChangeMatcher{ 374 RunIfChanged: `(Makefile|\.sh|_(windows|linux|osx|unknown)(_test)?\.go)$`, 375 }, 376 }, 377 } 378 SetPresubmitRegexes(presubmits) 379 ps := presubmits[0] 380 var testcases = []struct { 381 changes []string 382 expected bool 383 }{ 384 {[]string{"some random file"}, false}, 385 {[]string{"./pkg/util/rlimit/rlimit_linux.go"}, true}, 386 {[]string{"./pkg/util/rlimit/rlimit_unknown_test.go"}, true}, 387 {[]string{"build.sh"}, true}, 388 {[]string{"build.shoo"}, false}, 389 {[]string{"Makefile"}, true}, 390 } 391 for _, tc := range testcases { 392 actual := ps.RunsAgainstChanges(tc.changes) 393 if actual != tc.expected { 394 t.Errorf("wrong RunsAgainstChanges(%#v) result. Got %v, expected %v", tc.changes, actual, tc.expected) 395 } 396 } 397 } 398 399 func TestListPresubmit(t *testing.T) { 400 c := &Config{ 401 JobConfig: JobConfig{ 402 Presubmits: map[string][]Presubmit{ 403 "r1": { 404 { 405 JobBase: JobBase{ 406 Name: "a", 407 }, 408 RunAfterSuccess: []Presubmit{ 409 {JobBase: JobBase{Name: "aa"}}, 410 {JobBase: JobBase{Name: "ab"}}, 411 }, 412 }, 413 {JobBase: JobBase{Name: "b"}}, 414 }, 415 "r2": { 416 { 417 JobBase: JobBase{ 418 Name: "c", 419 }, 420 RunAfterSuccess: []Presubmit{ 421 {JobBase: JobBase{Name: "ca"}}, 422 {JobBase: JobBase{Name: "cb"}}, 423 }, 424 }, 425 {JobBase: JobBase{Name: "d"}}, 426 }, 427 }, 428 Postsubmits: map[string][]Postsubmit{ 429 "r1": {{JobBase: JobBase{Name: "e"}}}, 430 }, 431 Periodics: []Periodic{ 432 {JobBase: JobBase{Name: "f"}}, 433 }, 434 }, 435 } 436 437 var testcases = []struct { 438 name string 439 expected []string 440 repos []string 441 }{ 442 { 443 "all presubmits", 444 []string{"a", "aa", "ab", "b", "c", "ca", "cb", "d"}, 445 []string{}, 446 }, 447 { 448 "r2 presubmits", 449 []string{"c", "ca", "cb", "d"}, 450 []string{"r2"}, 451 }, 452 } 453 454 for _, tc := range testcases { 455 actual := c.AllPresubmits(tc.repos) 456 if len(actual) != len(tc.expected) { 457 t.Fatalf("test %s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected) 458 } 459 for _, j1 := range tc.expected { 460 found := false 461 for _, j2 := range actual { 462 if j1 == j2.Name { 463 found = true 464 break 465 } 466 } 467 if !found { 468 t.Errorf("test %s - Did not find job %s in output", tc.name, j1) 469 } 470 } 471 } 472 } 473 474 func TestListPostsubmit(t *testing.T) { 475 c := &Config{ 476 JobConfig: JobConfig{ 477 Presubmits: map[string][]Presubmit{ 478 "r1": {{JobBase: JobBase{Name: "a"}}}, 479 }, 480 Postsubmits: map[string][]Postsubmit{ 481 "r1": { 482 { 483 JobBase: JobBase{ 484 Name: "c", 485 }, 486 RunAfterSuccess: []Postsubmit{ 487 {JobBase: JobBase{Name: "ca"}}, 488 {JobBase: JobBase{Name: "cb"}}, 489 }, 490 }, 491 {JobBase: JobBase{Name: "d"}}, 492 }, 493 "r2": {{JobBase: JobBase{Name: "e"}}}, 494 }, 495 Periodics: []Periodic{ 496 {JobBase: JobBase{Name: "f"}}, 497 }, 498 }, 499 } 500 501 var testcases = []struct { 502 name string 503 expected []string 504 repos []string 505 }{ 506 { 507 "all postsubmits", 508 []string{"c", "ca", "cb", "d", "e"}, 509 []string{}, 510 }, 511 { 512 "r2 presubmits", 513 []string{"e"}, 514 []string{"r2"}, 515 }, 516 } 517 518 for _, tc := range testcases { 519 actual := c.AllPostsubmits(tc.repos) 520 if len(actual) != len(tc.expected) { 521 t.Fatalf("%s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected) 522 } 523 for _, j1 := range tc.expected { 524 found := false 525 for _, j2 := range actual { 526 if j1 == j2.Name { 527 found = true 528 break 529 } 530 } 531 if !found { 532 t.Errorf("Did not find job %s in output", j1) 533 } 534 } 535 } 536 } 537 538 func TestListPeriodic(t *testing.T) { 539 c := &Config{ 540 JobConfig: JobConfig{ 541 Presubmits: map[string][]Presubmit{ 542 "r1": {{JobBase: JobBase{Name: "a"}}}, 543 }, 544 Postsubmits: map[string][]Postsubmit{ 545 "r1": {{JobBase: JobBase{Name: "b"}}}, 546 }, 547 Periodics: []Periodic{ 548 { 549 JobBase: JobBase{ 550 Name: "c", 551 }, 552 RunAfterSuccess: []Periodic{ 553 {JobBase: JobBase{Name: "ca"}}, 554 {JobBase: JobBase{Name: "cb"}}, 555 }, 556 }, 557 {JobBase: JobBase{Name: "d"}}, 558 }, 559 }, 560 } 561 562 expected := []string{"c", "ca", "cb", "d"} 563 actual := c.AllPeriodics() 564 if len(actual) != len(expected) { 565 t.Fatalf("Wrong number of jobs. Got %v, expected %v", actual, expected) 566 } 567 for _, j1 := range expected { 568 found := false 569 for _, j2 := range actual { 570 if j1 == j2.Name { 571 found = true 572 break 573 } 574 } 575 if !found { 576 t.Errorf("Did not find job %s in output", j1) 577 } 578 } 579 } 580 581 func TestRunAgainstBranch(t *testing.T) { 582 jobs := []Presubmit{ 583 { 584 JobBase: JobBase{ 585 Name: "a", 586 }, 587 Brancher: Brancher{SkipBranches: []string{"s"}}, 588 }, 589 { 590 JobBase: JobBase{ 591 Name: "b", 592 }, 593 Brancher: Brancher{Branches: []string{"r"}}, 594 }, 595 { 596 JobBase: JobBase{ 597 Name: "c", 598 }, 599 Brancher: Brancher{ 600 SkipBranches: []string{"s"}, 601 Branches: []string{"r"}, 602 }, 603 }, 604 { 605 JobBase: JobBase{ 606 Name: "d", 607 }, 608 Brancher: Brancher{ 609 SkipBranches: []string{"s"}, 610 Branches: []string{"s", "r"}, 611 }, 612 }, 613 { 614 JobBase: JobBase{ 615 Name: "default", 616 }, 617 }, 618 } 619 620 if err := SetPresubmitRegexes(jobs); err != nil { 621 t.Fatalf("could not set regexes: %v", err) 622 } 623 624 for _, job := range jobs { 625 if job.Name == "default" { 626 if !job.RunsAgainstBranch("s") { 627 t.Errorf("Job %s should run branch s", job.Name) 628 } 629 } else if job.RunsAgainstBranch("s") { 630 t.Errorf("Job %s should not run branch s", job.Name) 631 } 632 633 if !job.RunsAgainstBranch("r") { 634 t.Errorf("Job %s should run branch r", job.Name) 635 } 636 } 637 } 638 639 func TestValidPodNames(t *testing.T) { 640 for _, j := range c.AllPresubmits([]string{}) { 641 if !podRe.MatchString(j.Name) { 642 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 643 } 644 } 645 for _, j := range c.AllPostsubmits([]string{}) { 646 if !podRe.MatchString(j.Name) { 647 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 648 } 649 } 650 for _, j := range c.AllPeriodics() { 651 if !podRe.MatchString(j.Name) { 652 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 653 } 654 } 655 } 656 657 func TestNoDuplicateJobs(t *testing.T) { 658 // Presubmit test is covered under TestPresubmits() above 659 660 allJobs := make(map[string]bool) 661 for _, j := range c.AllPostsubmits([]string{}) { 662 if allJobs[j.Name] { 663 t.Errorf("Found duplicate job in postsubmit: %s.", j.Name) 664 } 665 allJobs[j.Name] = true 666 } 667 668 allJobs = make(map[string]bool) 669 for _, j := range c.AllPeriodics() { 670 if allJobs[j.Name] { 671 t.Errorf("Found duplicate job in periodic %s.", j.Name) 672 } 673 allJobs[j.Name] = true 674 } 675 } 676 677 func TestMergePreset(t *testing.T) { 678 tcs := []struct { 679 name string 680 jobLabels map[string]string 681 pod *kube.PodSpec 682 presets []Preset 683 684 shouldError bool 685 numEnv int 686 numVol int 687 numVolMounts int 688 }{ 689 { 690 name: "one volume", 691 jobLabels: map[string]string{"foo": "bar"}, 692 pod: &kube.PodSpec{}, 693 presets: []Preset{ 694 { 695 Labels: map[string]string{"foo": "bar"}, 696 Volumes: []kube.Volume{{Name: "baz"}}, 697 }, 698 }, 699 numVol: 1, 700 }, 701 { 702 name: "wrong label", 703 jobLabels: map[string]string{"foo": "nope"}, 704 pod: &kube.PodSpec{}, 705 presets: []Preset{ 706 { 707 Labels: map[string]string{"foo": "bar"}, 708 Volumes: []kube.Volume{{Name: "baz"}}, 709 }, 710 }, 711 }, 712 { 713 name: "conflicting volume name", 714 jobLabels: map[string]string{"foo": "bar"}, 715 pod: &kube.PodSpec{Volumes: []kube.Volume{{Name: "baz"}}}, 716 presets: []Preset{ 717 { 718 Labels: map[string]string{"foo": "bar"}, 719 Volumes: []kube.Volume{{Name: "baz"}}, 720 }, 721 }, 722 shouldError: true, 723 }, 724 { 725 name: "non conflicting volume name", 726 jobLabels: map[string]string{"foo": "bar"}, 727 pod: &kube.PodSpec{Volumes: []kube.Volume{{Name: "baz"}}}, 728 presets: []Preset{ 729 { 730 Labels: map[string]string{"foo": "bar"}, 731 Volumes: []kube.Volume{{Name: "qux"}}, 732 }, 733 }, 734 numVol: 2, 735 }, 736 { 737 name: "one env", 738 jobLabels: map[string]string{"foo": "bar"}, 739 pod: &kube.PodSpec{Containers: []kube.Container{{}}}, 740 presets: []Preset{ 741 { 742 Labels: map[string]string{"foo": "bar"}, 743 Env: []kube.EnvVar{{Name: "baz"}}, 744 }, 745 }, 746 numEnv: 1, 747 }, 748 { 749 name: "one vm", 750 jobLabels: map[string]string{"foo": "bar"}, 751 pod: &kube.PodSpec{Containers: []kube.Container{{}}}, 752 presets: []Preset{ 753 { 754 Labels: map[string]string{"foo": "bar"}, 755 VolumeMounts: []kube.VolumeMount{{Name: "baz"}}, 756 }, 757 }, 758 numVolMounts: 1, 759 }, 760 { 761 name: "one of each", 762 jobLabels: map[string]string{"foo": "bar"}, 763 pod: &kube.PodSpec{Containers: []kube.Container{{}}}, 764 presets: []Preset{ 765 { 766 Labels: map[string]string{"foo": "bar"}, 767 Env: []kube.EnvVar{{Name: "baz"}}, 768 VolumeMounts: []kube.VolumeMount{{Name: "baz"}}, 769 Volumes: []kube.Volume{{Name: "qux"}}, 770 }, 771 }, 772 numEnv: 1, 773 numVol: 1, 774 numVolMounts: 1, 775 }, 776 { 777 name: "two vm", 778 jobLabels: map[string]string{"foo": "bar"}, 779 pod: &kube.PodSpec{Containers: []kube.Container{{}}}, 780 presets: []Preset{ 781 { 782 Labels: map[string]string{"foo": "bar"}, 783 VolumeMounts: []kube.VolumeMount{{Name: "baz"}, {Name: "foo"}}, 784 }, 785 }, 786 numVolMounts: 2, 787 }, 788 } 789 for _, tc := range tcs { 790 if err := resolvePresets("foo", tc.jobLabels, tc.pod, tc.presets); err == nil && tc.shouldError { 791 t.Errorf("For test \"%s\": expected error but got none.", tc.name) 792 } else if err != nil && !tc.shouldError { 793 t.Errorf("For test \"%s\": expected no error but got %v.", tc.name, err) 794 } 795 if tc.shouldError { 796 continue 797 } 798 if len(tc.pod.Volumes) != tc.numVol { 799 t.Errorf("For test \"%s\": wrong number of volumes. Got %d, expected %d.", tc.name, len(tc.pod.Volumes), tc.numVol) 800 } 801 for _, c := range tc.pod.Containers { 802 if len(c.VolumeMounts) != tc.numVolMounts { 803 t.Errorf("For test \"%s\": wrong number of volume mounts. Got %d, expected %d.", tc.name, len(c.VolumeMounts), tc.numVolMounts) 804 } 805 if len(c.Env) != tc.numEnv { 806 t.Errorf("For test \"%s\": wrong number of env vars. Got %d, expected %d.", tc.name, len(c.Env), tc.numEnv) 807 } 808 } 809 } 810 }