github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/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 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "regexp" 25 "strings" 26 "testing" 27 ) 28 29 var podRe = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`) 30 31 const ( 32 testThis = "/test all" 33 retestBody = "/test all [submit-queue is verifying that this PR is safe to merge]" 34 ) 35 36 type JSONJob struct { 37 Scenario string `json:"scenario"` 38 Args []string `json:"args"` 39 } 40 41 // Consistent but meaningless order. 42 func flattenJobs(jobs []Presubmit) []Presubmit { 43 ret := jobs 44 for _, job := range jobs { 45 if len(job.RunAfterSuccess) > 0 { 46 ret = append(ret, flattenJobs(job.RunAfterSuccess)...) 47 } 48 } 49 return ret 50 } 51 52 // Returns if two brancher has overlapping branches 53 func CheckOverlapBrancher(b1, b2 Brancher) bool { 54 if b1.RunsAgainstAllBranch() || b2.RunsAgainstAllBranch() { 55 return true 56 } 57 58 for _, run1 := range b1.Branches { 59 if b2.RunsAgainstBranch(run1) { 60 return true 61 } 62 } 63 64 for _, run2 := range b2.Branches { 65 if b1.RunsAgainstBranch(run2) { 66 return true 67 } 68 } 69 70 return false 71 } 72 73 // TODO(spxtr): Some of this is generic prowjob stuff and some of this is k8s- 74 // specific. Figure out which is which and split this up. 75 func TestPresubmits(t *testing.T) { 76 c, err := Load("../config.yaml") 77 if err != nil { 78 t.Fatalf("Could not load config: %v", err) 79 } 80 if len(c.Presubmits) == 0 { 81 t.Fatalf("No jobs found in presubmit.yaml.") 82 } 83 b, err := ioutil.ReadFile("../../jobs/config.json") 84 if err != nil { 85 t.Fatalf("Could not load jobs/config.json: %v", err) 86 } 87 var bootstrapConfig map[string]JSONJob 88 json.Unmarshal(b, &bootstrapConfig) 89 for _, rootJobs := range c.Presubmits { 90 jobs := flattenJobs(rootJobs) 91 for i, job := range jobs { 92 if job.Name == "" { 93 t.Errorf("Job %v needs a name.", job) 94 continue 95 } 96 if job.Context == "" { 97 t.Errorf("Job %s needs a context.", job.Name) 98 } 99 if job.RerunCommand == "" || job.Trigger == "" { 100 t.Errorf("Job %s needs a trigger and a rerun command.", job.Name) 101 continue 102 } 103 // Check that the merge bot will run AlwaysRun jobs, otherwise it 104 // will attempt to rerun forever. 105 if job.AlwaysRun && !job.re.MatchString(testThis) { 106 t.Errorf("AlwaysRun job %s: \"%s\" does not match regex \"%v\".", job.Name, testThis, job.Trigger) 107 } 108 if job.AlwaysRun && !job.re.MatchString(retestBody) { 109 t.Errorf("AlwaysRun job %s: \"%s\" does not match regex \"%v\".", job.Name, retestBody, job.Trigger) 110 } 111 // Check that the merge bot will not run Non-AlwaysRun jobs 112 if !job.AlwaysRun && job.re.MatchString(testThis) { 113 t.Errorf("Non-AlwaysRun job %s: \"%s\" matches regex \"%v\".", job.Name, testThis, job.Trigger) 114 } 115 if !job.AlwaysRun && job.re.MatchString(retestBody) { 116 t.Errorf("Non-AlwaysRun job %s: \"%s\" matches regex \"%v\".", job.Name, retestBody, job.Trigger) 117 } 118 119 if len(job.Brancher.Branches) > 0 && len(job.Brancher.SkipBranches) > 0 { 120 t.Errorf("Job %s : Cannot have both branches and skip_branches set", job.Name) 121 } 122 // Next check that the rerun command doesn't run any other jobs. 123 for j, job2 := range jobs[i+1:] { 124 if job.Name == job2.Name { 125 // Make sure max_concurrency are the same 126 if job.MaxConcurrency != job2.MaxConcurrency { 127 t.Errorf("Jobs %s share same name but has different max_concurrency", job.Name) 128 } 129 // Make sure branches are not overlapping 130 if CheckOverlapBrancher(job.Brancher, job2.Brancher) { 131 t.Errorf("Two jobs have the same name: %s, and has conflicted branches", job.Name) 132 } 133 } else { 134 if job.Context == job2.Context { 135 t.Errorf("Jobs %s and %s have the same context: %s", job.Name, job2.Name, job.Context) 136 } 137 if job2.re.MatchString(job.RerunCommand) { 138 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) 139 } 140 } 141 } 142 var scenario string 143 job.Name = strings.Replace(job.Name, "pull-security-kubernetes", "pull-kubernetes", 1) 144 if j, present := bootstrapConfig[job.Name]; present { 145 scenario = fmt.Sprintf("scenarios/%s.py", j.Scenario) 146 } else { 147 scenario = fmt.Sprintf("jobs/%s.sh", job.Name) 148 } 149 150 // Ensure that jobs have a shell script of the same name. 151 if s, err := os.Stat(fmt.Sprintf("../../%s", scenario)); err != nil { 152 t.Errorf("Cannot find test-infra/%s for %s", scenario, job.Name) 153 } else { 154 if s.Mode()&0111 == 0 { 155 t.Errorf("Not executable: test-infra/%s (%o)", scenario, s.Mode()&0777) 156 } 157 if s.Mode()&0444 == 0 { 158 t.Errorf("Not readable: test-infra/%s (%o)", scenario, s.Mode()&0777) 159 } 160 } 161 } 162 } 163 } 164 165 func TestCommentBodyMatches(t *testing.T) { 166 var testcases = []struct { 167 repo string 168 body string 169 expectedJobs []string 170 }{ 171 { 172 "org/repo", 173 "this is a random comment", 174 []string{}, 175 }, 176 { 177 "org/repo", 178 "/ok-to-test", 179 []string{"gce", "unit"}, 180 }, 181 { 182 "org/repo", 183 "/test all", 184 []string{"gce", "unit", "gke"}, 185 }, 186 { 187 "org/repo", 188 "/test unit", 189 []string{"unit"}, 190 }, 191 { 192 "org/repo", 193 "/test federation", 194 []string{"federation"}, 195 }, 196 { 197 "org/repo2", 198 "/test all", 199 []string{"cadveapster", "after-cadveapster", "after-after-cadveapster"}, 200 }, 201 { 202 "org/repo2", 203 "/test really", 204 []string{"after-cadveapster"}, 205 }, 206 { 207 "org/repo2", 208 "/test again really", 209 []string{"after-after-cadveapster"}, 210 }, 211 { 212 "org/repo3", 213 "/test all", 214 []string{}, 215 }, 216 } 217 c := &Config{ 218 Presubmits: map[string][]Presubmit{ 219 "org/repo": { 220 { 221 Name: "gce", 222 re: regexp.MustCompile(`/test (gce|all)`), 223 AlwaysRun: true, 224 }, 225 { 226 Name: "unit", 227 re: regexp.MustCompile(`/test (unit|all)`), 228 AlwaysRun: true, 229 }, 230 { 231 Name: "gke", 232 re: regexp.MustCompile(`/test (gke|all)`), 233 AlwaysRun: false, 234 }, 235 { 236 Name: "federation", 237 re: regexp.MustCompile(`/test federation`), 238 AlwaysRun: false, 239 }, 240 }, 241 "org/repo2": { 242 { 243 Name: "cadveapster", 244 re: regexp.MustCompile(`/test all`), 245 AlwaysRun: true, 246 RunAfterSuccess: []Presubmit{ 247 { 248 Name: "after-cadveapster", 249 re: regexp.MustCompile(`/test (really|all)`), 250 AlwaysRun: true, 251 RunAfterSuccess: []Presubmit{ 252 { 253 Name: "after-after-cadveapster", 254 re: regexp.MustCompile(`/test (again really|all)`), 255 AlwaysRun: true, 256 }, 257 }, 258 }, 259 { 260 Name: "another-after-cadveapster", 261 re: regexp.MustCompile(`@k8s-bot dont test this`), 262 AlwaysRun: true, 263 }, 264 }, 265 }, 266 }, 267 }, 268 } 269 for _, tc := range testcases { 270 actualJobs := c.MatchingPresubmits(tc.repo, tc.body, regexp.MustCompile(`/ok-to-test`)) 271 match := true 272 if len(actualJobs) != len(tc.expectedJobs) { 273 match = false 274 } else { 275 for _, actualJob := range actualJobs { 276 found := false 277 for _, expectedJob := range tc.expectedJobs { 278 if expectedJob == actualJob.Name { 279 found = true 280 break 281 } 282 } 283 if !found { 284 match = false 285 break 286 } 287 } 288 } 289 if !match { 290 t.Errorf("Wrong jobs for body %s. Got %v, expected %v.", tc.body, actualJobs, tc.expectedJobs) 291 } 292 } 293 } 294 295 func TestRetestPresubmits(t *testing.T) { 296 var testcases = []struct { 297 skipContexts map[string]bool 298 runContexts map[string]bool 299 expectedContexts []string 300 }{ 301 { 302 map[string]bool{}, 303 map[string]bool{}, 304 []string{"gce", "unit"}, 305 }, 306 { 307 map[string]bool{"gce": true}, 308 map[string]bool{}, 309 []string{"unit"}, 310 }, 311 { 312 map[string]bool{}, 313 map[string]bool{"federation": true, "nonexistent": true}, 314 []string{"gce", "unit", "federation"}, 315 }, 316 { 317 map[string]bool{}, 318 map[string]bool{"gke": true}, 319 []string{"gce", "unit", "gke"}, 320 }, 321 { 322 map[string]bool{"gce": true}, 323 map[string]bool{"gce": true}, // should never happen 324 []string{"unit"}, 325 }, 326 } 327 c := &Config{ 328 Presubmits: map[string][]Presubmit{ 329 "org/repo": { 330 { 331 Context: "gce", 332 AlwaysRun: true, 333 }, 334 { 335 Context: "unit", 336 AlwaysRun: true, 337 }, 338 { 339 Context: "gke", 340 AlwaysRun: false, 341 }, 342 { 343 Context: "federation", 344 AlwaysRun: false, 345 }, 346 }, 347 "org/repo2": { 348 { 349 Context: "shouldneverrun", 350 AlwaysRun: true, 351 }, 352 }, 353 }, 354 } 355 for _, tc := range testcases { 356 actualContexts := c.RetestPresubmits("org/repo", tc.skipContexts, tc.runContexts) 357 match := true 358 if len(actualContexts) != len(tc.expectedContexts) { 359 match = false 360 } else { 361 for _, actualJob := range actualContexts { 362 found := false 363 for _, expectedContext := range tc.expectedContexts { 364 if expectedContext == actualJob.Context { 365 found = true 366 break 367 } 368 } 369 if !found { 370 match = false 371 break 372 } 373 } 374 } 375 if !match { 376 t.Errorf("Wrong contexts for skip %v run %v. Got %v, expected %v.", tc.runContexts, tc.skipContexts, actualContexts, tc.expectedContexts) 377 } 378 } 379 380 } 381 382 func TestConditionalPresubmits(t *testing.T) { 383 presubmits := []Presubmit{ 384 { 385 Name: "cross build", 386 RunIfChanged: `(Makefile|\.sh|_(windows|linux|osx|unknown)(_test)?\.go)$`, 387 }, 388 } 389 SetRegexes(presubmits) 390 ps := presubmits[0] 391 var testcases = []struct { 392 changes []string 393 expected bool 394 }{ 395 {[]string{"some random file"}, false}, 396 {[]string{"./pkg/util/rlimit/rlimit_linux.go"}, true}, 397 {[]string{"./pkg/util/rlimit/rlimit_unknown_test.go"}, true}, 398 {[]string{"build.sh"}, true}, 399 {[]string{"build.shoo"}, false}, 400 {[]string{"Makefile"}, true}, 401 } 402 for _, tc := range testcases { 403 actual := ps.RunsAgainstChanges(tc.changes) 404 if actual != tc.expected { 405 t.Errorf("wrong RunsAgainstChanges(%#v) result. Got %v, expected %v", tc.changes, actual, tc.expected) 406 } 407 } 408 } 409 410 func TestListPresubmit(t *testing.T) { 411 c := &Config{ 412 Presubmits: map[string][]Presubmit{ 413 "r1": { 414 { 415 Name: "a", 416 RunAfterSuccess: []Presubmit{ 417 {Name: "aa"}, 418 {Name: "ab"}, 419 }, 420 }, 421 {Name: "b"}, 422 }, 423 "r2": { 424 { 425 Name: "c", 426 RunAfterSuccess: []Presubmit{ 427 {Name: "ca"}, 428 {Name: "cb"}, 429 }, 430 }, 431 {Name: "d"}, 432 }, 433 }, 434 Postsubmits: map[string][]Postsubmit{ 435 "r1": {{Name: "e"}}, 436 }, 437 Periodics: []Periodic{ 438 {Name: "f"}, 439 }, 440 } 441 442 var testcases = []struct { 443 name string 444 expected []string 445 repos []string 446 }{ 447 { 448 "all presubmits", 449 []string{"a", "aa", "ab", "b", "c", "ca", "cb", "d"}, 450 []string{}, 451 }, 452 { 453 "r2 presubmits", 454 []string{"c", "ca", "cb", "d"}, 455 []string{"r2"}, 456 }, 457 } 458 459 for _, tc := range testcases { 460 actual := c.AllPresubmits(tc.repos) 461 if len(actual) != len(tc.expected) { 462 t.Fatalf("test %s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected) 463 } 464 for _, j1 := range tc.expected { 465 found := false 466 for _, j2 := range actual { 467 if j1 == j2.Name { 468 found = true 469 break 470 } 471 } 472 if !found { 473 t.Errorf("test %s - Did not find job %s in output", tc.name, j1) 474 } 475 } 476 } 477 } 478 479 func TestListPostsubmit(t *testing.T) { 480 c := &Config{ 481 Presubmits: map[string][]Presubmit{ 482 "r1": {{Name: "a"}}, 483 }, 484 Postsubmits: map[string][]Postsubmit{ 485 "r1": { 486 { 487 Name: "c", 488 RunAfterSuccess: []Postsubmit{ 489 {Name: "ca"}, 490 {Name: "cb"}, 491 }, 492 }, 493 {Name: "d"}, 494 }, 495 "r2": {{Name: "e"}}, 496 }, 497 Periodics: []Periodic{ 498 {Name: "f"}, 499 }, 500 } 501 502 var testcases = []struct { 503 name string 504 expected []string 505 repos []string 506 }{ 507 { 508 "all postsubmits", 509 []string{"c", "ca", "cb", "d", "e"}, 510 []string{}, 511 }, 512 { 513 "r2 presubmits", 514 []string{"e"}, 515 []string{"r2"}, 516 }, 517 } 518 519 for _, tc := range testcases { 520 actual := c.AllPostsubmits(tc.repos) 521 if len(actual) != len(tc.expected) { 522 t.Fatalf("%s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected) 523 } 524 for _, j1 := range tc.expected { 525 found := false 526 for _, j2 := range actual { 527 if j1 == j2.Name { 528 found = true 529 break 530 } 531 } 532 if !found { 533 t.Errorf("Did not find job %s in output", j1) 534 } 535 } 536 } 537 } 538 539 func TestListPeriodic(t *testing.T) { 540 c := &Config{ 541 Presubmits: map[string][]Presubmit{ 542 "r1": {{Name: "a"}}, 543 }, 544 Postsubmits: map[string][]Postsubmit{ 545 "r1": {{Name: "b"}}, 546 }, 547 Periodics: []Periodic{ 548 { 549 Name: "c", 550 RunAfterSuccess: []Periodic{ 551 {Name: "ca"}, 552 {Name: "cb"}, 553 }, 554 }, 555 {Name: "d"}, 556 }, 557 } 558 559 expected := []string{"c", "ca", "cb", "d"} 560 actual := c.AllPeriodics() 561 if len(actual) != len(expected) { 562 t.Fatalf("Wrong number of jobs. Got %v, expected %v", actual, expected) 563 } 564 for _, j1 := range expected { 565 found := false 566 for _, j2 := range actual { 567 if j1 == j2.Name { 568 found = true 569 break 570 } 571 } 572 if !found { 573 t.Errorf("Did not find job %s in output", j1) 574 } 575 } 576 } 577 578 func TestRunAgainstBranch(t *testing.T) { 579 jobs := []Presubmit{ 580 { 581 Name: "a", 582 Brancher: Brancher{SkipBranches: []string{"s"}}, 583 }, 584 { 585 Name: "b", 586 Brancher: Brancher{Branches: []string{"r"}}, 587 }, 588 { 589 Name: "c", 590 Brancher: Brancher{ 591 SkipBranches: []string{"s"}, 592 Branches: []string{"r"}, 593 }, 594 }, 595 { 596 Name: "d", 597 Brancher: Brancher{ 598 SkipBranches: []string{"s"}, 599 Branches: []string{"s", "r"}, 600 }, 601 }, 602 { 603 Name: "default", 604 }, 605 } 606 607 for _, job := range jobs { 608 if job.Name == "default" { 609 if !job.RunsAgainstBranch("s") { 610 t.Errorf("Job %s should run branch s", job.Name) 611 } 612 } else if job.RunsAgainstBranch("s") { 613 t.Errorf("Job %s should not run branch s", job.Name) 614 } 615 616 if !job.RunsAgainstBranch("r") { 617 t.Errorf("Job %s should run branch r", job.Name) 618 } 619 } 620 } 621 622 func TestValidPodNames(t *testing.T) { 623 c, err := Load("../config.yaml") 624 if err != nil { 625 t.Fatalf("Could not load config: %v", err) 626 } 627 for _, j := range c.AllPresubmits([]string{}) { 628 if !podRe.MatchString(j.Name) { 629 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 630 } 631 } 632 for _, j := range c.AllPostsubmits([]string{}) { 633 if !podRe.MatchString(j.Name) { 634 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 635 } 636 } 637 for _, j := range c.AllPeriodics() { 638 if !podRe.MatchString(j.Name) { 639 t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String()) 640 } 641 } 642 } 643 644 func TestNoDuplicateJobs(t *testing.T) { 645 c, err := Load("../config.yaml") 646 if err != nil { 647 t.Fatalf("Could not load config: %v", err) 648 } 649 650 // Presubmit test is covered under TestPresubmits() above 651 652 allJobs := make(map[string]bool) 653 for _, j := range c.AllPostsubmits([]string{}) { 654 if allJobs[j.Name] { 655 t.Errorf("Found duplicate job in postsubmit: %s.", j.Name) 656 } 657 allJobs[j.Name] = true 658 } 659 660 allJobs = make(map[string]bool) 661 for _, j := range c.AllPeriodics() { 662 if allJobs[j.Name] { 663 t.Errorf("Found duplicate job in periodic %s.", j.Name) 664 } 665 allJobs[j.Name] = true 666 } 667 }