github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/validator/project_validator_test.go (about) 1 package validator 2 3 import ( 4 "testing" 5 6 "github.com/evergreen-ci/evergreen" 7 "github.com/evergreen-ci/evergreen/db" 8 "github.com/evergreen-ci/evergreen/model" 9 "github.com/evergreen-ci/evergreen/model/distro" 10 "github.com/evergreen-ci/evergreen/model/testutil" 11 _ "github.com/evergreen-ci/evergreen/plugin/config" 12 tu "github.com/evergreen-ci/evergreen/testutil" 13 . "github.com/smartystreets/goconvey/convey" 14 ) 15 16 var projectValidatorConf = tu.TestConfig() 17 18 func init() { 19 db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(projectValidatorConf)) 20 } 21 22 func TestVerifyTaskDependencies(t *testing.T) { 23 Convey("When validating a project's dependencies", t, func() { 24 Convey("if any task has a duplicate dependency, an error should be returned", func() { 25 project := &model.Project{ 26 Tasks: []model.ProjectTask{ 27 { 28 Name: "compile", 29 DependsOn: []model.TaskDependency{}, 30 }, 31 { 32 Name: "testOne", 33 DependsOn: []model.TaskDependency{ 34 {Name: "compile"}, 35 {Name: "compile"}, 36 }, 37 }, 38 }, 39 } 40 So(verifyTaskDependencies(project), ShouldNotResemble, []ValidationError{}) 41 So(len(verifyTaskDependencies(project)), ShouldEqual, 1) 42 }) 43 44 Convey("no error should be returned for dependencies of the same task on two variants", func() { 45 project := &model.Project{ 46 Tasks: []model.ProjectTask{ 47 { 48 Name: "compile", 49 DependsOn: []model.TaskDependency{}, 50 }, 51 { 52 Name: "testOne", 53 DependsOn: []model.TaskDependency{ 54 {Name: "compile", Variant: "v1"}, 55 {Name: "compile", Variant: "v2"}, 56 }, 57 }, 58 }, 59 } 60 So(verifyTaskDependencies(project), ShouldResemble, []ValidationError{}) 61 So(len(verifyTaskDependencies(project)), ShouldEqual, 0) 62 }) 63 64 Convey("if any dependencies have an invalid name field, an error should be returned", func() { 65 66 project := &model.Project{ 67 Tasks: []model.ProjectTask{ 68 { 69 Name: "compile", 70 DependsOn: []model.TaskDependency{}, 71 }, 72 { 73 Name: "testOne", 74 DependsOn: []model.TaskDependency{{Name: "bad"}}, 75 }, 76 }, 77 } 78 So(verifyTaskDependencies(project), ShouldNotResemble, []ValidationError{}) 79 So(len(verifyTaskDependencies(project)), ShouldEqual, 1) 80 }) 81 82 Convey("if any dependencies have an invalid status field, an error should be returned", func() { 83 project := &model.Project{ 84 Tasks: []model.ProjectTask{ 85 { 86 Name: "compile", 87 DependsOn: []model.TaskDependency{}, 88 }, 89 { 90 Name: "testOne", 91 DependsOn: []model.TaskDependency{{Name: "compile", Status: "flibbertyjibbit"}}, 92 }, 93 { 94 Name: "testTwo", 95 DependsOn: []model.TaskDependency{{Name: "compile", Status: evergreen.TaskSucceeded}}, 96 }, 97 }, 98 } 99 So(verifyTaskDependencies(project), ShouldNotResemble, []ValidationError{}) 100 So(len(verifyTaskDependencies(project)), ShouldEqual, 1) 101 }) 102 103 Convey("if the dependencies are well-formed, no error should be returned", func() { 104 project := &model.Project{ 105 Tasks: []model.ProjectTask{ 106 { 107 Name: "compile", 108 DependsOn: []model.TaskDependency{}, 109 }, 110 { 111 Name: "testOne", 112 DependsOn: []model.TaskDependency{{Name: "compile"}}, 113 }, 114 { 115 Name: "testTwo", 116 DependsOn: []model.TaskDependency{{Name: "compile"}}, 117 }, 118 }, 119 } 120 So(verifyTaskDependencies(project), ShouldResemble, []ValidationError{}) 121 }) 122 }) 123 } 124 125 func TestCheckDependencyGraph(t *testing.T) { 126 Convey("When checking a project's dependency graph", t, func() { 127 Convey("cycles in the dependency graph should cause error to be returned", func() { 128 project := &model.Project{ 129 Tasks: []model.ProjectTask{ 130 { 131 Name: "compile", 132 DependsOn: []model.TaskDependency{{Name: "testOne"}}, 133 }, 134 { 135 Name: "testOne", 136 DependsOn: []model.TaskDependency{{Name: "compile"}}, 137 }, 138 { 139 Name: "testTwo", 140 DependsOn: []model.TaskDependency{{Name: "compile"}}, 141 }, 142 }, 143 BuildVariants: []model.BuildVariant{ 144 { 145 Name: "bv", 146 Tasks: []model.BuildVariantTask{ 147 {Name: "compile"}, {Name: "testOne"}, {Name: "testTwo"}}, 148 }, 149 }, 150 } 151 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 152 So(len(checkDependencyGraph(project)), ShouldEqual, 3) 153 }) 154 155 Convey("task wildcard cycles in the dependency graph should return an error", func() { 156 project := &model.Project{ 157 Tasks: []model.ProjectTask{ 158 {Name: "compile"}, 159 { 160 Name: "testOne", 161 DependsOn: []model.TaskDependency{{Name: "compile"}, {Name: "testTwo"}}, 162 }, 163 { 164 Name: "testTwo", 165 DependsOn: []model.TaskDependency{{Name: model.AllDependencies}}, 166 }, 167 }, 168 BuildVariants: []model.BuildVariant{ 169 { 170 Name: "bv", 171 Tasks: []model.BuildVariantTask{ 172 {Name: "compile"}, {Name: "testOne"}, {Name: "testTwo"}}, 173 }, 174 }, 175 } 176 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 177 So(len(checkDependencyGraph(project)), ShouldEqual, 2) 178 }) 179 180 Convey("nonexisting nodes in the dependency graph should return an error", func() { 181 project := &model.Project{ 182 Tasks: []model.ProjectTask{ 183 {Name: "compile"}, 184 { 185 Name: "testOne", 186 DependsOn: []model.TaskDependency{{Name: "compile"}, {Name: "hamSteak"}}, 187 }, 188 }, 189 BuildVariants: []model.BuildVariant{ 190 { 191 Name: "bv", 192 Tasks: []model.BuildVariantTask{ 193 {Name: "compile"}, {Name: "testOne"}}, 194 }, 195 }, 196 } 197 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 198 So(len(checkDependencyGraph(project)), ShouldEqual, 1) 199 }) 200 201 Convey("cross-variant cycles in the dependency graph should return an error", func() { 202 project := &model.Project{ 203 Tasks: []model.ProjectTask{ 204 { 205 Name: "compile", 206 }, 207 { 208 Name: "testOne", 209 DependsOn: []model.TaskDependency{ 210 {Name: "compile"}, 211 {Name: "testSpecial", Variant: "bv2"}, 212 }, 213 }, 214 { 215 Name: "testSpecial", 216 DependsOn: []model.TaskDependency{{Name: "testOne", Variant: "bv1"}}, 217 }, 218 }, 219 BuildVariants: []model.BuildVariant{ 220 { 221 Name: "bv1", 222 Tasks: []model.BuildVariantTask{ 223 {Name: "compile"}, {Name: "testOne"}}, 224 }, 225 { 226 Name: "bv2", 227 Tasks: []model.BuildVariantTask{{Name: "testSpecial"}}}, 228 }, 229 } 230 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 231 So(len(checkDependencyGraph(project)), ShouldEqual, 2) 232 }) 233 234 Convey("cycles/errors from overwriting the dependency graph should return an error", func() { 235 project := &model.Project{ 236 Tasks: []model.ProjectTask{ 237 { 238 Name: "compile", 239 }, 240 { 241 Name: "testOne", 242 DependsOn: []model.TaskDependency{ 243 {Name: "compile"}, 244 }, 245 }, 246 }, 247 BuildVariants: []model.BuildVariant{ 248 { 249 Name: "bv1", 250 Tasks: []model.BuildVariantTask{ 251 {Name: "compile", DependsOn: []model.TaskDependency{{Name: "testOne"}}}, 252 {Name: "testOne"}, 253 }, 254 }, 255 }, 256 } 257 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 258 So(len(checkDependencyGraph(project)), ShouldEqual, 2) 259 260 project.BuildVariants[0].Tasks[0].DependsOn = nil 261 project.BuildVariants[0].Tasks[1].DependsOn = []model.TaskDependency{{Name: "NOPE"}} 262 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 263 So(len(checkDependencyGraph(project)), ShouldEqual, 1) 264 265 project.BuildVariants[0].Tasks[1].DependsOn = []model.TaskDependency{{Name: "compile", Variant: "bvNOPE"}} 266 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 267 So(len(checkDependencyGraph(project)), ShouldEqual, 1) 268 }) 269 270 Convey("variant wildcard cycles in the dependency graph should return an error", func() { 271 project := &model.Project{ 272 Tasks: []model.ProjectTask{ 273 { 274 Name: "compile", 275 }, 276 { 277 Name: "testOne", 278 DependsOn: []model.TaskDependency{ 279 {Name: "compile"}, 280 {Name: "testSpecial", Variant: "bv2"}, 281 }, 282 }, 283 { 284 Name: "testSpecial", 285 DependsOn: []model.TaskDependency{{Name: "testOne", Variant: model.AllVariants}}, 286 }, 287 }, 288 BuildVariants: []model.BuildVariant{ 289 { 290 Name: "bv1", 291 Tasks: []model.BuildVariantTask{ 292 {Name: "compile"}, {Name: "testOne"}}, 293 }, 294 { 295 Name: "bv2", 296 Tasks: []model.BuildVariantTask{ 297 {Name: "testSpecial"}}, 298 }, 299 { 300 Name: "bv3", 301 Tasks: []model.BuildVariantTask{ 302 {Name: "compile"}, {Name: "testOne"}}, 303 }, 304 { 305 Name: "bv4", 306 Tasks: []model.BuildVariantTask{ 307 {Name: "compile"}, {Name: "testOne"}}, 308 }, 309 }, 310 } 311 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 312 So(len(checkDependencyGraph(project)), ShouldEqual, 4) 313 }) 314 315 Convey("cycles in a ** dependency graph should return an error", func() { 316 project := &model.Project{ 317 Tasks: []model.ProjectTask{ 318 {Name: "compile"}, 319 { 320 Name: "testOne", 321 DependsOn: []model.TaskDependency{ 322 {Name: "compile", Variant: model.AllVariants}, 323 {Name: "testTwo"}, 324 }, 325 }, 326 { 327 Name: "testTwo", 328 DependsOn: []model.TaskDependency{ 329 {Name: model.AllDependencies, Variant: model.AllVariants}, 330 }, 331 }, 332 }, 333 BuildVariants: []model.BuildVariant{ 334 { 335 Name: "bv1", 336 Tasks: []model.BuildVariantTask{ 337 {Name: "compile"}, {Name: "testOne"}}, 338 }, 339 { 340 Name: "bv2", 341 Tasks: []model.BuildVariantTask{ 342 {Name: "compile"}, {Name: "testOne"}, {Name: "testTwo"}}, 343 }, 344 }, 345 } 346 347 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 348 So(len(checkDependencyGraph(project)), ShouldEqual, 3) 349 }) 350 351 Convey("if any task has itself as a dependency, an error should be"+ 352 " returned", func() { 353 project := &model.Project{ 354 Tasks: []model.ProjectTask{ 355 { 356 Name: "compile", 357 DependsOn: []model.TaskDependency{}, 358 }, 359 { 360 Name: "testOne", 361 DependsOn: []model.TaskDependency{{Name: "testOne"}}, 362 }, 363 }, 364 BuildVariants: []model.BuildVariant{ 365 { 366 Name: "bv", 367 Tasks: []model.BuildVariantTask{{Name: "compile"}, {Name: "testOne"}}, 368 }, 369 }, 370 } 371 So(checkDependencyGraph(project), ShouldNotResemble, []ValidationError{}) 372 So(len(checkDependencyGraph(project)), ShouldEqual, 1) 373 }) 374 375 Convey("if there is no cycle in the dependency graph, no error should"+ 376 " be returned", func() { 377 project := &model.Project{ 378 Tasks: []model.ProjectTask{ 379 { 380 Name: "compile", 381 DependsOn: []model.TaskDependency{}, 382 }, 383 { 384 Name: "testOne", 385 DependsOn: []model.TaskDependency{{Name: "compile"}}, 386 }, 387 { 388 Name: "testTwo", 389 DependsOn: []model.TaskDependency{{Name: "compile"}}, 390 }, 391 { 392 Name: "push", 393 DependsOn: []model.TaskDependency{ 394 {Name: "testOne"}, 395 {Name: "testTwo"}, 396 }, 397 }, 398 }, 399 BuildVariants: []model.BuildVariant{ 400 { 401 Name: "bv", 402 Tasks: []model.BuildVariantTask{ 403 {Name: "compile"}, {Name: "testOne"}, {Name: "testTwo"}}, 404 }, 405 }, 406 } 407 So(checkDependencyGraph(project), ShouldResemble, []ValidationError{}) 408 }) 409 410 Convey("if there is no cycle in the cross-variant dependency graph, no error should"+ 411 " be returned", func() { 412 project := &model.Project{ 413 Tasks: []model.ProjectTask{ 414 {Name: "compile"}, 415 { 416 Name: "testOne", 417 DependsOn: []model.TaskDependency{ 418 {Name: "compile", Variant: "bv2"}, 419 }, 420 }, 421 { 422 Name: "testSpecial", 423 DependsOn: []model.TaskDependency{ 424 {Name: "compile"}, 425 {Name: "testOne", Variant: "bv1"}}, 426 }, 427 }, 428 BuildVariants: []model.BuildVariant{ 429 { 430 Name: "bv1", 431 Tasks: []model.BuildVariantTask{ 432 {Name: "testOne"}}, 433 }, 434 { 435 Name: "bv2", 436 Tasks: []model.BuildVariantTask{ 437 {Name: "compile"}, {Name: "testSpecial"}}, 438 }, 439 }, 440 } 441 442 So(checkDependencyGraph(project), ShouldResemble, []ValidationError{}) 443 }) 444 445 Convey("if there is no cycle in the * dependency graph, no error should be returned", func() { 446 project := &model.Project{ 447 Tasks: []model.ProjectTask{ 448 {Name: "compile"}, 449 { 450 Name: "testOne", 451 DependsOn: []model.TaskDependency{ 452 {Name: "compile", Variant: model.AllVariants}, 453 }, 454 }, 455 { 456 Name: "testTwo", 457 DependsOn: []model.TaskDependency{{Name: model.AllDependencies}}, 458 }, 459 }, 460 BuildVariants: []model.BuildVariant{ 461 { 462 Name: "bv1", 463 Tasks: []model.BuildVariantTask{ 464 {Name: "compile"}, {Name: "testOne"}}, 465 }, 466 { 467 Name: "bv2", 468 Tasks: []model.BuildVariantTask{ 469 {Name: "compile"}, {Name: "testTwo"}}, 470 }, 471 }, 472 } 473 474 So(checkDependencyGraph(project), ShouldResemble, []ValidationError{}) 475 }) 476 477 Convey("if there is no cycle in the ** dependency graph, no error should be returned", func() { 478 project := &model.Project{ 479 Tasks: []model.ProjectTask{ 480 {Name: "compile"}, 481 { 482 Name: "testOne", 483 DependsOn: []model.TaskDependency{ 484 {Name: "compile", Variant: model.AllVariants}, 485 }, 486 }, 487 { 488 Name: "testTwo", 489 DependsOn: []model.TaskDependency{{Name: model.AllDependencies, Variant: model.AllVariants}}, 490 }, 491 }, 492 BuildVariants: []model.BuildVariant{ 493 { 494 Name: "bv1", 495 Tasks: []model.BuildVariantTask{ 496 {Name: "compile"}, {Name: "testOne"}}, 497 }, 498 { 499 Name: "bv2", 500 Tasks: []model.BuildVariantTask{ 501 {Name: "compile"}, {Name: "testOne"}, {Name: "testTwo"}}, 502 }, 503 }, 504 } 505 506 So(checkDependencyGraph(project), ShouldResemble, []ValidationError{}) 507 }) 508 509 }) 510 } 511 512 func TestVerifyTaskRequirements(t *testing.T) { 513 Convey("When validating a project's requirements", t, func() { 514 Convey("projects with requirements for non-existing tasks should error", func() { 515 p := &model.Project{ 516 Tasks: []model.ProjectTask{ 517 {Name: "1", Requires: []model.TaskRequirement{{Name: "2"}}}, 518 {Name: "X"}, 519 }, 520 BuildVariants: []model.BuildVariant{ 521 {Name: "v1", Tasks: []model.BuildVariantTask{ 522 {Name: "1"}, 523 {Name: "X", Requires: []model.TaskRequirement{{Name: "2"}}}}, 524 }, 525 }, 526 } 527 So(verifyTaskRequirements(p), ShouldNotResemble, []ValidationError{}) 528 So(len(verifyTaskRequirements(p)), ShouldEqual, 2) 529 }) 530 Convey("projects with requirements for non-existing variants should error", func() { 531 p := &model.Project{ 532 Tasks: []model.ProjectTask{ 533 {Name: "1", Requires: []model.TaskRequirement{{Name: "X", Variant: "$"}}}, 534 {Name: "X"}, 535 }, 536 BuildVariants: []model.BuildVariant{ 537 {Name: "v1", Tasks: []model.BuildVariantTask{ 538 {Name: "1"}, 539 {Name: "X", Requires: []model.TaskRequirement{{Name: "1", Variant: "$"}}}}, 540 }, 541 }, 542 } 543 So(verifyTaskRequirements(p), ShouldNotResemble, []ValidationError{}) 544 So(len(verifyTaskRequirements(p)), ShouldEqual, 2) 545 }) 546 Convey("projects with requirements for a normal project configuration should pass", func() { 547 all := []model.BuildVariantTask{{Name: "1"}, {Name: "2"}, {Name: "3"}, 548 {Name: "before"}, {Name: "after"}} 549 beforeDep := []model.TaskDependency{{Name: "before"}} 550 p := &model.Project{ 551 Tasks: []model.ProjectTask{ 552 {Name: "before", Requires: []model.TaskRequirement{{Name: "after"}}}, 553 {Name: "1", DependsOn: beforeDep}, 554 {Name: "2", DependsOn: beforeDep}, 555 {Name: "3", DependsOn: beforeDep}, 556 {Name: "after", DependsOn: []model.TaskDependency{ 557 {Name: "before"}, 558 {Name: "1", PatchOptional: true}, 559 {Name: "2", PatchOptional: true}, 560 {Name: "3", PatchOptional: true}, 561 }}, 562 }, 563 BuildVariants: []model.BuildVariant{ 564 {Name: "v1", Tasks: all}, 565 {Name: "v2", Tasks: all}, 566 }, 567 } 568 So(verifyTaskRequirements(p), ShouldResemble, []ValidationError{}) 569 }) 570 }) 571 } 572 573 func TestValidateBVNames(t *testing.T) { 574 Convey("When validating a project's build variants' names", t, func() { 575 Convey("if any variant has a duplicate entry, an error should be returned", func() { 576 project := &model.Project{ 577 BuildVariants: []model.BuildVariant{ 578 {Name: "linux"}, 579 {Name: "linux"}, 580 }, 581 } 582 validationResults := validateBVNames(project) 583 So(validationResults, ShouldNotResemble, []ValidationError{}) 584 So(len(validationResults), ShouldEqual, 1) 585 So(validationResults[0].Level, ShouldEqual, Error) 586 }) 587 588 Convey("if two variants have the same display name, a warning should be returned, but no errors", func() { 589 project := &model.Project{ 590 BuildVariants: []model.BuildVariant{ 591 {Name: "linux1", DisplayName: "foo"}, 592 {Name: "linux", DisplayName: "foo"}, 593 }, 594 } 595 596 validationResults := validateBVNames(project) 597 numErrors, numWarnings := 0, 0 598 for _, val := range validationResults { 599 if val.Level == Error { 600 numErrors++ 601 } else if val.Level == Warning { 602 numWarnings++ 603 } 604 } 605 606 So(numWarnings, ShouldEqual, 1) 607 So(numErrors, ShouldEqual, 0) 608 So(len(validationResults), ShouldEqual, 1) 609 }) 610 611 Convey("if several buildvariants have duplicate entries, all errors "+ 612 "should be returned", func() { 613 project := &model.Project{ 614 BuildVariants: []model.BuildVariant{ 615 {Name: "linux"}, 616 {Name: "linux"}, 617 {Name: "windows"}, 618 {Name: "windows"}, 619 }, 620 } 621 So(validateBVNames(project), ShouldNotResemble, []ValidationError{}) 622 So(len(validateBVNames(project)), ShouldEqual, 2) 623 }) 624 625 Convey("if no buildvariants have duplicate entries, no error should be"+ 626 " returned", func() { 627 project := &model.Project{ 628 BuildVariants: []model.BuildVariant{ 629 {Name: "linux"}, 630 {Name: "windows"}, 631 }, 632 } 633 So(validateBVNames(project), ShouldResemble, []ValidationError{}) 634 }) 635 }) 636 } 637 638 func TestValidateBVTaskNames(t *testing.T) { 639 Convey("When validating a project's build variant's task names", t, func() { 640 Convey("if any task has a duplicate entry, an error should be"+ 641 " returned", func() { 642 project := &model.Project{ 643 BuildVariants: []model.BuildVariant{ 644 { 645 Name: "linux", 646 Tasks: []model.BuildVariantTask{ 647 {Name: "compile"}, 648 {Name: "compile"}, 649 }, 650 }, 651 }, 652 } 653 So(validateBVTaskNames(project), ShouldNotResemble, []ValidationError{}) 654 So(len(validateBVTaskNames(project)), ShouldEqual, 1) 655 }) 656 657 Convey("if several task have duplicate entries, all errors should be"+ 658 " returned", func() { 659 project := &model.Project{ 660 BuildVariants: []model.BuildVariant{ 661 { 662 Name: "linux", 663 Tasks: []model.BuildVariantTask{ 664 {Name: "compile"}, 665 {Name: "compile"}, 666 {Name: "test"}, 667 {Name: "test"}, 668 }, 669 }, 670 }, 671 } 672 So(validateBVTaskNames(project), ShouldNotResemble, []ValidationError{}) 673 So(len(validateBVTaskNames(project)), ShouldEqual, 2) 674 }) 675 676 Convey("if no tasks have duplicate entries, no error should be"+ 677 " returned", func() { 678 project := &model.Project{ 679 BuildVariants: []model.BuildVariant{ 680 { 681 Name: "linux", 682 Tasks: []model.BuildVariantTask{ 683 {Name: "compile"}, 684 {Name: "test"}, 685 }, 686 }, 687 }, 688 } 689 So(validateBVTaskNames(project), ShouldResemble, []ValidationError{}) 690 }) 691 }) 692 } 693 694 func TestCheckAllDependenciesSpec(t *testing.T) { 695 Convey("When validating a project", t, func() { 696 Convey("if a task references all dependencies, no other dependency "+ 697 "should be specified. If one is, an error should be returned", 698 func() { 699 project := &model.Project{ 700 Tasks: []model.ProjectTask{ 701 { 702 Name: "compile", 703 DependsOn: []model.TaskDependency{ 704 {Name: model.AllDependencies}, 705 {Name: "testOne"}, 706 }, 707 }, 708 }, 709 } 710 So(checkAllDependenciesSpec(project), ShouldNotResemble, 711 []ValidationError{}) 712 So(len(checkAllDependenciesSpec(project)), ShouldEqual, 1) 713 }) 714 Convey("if a task references only all dependencies, no error should "+ 715 "be returned", func() { 716 project := &model.Project{ 717 Tasks: []model.ProjectTask{ 718 { 719 Name: "compile", 720 DependsOn: []model.TaskDependency{ 721 {Name: model.AllDependencies}, 722 }, 723 }, 724 }, 725 } 726 So(checkAllDependenciesSpec(project), ShouldResemble, []ValidationError{}) 727 }) 728 Convey("if a task references any other dependencies, no error should "+ 729 "be returned", func() { 730 project := &model.Project{ 731 Tasks: []model.ProjectTask{ 732 { 733 Name: "compile", 734 DependsOn: []model.TaskDependency{ 735 {Name: "hello"}, 736 }, 737 }, 738 }, 739 } 740 So(checkAllDependenciesSpec(project), ShouldResemble, []ValidationError{}) 741 }) 742 }) 743 } 744 745 func TestValidateProjectTaskNames(t *testing.T) { 746 Convey("When validating a project", t, func() { 747 Convey("ensure any duplicate task names throw an error", func() { 748 project := &model.Project{ 749 Tasks: []model.ProjectTask{ 750 {Name: "compile"}, 751 {Name: "compile"}, 752 }, 753 } 754 So(validateProjectTaskNames(project), ShouldNotResemble, []ValidationError{}) 755 So(len(validateProjectTaskNames(project)), ShouldEqual, 1) 756 }) 757 Convey("ensure unique task names do not throw an error", func() { 758 project := &model.Project{ 759 Tasks: []model.ProjectTask{ 760 {Name: "compile"}, 761 }, 762 } 763 So(validateProjectTaskNames(project), ShouldResemble, []ValidationError{}) 764 }) 765 }) 766 } 767 768 func TestValidateProjectTaskIdsAndTags(t *testing.T) { 769 Convey("When validating a project", t, func() { 770 Convey("ensure bad task tags throw an error", func() { 771 project := &model.Project{ 772 Tasks: []model.ProjectTask{ 773 {Name: "compile", Tags: []string{"a", "!b", "ccc ccc", "d", ".e", "f\tf"}}, 774 }, 775 } 776 So(validateProjectTaskIdsAndTags(project), ShouldNotResemble, []ValidationError{}) 777 So(len(validateProjectTaskIdsAndTags(project)), ShouldEqual, 4) 778 }) 779 Convey("ensure bad task names throw an error", func() { 780 project := &model.Project{ 781 Tasks: []model.ProjectTask{ 782 {Name: "compile"}, 783 {Name: "!compile"}, 784 {Name: ".compile"}, 785 {Name: "Fun!"}, 786 }, 787 } 788 So(validateProjectTaskIdsAndTags(project), ShouldNotResemble, []ValidationError{}) 789 So(len(validateProjectTaskIdsAndTags(project)), ShouldEqual, 2) 790 }) 791 }) 792 } 793 794 func TestCheckTaskCommands(t *testing.T) { 795 Convey("When validating a project", t, func() { 796 Convey("ensure tasks that do not have at least one command throw "+ 797 "an error", func() { 798 project := &model.Project{ 799 Tasks: []model.ProjectTask{ 800 {Name: "compile"}, 801 }, 802 } 803 So(checkTaskCommands(project), ShouldNotResemble, []ValidationError{}) 804 So(len(checkTaskCommands(project)), ShouldEqual, 1) 805 }) 806 Convey("ensure tasks that have at least one command do not throw any errors", 807 func() { 808 project := &model.Project{ 809 Tasks: []model.ProjectTask{ 810 { 811 Name: "compile", 812 Commands: []model.PluginCommandConf{ 813 { 814 Command: "gotest.parse_files", 815 Params: map[string]interface{}{ 816 "files": []interface{}{"test"}, 817 }, 818 }, 819 }, 820 }, 821 }, 822 } 823 So(validateProjectTaskNames(project), ShouldResemble, []ValidationError{}) 824 }) 825 }) 826 } 827 828 func TestEnsureReferentialIntegrity(t *testing.T) { 829 Convey("When validating a project", t, func() { 830 distroIds := []string{"rhel55"} 831 Convey("an error should be thrown if a referenced task for a "+ 832 "buildvariant does not exist", func() { 833 project := &model.Project{ 834 Tasks: []model.ProjectTask{ 835 { 836 Name: "compile", 837 }, 838 }, 839 BuildVariants: []model.BuildVariant{ 840 { 841 Name: "linux", 842 Tasks: []model.BuildVariantTask{ 843 {Name: "test"}, 844 }, 845 }, 846 }, 847 } 848 So(ensureReferentialIntegrity(project, distroIds), ShouldNotResemble, 849 []ValidationError{}) 850 So(len(ensureReferentialIntegrity(project, distroIds)), ShouldEqual, 1) 851 }) 852 853 Convey("no error should be thrown if a referenced task for a "+ 854 "buildvariant does exist", func() { 855 project := &model.Project{ 856 Tasks: []model.ProjectTask{ 857 {Name: "compile"}, 858 }, 859 BuildVariants: []model.BuildVariant{ 860 { 861 Name: "linux", 862 Tasks: []model.BuildVariantTask{ 863 {Name: "compile"}, 864 }, 865 }, 866 }, 867 } 868 So(ensureReferentialIntegrity(project, distroIds), ShouldResemble, 869 []ValidationError{}) 870 }) 871 872 Convey("an error should be thrown if a referenced distro for a "+ 873 "buildvariant does not exist", func() { 874 project := &model.Project{ 875 BuildVariants: []model.BuildVariant{ 876 { 877 Name: "enterprise", 878 RunOn: []string{"hello"}, 879 }, 880 }, 881 } 882 So(ensureReferentialIntegrity(project, distroIds), ShouldNotResemble, 883 []ValidationError{}) 884 So(len(ensureReferentialIntegrity(project, distroIds)), ShouldEqual, 1) 885 }) 886 887 Convey("no error should be thrown if a referenced distro for a "+ 888 "buildvariant does exist", func() { 889 project := &model.Project{ 890 BuildVariants: []model.BuildVariant{ 891 { 892 Name: "enterprise", 893 RunOn: []string{"rhel55"}, 894 }, 895 }, 896 } 897 So(ensureReferentialIntegrity(project, distroIds), ShouldResemble, []ValidationError{}) 898 }) 899 }) 900 } 901 902 func TestValidatePluginCommands(t *testing.T) { 903 Convey("When validating a project", t, func() { 904 Convey("an error should be thrown if a referenced plugin for a task does not exist", func() { 905 project := &model.Project{ 906 Tasks: []model.ProjectTask{ 907 { 908 Name: "compile", 909 Commands: []model.PluginCommandConf{ 910 { 911 Function: "", 912 Command: "a.b", 913 Params: map[string]interface{}{}, 914 }, 915 }, 916 }, 917 }, 918 } 919 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 920 So(len(validatePluginCommands(project)), ShouldEqual, 1) 921 }) 922 Convey("an error should be thrown if a referenced function command is invalid (invalid params)", func() { 923 project := &model.Project{ 924 Functions: map[string]*model.YAMLCommandSet{ 925 "funcOne": { 926 SingleCommand: &model.PluginCommandConf{ 927 Command: "gotest.parse_files", 928 Params: map[string]interface{}{ 929 "blah": []interface{}{"test"}, 930 }, 931 }, 932 }, 933 }, 934 } 935 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 936 So(len(validatePluginCommands(project)), ShouldEqual, 1) 937 }) 938 Convey("no error should be thrown if a function plugin command is valid", func() { 939 project := &model.Project{ 940 Functions: map[string]*model.YAMLCommandSet{ 941 "funcOne": { 942 SingleCommand: &model.PluginCommandConf{ 943 Command: "gotest.parse_files", 944 Params: map[string]interface{}{ 945 "files": []interface{}{"test"}, 946 }, 947 }, 948 }, 949 }, 950 } 951 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 952 }) 953 Convey("an error should be thrown if a function 'a' references "+ 954 "any another function", func() { 955 project := &model.Project{ 956 Functions: map[string]*model.YAMLCommandSet{ 957 "a": { 958 SingleCommand: &model.PluginCommandConf{ 959 Function: "b", 960 Command: "gotest.parse_files", 961 Params: map[string]interface{}{ 962 "files": []interface{}{"test"}, 963 }, 964 }, 965 }, 966 "b": { 967 SingleCommand: &model.PluginCommandConf{ 968 Command: "gotest.parse_files", 969 Params: map[string]interface{}{ 970 "files": []interface{}{"test"}, 971 }, 972 }, 973 }, 974 }, 975 } 976 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 977 So(len(validatePluginCommands(project)), ShouldEqual, 1) 978 }) 979 Convey("errors should be thrown if a function 'a' references "+ 980 "another function, 'b', which that does not exist", func() { 981 project := &model.Project{ 982 Functions: map[string]*model.YAMLCommandSet{ 983 "a": { 984 SingleCommand: &model.PluginCommandConf{ 985 Function: "b", 986 Command: "gotest.parse_files", 987 Params: map[string]interface{}{ 988 "files": []interface{}{"test"}, 989 }, 990 }, 991 }, 992 }, 993 } 994 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 995 So(len(validatePluginCommands(project)), ShouldEqual, 2) 996 }) 997 998 Convey("an error should be thrown if a referenced pre plugin command is invalid", func() { 999 project := &model.Project{ 1000 Pre: &model.YAMLCommandSet{ 1001 MultiCommand: []model.PluginCommandConf{ 1002 { 1003 Command: "gotest.parse_files", 1004 Params: map[string]interface{}{}, 1005 }, 1006 }, 1007 }, 1008 } 1009 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 1010 So(len(validatePluginCommands(project)), ShouldEqual, 1) 1011 }) 1012 Convey("no error should be thrown if a referenced pre plugin command is valid", func() { 1013 project := &model.Project{ 1014 Pre: &model.YAMLCommandSet{ 1015 MultiCommand: []model.PluginCommandConf{ 1016 { 1017 Function: "", 1018 Command: "gotest.parse_files", 1019 Params: map[string]interface{}{ 1020 "files": []interface{}{"test"}, 1021 }, 1022 }, 1023 }, 1024 }, 1025 } 1026 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1027 }) 1028 Convey("an error should be thrown if a referenced post plugin command is invalid", func() { 1029 project := &model.Project{ 1030 Post: &model.YAMLCommandSet{ 1031 MultiCommand: []model.PluginCommandConf{ 1032 { 1033 Function: "", 1034 Command: "gotest.parse_files", 1035 Params: map[string]interface{}{}, 1036 }, 1037 }, 1038 }, 1039 } 1040 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 1041 So(len(validatePluginCommands(project)), ShouldEqual, 1) 1042 }) 1043 Convey("no error should be thrown if a referenced post plugin command is valid", func() { 1044 project := &model.Project{ 1045 Post: &model.YAMLCommandSet{ 1046 MultiCommand: []model.PluginCommandConf{ 1047 { 1048 Function: "", 1049 Command: "gotest.parse_files", 1050 Params: map[string]interface{}{ 1051 "files": []interface{}{"test"}, 1052 }, 1053 }, 1054 }, 1055 }, 1056 } 1057 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1058 }) 1059 Convey("an error should be thrown if a referenced timeout plugin command is invalid", func() { 1060 project := &model.Project{ 1061 Timeout: &model.YAMLCommandSet{ 1062 MultiCommand: []model.PluginCommandConf{ 1063 { 1064 Function: "", 1065 Command: "gotest.parse_files", 1066 Params: map[string]interface{}{}, 1067 }, 1068 }, 1069 }, 1070 } 1071 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 1072 So(len(validatePluginCommands(project)), ShouldEqual, 1) 1073 }) 1074 Convey("no error should be thrown if a referenced timeout plugin command is valid", func() { 1075 project := &model.Project{ 1076 Timeout: &model.YAMLCommandSet{ 1077 MultiCommand: []model.PluginCommandConf{ 1078 { 1079 Function: "", 1080 Command: "gotest.parse_files", 1081 Params: map[string]interface{}{ 1082 "files": []interface{}{"test"}, 1083 }, 1084 }, 1085 }, 1086 }, 1087 } 1088 1089 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1090 }) 1091 Convey("no error should be thrown if a referenced plugin for a task does exist", func() { 1092 project := &model.Project{ 1093 Tasks: []model.ProjectTask{ 1094 { 1095 Name: "compile", 1096 Commands: []model.PluginCommandConf{ 1097 { 1098 Function: "", 1099 Command: "archive.targz_pack", 1100 Params: map[string]interface{}{ 1101 "target": "tgz", 1102 "source_dir": "src", 1103 "include": []string{":"}, 1104 }, 1105 }, 1106 }, 1107 }, 1108 }, 1109 } 1110 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1111 }) 1112 Convey("no error should be thrown if a referenced plugin that exists contains unneeded parameters", func() { 1113 project := &model.Project{ 1114 Tasks: []model.ProjectTask{ 1115 { 1116 Name: "compile", 1117 Commands: []model.PluginCommandConf{ 1118 { 1119 Function: "", 1120 Command: "archive.targz_pack", 1121 Params: map[string]interface{}{ 1122 "target": "tgz", 1123 "source_dir": "src", 1124 "include": []string{":"}, 1125 "extraneous": "G", 1126 }, 1127 }, 1128 }, 1129 }, 1130 }, 1131 } 1132 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1133 }) 1134 Convey("an error should be thrown if a referenced plugin contains invalid parameters", func() { 1135 params := map[string]interface{}{ 1136 "aws_key": "key", 1137 "aws_secret": "sec", 1138 "s3_copy_files": []interface{}{ 1139 map[string]interface{}{ 1140 "source": map[string]interface{}{ 1141 "bucket": "long3nough", 1142 "path": "fghij", 1143 }, 1144 "destination": map[string]interface{}{ 1145 "bucket": "..long-but-invalid", 1146 "path": "fghij", 1147 }, 1148 }, 1149 }, 1150 } 1151 project := &model.Project{ 1152 Tasks: []model.ProjectTask{ 1153 { 1154 Name: "compile", 1155 Commands: []model.PluginCommandConf{ 1156 { 1157 Function: "", 1158 Command: "s3Copy.copy", 1159 Params: params, 1160 }, 1161 }, 1162 }, 1163 }, 1164 } 1165 So(validatePluginCommands(project), ShouldNotResemble, []ValidationError{}) 1166 So(len(validatePluginCommands(project)), ShouldEqual, 1) 1167 }) 1168 Convey("no error should be thrown if a referenced plugin that "+ 1169 "exists contains params that appear invalid but are in expansions", 1170 func() { 1171 params := map[string]interface{}{ 1172 "aws_key": "key", 1173 "aws_secret": "sec", 1174 "s3_copy_files": []interface{}{ 1175 map[string]interface{}{ 1176 "source": map[string]interface{}{ 1177 "bucket": "long3nough", 1178 "path": "fghij", 1179 }, 1180 "destination": map[string]interface{}{ 1181 "bucket": "${..longButInvalid}", 1182 "path": "fghij", 1183 }, 1184 }, 1185 }, 1186 } 1187 project := &model.Project{ 1188 Tasks: []model.ProjectTask{ 1189 { 1190 Name: "compile", 1191 Commands: []model.PluginCommandConf{ 1192 { 1193 Function: "", 1194 Command: "s3Copy.copy", 1195 Params: params, 1196 }, 1197 }, 1198 }, 1199 }, 1200 } 1201 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1202 }) 1203 Convey("no error should be thrown if a referenced plugin contains all "+ 1204 "the necessary and valid parameters", func() { 1205 params := map[string]interface{}{ 1206 "aws_key": "key", 1207 "aws_secret": "sec", 1208 "s3_copy_files": []interface{}{ 1209 map[string]interface{}{ 1210 "source": map[string]interface{}{ 1211 "bucket": "abcde", 1212 "path": "fghij", 1213 }, 1214 "destination": map[string]interface{}{ 1215 "bucket": "abcde", 1216 "path": "fghij", 1217 }, 1218 }, 1219 }, 1220 } 1221 project := &model.Project{ 1222 Tasks: []model.ProjectTask{ 1223 { 1224 Name: "compile", 1225 Commands: []model.PluginCommandConf{ 1226 { 1227 Function: "", 1228 Command: "s3Copy.copy", 1229 Params: params, 1230 }, 1231 }, 1232 }, 1233 }, 1234 } 1235 So(validatePluginCommands(project), ShouldResemble, []ValidationError{}) 1236 }) 1237 }) 1238 } 1239 1240 func TestCheckProjectSyntax(t *testing.T) { 1241 Convey("When validating a project's syntax", t, func() { 1242 Convey("if the project passes all of the validation funcs, no errors"+ 1243 " should be returned", func() { 1244 distros := []distro.Distro{ 1245 {Id: "test-distro-one"}, 1246 {Id: "test-distro-two"}, 1247 } 1248 1249 err := testutil.CreateTestLocalConfig(projectValidatorConf, "project_test", "") 1250 So(err, ShouldBeNil) 1251 1252 projectRef, err := model.FindOneProjectRef("project_test") 1253 So(err, ShouldBeNil) 1254 1255 for _, d := range distros { 1256 So(d.Insert(), ShouldBeNil) 1257 } 1258 1259 project, err := model.FindProject("", projectRef) 1260 So(err, ShouldBeNil) 1261 verrs, err := CheckProjectSyntax(project) 1262 So(err, ShouldBeNil) 1263 So(verrs, ShouldResemble, []ValidationError{}) 1264 }) 1265 1266 Reset(func() { 1267 So(db.Clear(distro.Collection), ShouldBeNil) 1268 }) 1269 }) 1270 } 1271 1272 func TestCheckProjectSemantics(t *testing.T) { 1273 Convey("When validating a project's semantics", t, func() { 1274 Convey("if the project passes all of the validation funcs, no errors"+ 1275 " should be returned", func() { 1276 distros := []distro.Distro{ 1277 {Id: "test-distro-one"}, 1278 {Id: "test-distro-two"}, 1279 } 1280 1281 for _, d := range distros { 1282 So(d.Insert(), ShouldBeNil) 1283 } 1284 1285 projectRef := &model.ProjectRef{ 1286 Identifier: "project_test", 1287 LocalConfig: "test: testing", 1288 } 1289 1290 project, err := model.FindProject("", projectRef) 1291 So(err, ShouldBeNil) 1292 So(CheckProjectSemantics(project), ShouldResemble, []ValidationError{}) 1293 }) 1294 1295 Reset(func() { 1296 So(db.Clear(distro.Collection), ShouldBeNil) 1297 }) 1298 }) 1299 } 1300 1301 func TestEnsureHasNecessaryProjectFields(t *testing.T) { 1302 Convey("When ensuring necessary project fields are set, ensure that", t, func() { 1303 Convey("projects validate all necessary fields exist", func() { 1304 Convey("an error should be thrown if the batch_time field is "+ 1305 "set to a negative value", func() { 1306 project := &model.Project{ 1307 Enabled: true, 1308 Identifier: "identifier", 1309 Owner: "owner", 1310 Repo: "repo", 1311 Branch: "branch", 1312 DisplayName: "test", 1313 RepoKind: "github", 1314 BatchTime: -10, 1315 } 1316 So(ensureHasNecessaryProjectFields(project), 1317 ShouldNotResemble, []ValidationError{}) 1318 So(len(ensureHasNecessaryProjectFields(project)), 1319 ShouldEqual, 1) 1320 }) 1321 Convey("an error should be thrown if the command type "+ 1322 "field is invalid", func() { 1323 project := &model.Project{ 1324 BatchTime: 10, 1325 CommandType: "random", 1326 } 1327 So(ensureHasNecessaryProjectFields(project), 1328 ShouldNotResemble, []ValidationError{}) 1329 So(len(ensureHasNecessaryProjectFields(project)), 1330 ShouldEqual, 1) 1331 }) 1332 }) 1333 }) 1334 } 1335 1336 func TestEnsureHasNecessaryBVFields(t *testing.T) { 1337 Convey("When ensuring necessary buildvariant fields are set, ensure that", t, func() { 1338 Convey("an error is thrown if no build variants exist", func() { 1339 project := &model.Project{ 1340 Identifier: "test", 1341 } 1342 So(ensureHasNecessaryBVFields(project), 1343 ShouldNotResemble, []ValidationError{}) 1344 So(len(ensureHasNecessaryBVFields(project)), 1345 ShouldEqual, 1) 1346 }) 1347 Convey("buildvariants with none of the necessary fields set throw errors", func() { 1348 project := &model.Project{ 1349 Identifier: "test", 1350 BuildVariants: []model.BuildVariant{{}}, 1351 } 1352 So(ensureHasNecessaryBVFields(project), 1353 ShouldNotResemble, []ValidationError{}) 1354 So(len(ensureHasNecessaryBVFields(project)), 1355 ShouldEqual, 2) 1356 }) 1357 Convey("an error is thrown if the buildvariant does not have a "+ 1358 "name field set", func() { 1359 project := &model.Project{ 1360 Identifier: "projectId", 1361 BuildVariants: []model.BuildVariant{ 1362 { 1363 RunOn: []string{"mongo"}, 1364 Tasks: []model.BuildVariantTask{{Name: "db"}}, 1365 }, 1366 }, 1367 } 1368 So(ensureHasNecessaryBVFields(project), 1369 ShouldNotResemble, []ValidationError{}) 1370 So(len(ensureHasNecessaryBVFields(project)), 1371 ShouldEqual, 1) 1372 }) 1373 Convey("an error is thrown if the buildvariant does not have any tasks set", func() { 1374 project := &model.Project{ 1375 Identifier: "projectId", 1376 BuildVariants: []model.BuildVariant{ 1377 { 1378 Name: "postal", 1379 RunOn: []string{"service"}, 1380 }, 1381 }, 1382 } 1383 So(ensureHasNecessaryBVFields(project), 1384 ShouldNotResemble, []ValidationError{}) 1385 So(len(ensureHasNecessaryBVFields(project)), 1386 ShouldEqual, 1) 1387 }) 1388 Convey("no error is thrown if the buildvariant has a run_on field set", func() { 1389 project := &model.Project{ 1390 Identifier: "projectId", 1391 BuildVariants: []model.BuildVariant{ 1392 { 1393 Name: "import", 1394 RunOn: []string{"export"}, 1395 Tasks: []model.BuildVariantTask{{Name: "db"}}, 1396 }, 1397 }, 1398 } 1399 So(ensureHasNecessaryBVFields(project), 1400 ShouldResemble, []ValidationError{}) 1401 }) 1402 Convey("an error should be thrown if the buildvariant has no "+ 1403 "run_on field and at least one task has no distro field "+ 1404 "specified", func() { 1405 project := &model.Project{ 1406 Identifier: "projectId", 1407 BuildVariants: []model.BuildVariant{ 1408 { 1409 Name: "import", 1410 Tasks: []model.BuildVariantTask{{Name: "db"}}, 1411 }, 1412 }, 1413 } 1414 So(ensureHasNecessaryBVFields(project), 1415 ShouldNotResemble, []ValidationError{}) 1416 So(len(ensureHasNecessaryBVFields(project)), 1417 ShouldEqual, 1) 1418 }) 1419 Convey("no error should be thrown if the buildvariant does not "+ 1420 "have a run_on field specified but all tasks within it have a "+ 1421 "distro field specified", func() { 1422 project := &model.Project{ 1423 Identifier: "projectId", 1424 BuildVariants: []model.BuildVariant{ 1425 { 1426 Name: "import", 1427 Tasks: []model.BuildVariantTask{ 1428 { 1429 Name: "silhouettes", 1430 Distros: []string{ 1431 "echoes", 1432 }, 1433 }, 1434 }, 1435 }, 1436 }, 1437 } 1438 So(ensureHasNecessaryBVFields(project), 1439 ShouldResemble, []ValidationError{}) 1440 }) 1441 }) 1442 }