github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/project_parser_test.go (about) 1 package model 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 8 "github.com/evergreen-ci/evergreen/util" 9 . "github.com/smartystreets/goconvey/convey" 10 ) 11 12 // ShouldContainResembling tests whether a slice contains an element that DeepEquals 13 // the expected input. 14 func ShouldContainResembling(actual interface{}, expected ...interface{}) string { 15 if len(expected) != 1 { 16 return "ShouldContainResembling takes 1 argument" 17 } 18 if !util.SliceContains(actual, expected[0]) { 19 return fmt.Sprintf("%#v does not contain %#v", actual, expected[0]) 20 } 21 return "" 22 } 23 24 func TestCreateIntermediateProjectDependencies(t *testing.T) { 25 Convey("Testing different project files", t, func() { 26 Convey("a simple project file should parse", func() { 27 simple := ` 28 tasks: 29 - name: "compile" 30 - name: task0 31 - name: task1 32 patchable: false 33 tags: ["tag1", "tag2"] 34 depends_on: 35 - compile 36 - name: "task0" 37 status: "failed" 38 patch_optional: true 39 ` 40 p, errs := createIntermediateProject([]byte(simple)) 41 So(p, ShouldNotBeNil) 42 So(len(errs), ShouldEqual, 0) 43 So(p.Tasks[2].DependsOn[0].Name, ShouldEqual, "compile") 44 So(p.Tasks[2].DependsOn[0].PatchOptional, ShouldEqual, false) 45 So(p.Tasks[2].DependsOn[1].Name, ShouldEqual, "task0") 46 So(p.Tasks[2].DependsOn[1].Status, ShouldEqual, "failed") 47 So(p.Tasks[2].DependsOn[1].PatchOptional, ShouldEqual, true) 48 }) 49 Convey("a file with a single dependency should parse", func() { 50 single := ` 51 tasks: 52 - name: "compile" 53 - name: task0 54 - name: task1 55 depends_on: task0 56 ` 57 p, errs := createIntermediateProject([]byte(single)) 58 So(p, ShouldNotBeNil) 59 So(len(errs), ShouldEqual, 0) 60 So(p.Tasks[2].DependsOn[0].Name, ShouldEqual, "task0") 61 }) 62 Convey("a file with a nameless dependency should error", func() { 63 Convey("with a single dep", func() { 64 nameless := ` 65 tasks: 66 - name: "compile" 67 depends_on: "" 68 ` 69 p, errs := createIntermediateProject([]byte(nameless)) 70 So(p, ShouldBeNil) 71 So(len(errs), ShouldEqual, 1) 72 }) 73 Convey("or multiple", func() { 74 nameless := ` 75 tasks: 76 - name: "compile" 77 depends_on: 78 - name: "task1" 79 - status: "failed" #this has no task attached 80 ` 81 p, errs := createIntermediateProject([]byte(nameless)) 82 So(p, ShouldBeNil) 83 So(len(errs), ShouldEqual, 1) 84 }) 85 Convey("but an unused depends_on field should not error", func() { 86 nameless := ` 87 tasks: 88 - name: "compile" 89 ` 90 p, errs := createIntermediateProject([]byte(nameless)) 91 So(p, ShouldNotBeNil) 92 So(len(errs), ShouldEqual, 0) 93 }) 94 }) 95 }) 96 } 97 98 func TestCreateIntermediateProjectRequirements(t *testing.T) { 99 Convey("Testing different project files", t, func() { 100 Convey("a simple project file should parse", func() { 101 simple := ` 102 tasks: 103 - name: task0 104 - name: task1 105 requires: 106 - name: "task0" 107 variant: "v1" 108 - "task2" 109 ` 110 p, errs := createIntermediateProject([]byte(simple)) 111 So(p, ShouldNotBeNil) 112 So(len(errs), ShouldEqual, 0) 113 So(p.Tasks[1].Requires[0].Name, ShouldEqual, "task0") 114 So(p.Tasks[1].Requires[0].Variant.stringSelector, ShouldEqual, "v1") 115 So(p.Tasks[1].Requires[1].Name, ShouldEqual, "task2") 116 So(p.Tasks[1].Requires[1].Variant, ShouldBeNil) 117 }) 118 Convey("a single requirement should parse", func() { 119 simple := ` 120 tasks: 121 - name: task1 122 requires: 123 name: "task0" 124 variant: "v1" 125 ` 126 p, errs := createIntermediateProject([]byte(simple)) 127 So(p, ShouldNotBeNil) 128 So(len(errs), ShouldEqual, 0) 129 So(p.Tasks[0].Requires[0].Name, ShouldEqual, "task0") 130 So(p.Tasks[0].Requires[0].Variant.stringSelector, ShouldEqual, "v1") 131 }) 132 Convey("a single requirement with a matrix selector should parse", func() { 133 simple := ` 134 tasks: 135 - name: task1 136 requires: 137 name: "task0" 138 variant: 139 cool: "shoes" 140 colors: 141 - red 142 - green 143 - blue 144 ` 145 p, errs := createIntermediateProject([]byte(simple)) 146 So(errs, ShouldBeNil) 147 So(p, ShouldNotBeNil) 148 So(p.Tasks[0].Requires[0].Name, ShouldEqual, "task0") 149 So(p.Tasks[0].Requires[0].Variant.stringSelector, ShouldEqual, "") 150 So(p.Tasks[0].Requires[0].Variant.matrixSelector, ShouldResemble, matrixDefinition{ 151 "cool": []string{"shoes"}, "colors": []string{"red", "green", "blue"}, 152 }) 153 }) 154 }) 155 } 156 157 func TestCreateIntermediateProjectBuildVariants(t *testing.T) { 158 Convey("Testing different project files", t, func() { 159 Convey("a file with multiple BVTs should parse", func() { 160 simple := ` 161 buildvariants: 162 - name: "v1" 163 stepback: true 164 batchtime: 123 165 modules: ["wow","cool"] 166 run_on: 167 - "windows2000" 168 tasks: 169 - name: "t1" 170 - name: "t2" 171 depends_on: 172 - name: "t3" 173 variant: "v0" 174 requires: 175 - name: "t4" 176 stepback: false 177 priority: 77 178 ` 179 p, errs := createIntermediateProject([]byte(simple)) 180 So(p, ShouldNotBeNil) 181 So(len(errs), ShouldEqual, 0) 182 bv := p.BuildVariants[0] 183 So(bv.Name, ShouldEqual, "v1") 184 So(*bv.Stepback, ShouldBeTrue) 185 So(bv.RunOn[0], ShouldEqual, "windows2000") 186 So(len(bv.Modules), ShouldEqual, 2) 187 So(bv.Tasks[0].Name, ShouldEqual, "t1") 188 So(bv.Tasks[1].Name, ShouldEqual, "t2") 189 So(bv.Tasks[1].DependsOn[0].taskSelector, ShouldResemble, 190 taskSelector{Name: "t3", Variant: &variantSelector{stringSelector: "v0"}}) 191 So(bv.Tasks[1].Requires[0], ShouldResemble, taskSelector{Name: "t4"}) 192 So(*bv.Tasks[1].Stepback, ShouldBeFalse) 193 So(bv.Tasks[1].Priority, ShouldEqual, 77) 194 }) 195 Convey("a file with oneline BVTs should parse", func() { 196 simple := ` 197 buildvariants: 198 - name: "v1" 199 tasks: 200 - "t1" 201 - name: "t2" 202 depends_on: "t3" 203 requires: "t4" 204 ` 205 p, errs := createIntermediateProject([]byte(simple)) 206 So(p, ShouldNotBeNil) 207 So(len(errs), ShouldEqual, 0) 208 bv := p.BuildVariants[0] 209 So(bv.Name, ShouldEqual, "v1") 210 So(bv.Tasks[0].Name, ShouldEqual, "t1") 211 So(bv.Tasks[1].Name, ShouldEqual, "t2") 212 So(bv.Tasks[1].DependsOn[0].taskSelector, ShouldResemble, taskSelector{Name: "t3"}) 213 So(bv.Tasks[1].Requires[0], ShouldResemble, taskSelector{Name: "t4"}) 214 }) 215 Convey("a file with single BVTs should parse", func() { 216 simple := ` 217 buildvariants: 218 - name: "v1" 219 tasks: "*" 220 - name: "v2" 221 tasks: 222 name: "t1" 223 ` 224 p, errs := createIntermediateProject([]byte(simple)) 225 So(p, ShouldNotBeNil) 226 So(len(errs), ShouldEqual, 0) 227 So(len(p.BuildVariants), ShouldEqual, 2) 228 bv1 := p.BuildVariants[0] 229 bv2 := p.BuildVariants[1] 230 So(bv1.Name, ShouldEqual, "v1") 231 So(bv2.Name, ShouldEqual, "v2") 232 So(len(bv1.Tasks), ShouldEqual, 1) 233 So(bv1.Tasks[0].Name, ShouldEqual, "*") 234 So(len(bv2.Tasks), ShouldEqual, 1) 235 So(bv2.Tasks[0].Name, ShouldEqual, "t1") 236 }) 237 Convey("a file with single run_on, tags, and ignore fields should parse ", func() { 238 single := ` 239 ignore: "*.md" 240 tasks: 241 - name: "t1" 242 tags: wow 243 buildvariants: 244 - name: "v1" 245 run_on: "distro1" 246 tasks: "*" 247 ` 248 p, errs := createIntermediateProject([]byte(single)) 249 So(p, ShouldNotBeNil) 250 So(len(errs), ShouldEqual, 0) 251 So(len(p.Ignore), ShouldEqual, 1) 252 So(p.Ignore[0], ShouldEqual, "*.md") 253 So(len(p.Tasks[0].Tags), ShouldEqual, 1) 254 So(p.Tasks[0].Tags[0], ShouldEqual, "wow") 255 So(len(p.BuildVariants), ShouldEqual, 1) 256 bv1 := p.BuildVariants[0] 257 So(bv1.Name, ShouldEqual, "v1") 258 So(len(bv1.RunOn), ShouldEqual, 1) 259 So(bv1.RunOn[0], ShouldEqual, "distro1") 260 }) 261 Convey("a file that uses run_on for BVTasks should parse", func() { 262 single := ` 263 buildvariants: 264 - name: "v1" 265 tasks: 266 - name: "t1" 267 run_on: "test" 268 ` 269 p, errs := createIntermediateProject([]byte(single)) 270 So(p, ShouldNotBeNil) 271 So(len(errs), ShouldEqual, 0) 272 So(p.BuildVariants[0].Tasks[0].Distros[0], ShouldEqual, "test") 273 So(p.BuildVariants[0].Tasks[0].RunOn, ShouldBeNil) 274 }) 275 Convey("a file that uses run_on AND distros for BVTasks should not parse", func() { 276 single := ` 277 buildvariants: 278 - name: "v1" 279 tasks: 280 - name: "t1" 281 run_on: "test" 282 distros: "asdasdasd" 283 ` 284 p, errs := createIntermediateProject([]byte(single)) 285 So(p, ShouldBeNil) 286 So(len(errs), ShouldEqual, 1) 287 }) 288 }) 289 } 290 291 func TestTranslateDependsOn(t *testing.T) { 292 Convey("With an intermediate parseProject", t, func() { 293 pp := &parserProject{} 294 Convey("a tag-free dependency config should be unchanged", func() { 295 pp.BuildVariants = []parserBV{ 296 {Name: "v1"}, 297 } 298 pp.Tasks = []parserTask{ 299 {Name: "t1"}, 300 {Name: "t2"}, 301 {Name: "t3", DependsOn: parserDependencies{ 302 {taskSelector: taskSelector{Name: "t1"}}, 303 {taskSelector: taskSelector{ 304 Name: "t2", Variant: &variantSelector{stringSelector: "v1"}}}}, 305 }, 306 } 307 out, errs := translateProject(pp) 308 So(out, ShouldNotBeNil) 309 So(len(errs), ShouldEqual, 0) 310 deps := out.Tasks[2].DependsOn 311 So(deps[0].Name, ShouldEqual, "t1") 312 So(deps[1].Name, ShouldEqual, "t2") 313 So(deps[1].Variant, ShouldEqual, "v1") 314 }) 315 Convey("a dependency with tag selectors should evaluate", func() { 316 pp.BuildVariants = []parserBV{ 317 {Name: "v1", Tags: []string{"cool"}}, 318 {Name: "v2", Tags: []string{"cool"}}, 319 } 320 pp.Tasks = []parserTask{ 321 {Name: "t1", Tags: []string{"a", "b"}}, 322 {Name: "t2", Tags: []string{"a", "c"}, DependsOn: parserDependencies{ 323 {taskSelector: taskSelector{Name: "*"}}}}, 324 {Name: "t3", DependsOn: parserDependencies{ 325 {taskSelector: taskSelector{ 326 Name: ".b", Variant: &variantSelector{stringSelector: ".cool !v2"}}}, 327 {taskSelector: taskSelector{ 328 Name: ".a !.b", Variant: &variantSelector{stringSelector: ".cool"}}}}, 329 }, 330 } 331 out, errs := translateProject(pp) 332 So(out, ShouldNotBeNil) 333 So(len(errs), ShouldEqual, 0) 334 So(out.Tasks[1].DependsOn[0].Name, ShouldEqual, "*") 335 deps := out.Tasks[2].DependsOn 336 So(deps[0].Name, ShouldEqual, "t1") 337 So(deps[0].Variant, ShouldEqual, "v1") 338 So(deps[1].Name, ShouldEqual, "t2") 339 So(deps[1].Variant, ShouldEqual, "v1") 340 So(deps[2].Name, ShouldEqual, "t2") 341 So(deps[2].Variant, ShouldEqual, "v2") 342 }) 343 Convey("a dependency with erroneous selectors should fail", func() { 344 pp.BuildVariants = []parserBV{ 345 {Name: "v1"}, 346 } 347 pp.Tasks = []parserTask{ 348 {Name: "t1", Tags: []string{"a", "b"}}, 349 {Name: "t2", Tags: []string{"a", "c"}}, 350 {Name: "t3", DependsOn: parserDependencies{ 351 {taskSelector: taskSelector{Name: ".cool"}}, 352 {taskSelector: taskSelector{Name: "!!.cool"}}, //[1] illegal selector 353 {taskSelector: taskSelector{Name: "!.c !.b", Variant: &variantSelector{stringSelector: "v1"}}}, //[2] no matching tasks 354 {taskSelector: taskSelector{Name: "t1", Variant: &variantSelector{stringSelector: ".nope"}}}, //[3] no matching variants 355 {taskSelector: taskSelector{Name: "t1"}, Status: "*"}, // valid, but: 356 {taskSelector: taskSelector{Name: ".b"}}, //[4] conflicts with above 357 }}, 358 } 359 out, errs := translateProject(pp) 360 So(out, ShouldNotBeNil) 361 So(len(errs), ShouldEqual, 4) 362 }) 363 }) 364 } 365 366 func TestTranslateRequires(t *testing.T) { 367 Convey("With an intermediate parseProject", t, func() { 368 pp := &parserProject{} 369 Convey("a task with valid requirements should succeed", func() { 370 pp.BuildVariants = []parserBV{ 371 {Name: "v1"}, 372 } 373 pp.Tasks = []parserTask{ 374 {Name: "t1"}, 375 {Name: "t2"}, 376 {Name: "t3", Requires: taskSelectors{ 377 {Name: "t1"}, 378 {Name: "t2", Variant: &variantSelector{stringSelector: "v1"}}, 379 }}, 380 } 381 out, errs := translateProject(pp) 382 So(out, ShouldNotBeNil) 383 So(len(errs), ShouldEqual, 0) 384 reqs := out.Tasks[2].Requires 385 So(reqs[0].Name, ShouldEqual, "t1") 386 So(reqs[1].Name, ShouldEqual, "t2") 387 So(reqs[1].Variant, ShouldEqual, "v1") 388 }) 389 Convey("a task with erroneous requirements should fail", func() { 390 pp.BuildVariants = []parserBV{ 391 {Name: "v1"}, 392 } 393 pp.Tasks = []parserTask{ 394 {Name: "t1"}, 395 {Name: "t2", Tags: []string{"taggy"}}, 396 {Name: "t3", Requires: taskSelectors{ 397 {Name: "!!!!!"}, //illegal selector 398 {Name: ".taggy !t2", Variant: &variantSelector{stringSelector: "v1"}}, //nothing returned 399 {Name: "t1", Variant: &variantSelector{stringSelector: "!v1"}}, //no variants returned 400 {Name: "t1 t2"}, //nothing returned 401 }}, 402 } 403 out, errs := translateProject(pp) 404 So(out, ShouldNotBeNil) 405 So(len(errs), ShouldEqual, 4) 406 }) 407 }) 408 } 409 410 func TestTranslateBuildVariants(t *testing.T) { 411 Convey("With an intermediate parseProject", t, func() { 412 pp := &parserProject{} 413 Convey("a project with valid variant tasks should succeed", func() { 414 pp.Tasks = []parserTask{ 415 {Name: "t1"}, 416 {Name: "t2", Tags: []string{"a", "z"}}, 417 {Name: "t3", Tags: []string{"a", "b"}}, 418 } 419 pp.BuildVariants = []parserBV{{ 420 Name: "v1", 421 Tasks: parserBVTasks{ 422 {Name: "t1"}, 423 {Name: ".z", DependsOn: parserDependencies{ 424 {taskSelector: taskSelector{Name: ".b"}}}}, 425 {Name: "* !t1 !t2", Requires: taskSelectors{{Name: "!.a"}}}, 426 }, 427 }} 428 429 out, errs := translateProject(pp) 430 So(out, ShouldNotBeNil) 431 So(len(errs), ShouldEqual, 0) 432 bvts := out.BuildVariants[0].Tasks 433 So(bvts[0].Name, ShouldEqual, "t1") 434 So(bvts[1].Name, ShouldEqual, "t2") 435 So(bvts[2].Name, ShouldEqual, "t3") 436 So(bvts[1].DependsOn[0].Name, ShouldEqual, "t3") 437 So(bvts[2].Requires[0].Name, ShouldEqual, "t1") 438 }) 439 Convey("a bvtask with erroneous requirements should fail", func() { 440 pp.Tasks = []parserTask{ 441 {Name: "t1"}, 442 } 443 pp.BuildVariants = []parserBV{{ 444 Name: "v1", 445 Tasks: parserBVTasks{ 446 {Name: "t1", Requires: taskSelectors{{Name: ".b"}}}, 447 }, 448 }} 449 out, errs := translateProject(pp) 450 So(out, ShouldNotBeNil) 451 So(len(errs), ShouldEqual, 1) 452 }) 453 }) 454 } 455 456 func parserTaskSelectorTaskEval(tse *taskSelectorEvaluator, tasks parserBVTasks, expected []BuildVariantTask) { 457 names := []string{} 458 exp := []string{} 459 for _, t := range tasks { 460 names = append(names, t.Name) 461 } 462 for _, e := range expected { 463 exp = append(exp, e.Name) 464 } 465 vse := NewVariantSelectorEvaluator([]parserBV{}, nil) 466 Convey(fmt.Sprintf("tasks [%v] should evaluate to [%v]", 467 strings.Join(names, ", "), strings.Join(exp, ", ")), func() { 468 ts, errs := evaluateBVTasks(tse, vse, tasks) 469 if expected != nil { 470 So(errs, ShouldBeNil) 471 } else { 472 So(errs, ShouldNotBeNil) 473 } 474 So(len(ts), ShouldEqual, len(expected)) 475 for _, e := range expected { 476 exists := false 477 for _, t := range ts { 478 if t.Name == e.Name && t.Priority == e.Priority && len(t.DependsOn) == len(e.DependsOn) { 479 exists = true 480 } 481 } 482 So(exists, ShouldBeTrue) 483 } 484 }) 485 } 486 487 func TestParserTaskSelectorEvaluation(t *testing.T) { 488 Convey("With a colorful set of ProjectTasks", t, func() { 489 taskDefs := []parserTask{ 490 {Name: "red", Tags: []string{"primary", "warm"}}, 491 {Name: "orange", Tags: []string{"secondary", "warm"}}, 492 {Name: "yellow", Tags: []string{"primary", "warm"}}, 493 {Name: "green", Tags: []string{"secondary", "cool"}}, 494 {Name: "blue", Tags: []string{"primary", "cool"}}, 495 {Name: "purple", Tags: []string{"secondary", "cool"}}, 496 {Name: "brown", Tags: []string{"tertiary"}}, 497 {Name: "black", Tags: []string{"special"}}, 498 {Name: "white", Tags: []string{"special"}}, 499 } 500 501 Convey("a project parser", func() { 502 tse := NewParserTaskSelectorEvaluator(taskDefs) 503 Convey("should evaluate valid tasks pointers properly", func() { 504 parserTaskSelectorTaskEval(tse, 505 parserBVTasks{{Name: "white"}}, 506 []BuildVariantTask{{Name: "white"}}) 507 parserTaskSelectorTaskEval(tse, 508 parserBVTasks{{Name: "red", Priority: 500}, {Name: ".secondary"}}, 509 []BuildVariantTask{{Name: "red", Priority: 500}, {Name: "orange"}, {Name: "purple"}, {Name: "green"}}) 510 parserTaskSelectorTaskEval(tse, 511 parserBVTasks{ 512 {Name: "orange", Distros: []string{"d1"}}, 513 {Name: ".warm .secondary", Distros: []string{"d1"}}}, 514 []BuildVariantTask{{Name: "orange", Distros: []string{"d1"}}}) 515 parserTaskSelectorTaskEval(tse, 516 parserBVTasks{ 517 {Name: "orange", Distros: []string{"d1"}}, 518 {Name: "!.warm .secondary", Distros: []string{"d1"}}}, 519 []BuildVariantTask{ 520 {Name: "orange", Distros: []string{"d1"}}, 521 {Name: "purple", Distros: []string{"d1"}}, 522 {Name: "green", Distros: []string{"d1"}}}) 523 parserTaskSelectorTaskEval(tse, 524 parserBVTasks{{Name: "*"}}, 525 []BuildVariantTask{ 526 {Name: "red"}, {Name: "blue"}, {Name: "yellow"}, 527 {Name: "orange"}, {Name: "purple"}, {Name: "green"}, 528 {Name: "brown"}, {Name: "white"}, {Name: "black"}, 529 }) 530 parserTaskSelectorTaskEval(tse, 531 parserBVTasks{ 532 {Name: "red", Priority: 100}, 533 {Name: "!.warm .secondary", Priority: 100}}, 534 []BuildVariantTask{ 535 {Name: "red", Priority: 100}, 536 {Name: "purple", Priority: 100}, 537 {Name: "green", Priority: 100}}) 538 }) 539 }) 540 }) 541 }