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  }