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  }