github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/lifecycle_test.go (about)

     1  package model
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/evergreen-ci/evergreen"
     8  	"github.com/evergreen-ci/evergreen/db"
     9  	"github.com/evergreen-ci/evergreen/model/build"
    10  	"github.com/evergreen-ci/evergreen/model/task"
    11  	"github.com/evergreen-ci/evergreen/model/version"
    12  	"github.com/evergreen-ci/evergreen/testutil"
    13  	. "github.com/smartystreets/goconvey/convey"
    14  )
    15  
    16  func init() {
    17  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(testutil.TestConfig()))
    18  }
    19  
    20  func taskIdInSlice(tasks []task.Task, id string) bool {
    21  	for _, task := range tasks {
    22  		if task.Id == id {
    23  			return true
    24  		}
    25  	}
    26  	return false
    27  }
    28  
    29  func TestBuildSetPriority(t *testing.T) {
    30  
    31  	Convey("With a build", t, func() {
    32  
    33  		testutil.HandleTestingErr(db.ClearCollections(build.Collection, task.Collection), t,
    34  			"Error clearing test collection")
    35  
    36  		b := &build.Build{
    37  			Id: "build",
    38  		}
    39  		So(b.Insert(), ShouldBeNil)
    40  
    41  		taskOne := &task.Task{Id: "taskOne", BuildId: b.Id}
    42  		So(taskOne.Insert(), ShouldBeNil)
    43  
    44  		taskTwo := &task.Task{Id: "taskTwo", BuildId: b.Id}
    45  		So(taskTwo.Insert(), ShouldBeNil)
    46  
    47  		taskThree := &task.Task{Id: "taskThree", BuildId: b.Id}
    48  		So(taskThree.Insert(), ShouldBeNil)
    49  
    50  		Convey("setting its priority should update the priority"+
    51  			" of all its tasks in the database", func() {
    52  
    53  			So(SetBuildPriority(b.Id, 42), ShouldBeNil)
    54  
    55  			tasks, err := task.Find(task.ByBuildId(b.Id))
    56  			So(err, ShouldBeNil)
    57  			So(len(tasks), ShouldEqual, 3)
    58  			So(tasks[0].Priority, ShouldEqual, 42)
    59  			So(tasks[1].Priority, ShouldEqual, 42)
    60  			So(tasks[2].Priority, ShouldEqual, 42)
    61  		})
    62  
    63  	})
    64  
    65  }
    66  
    67  func TestBuildRestart(t *testing.T) {
    68  	Convey("Restarting a build", t, func() {
    69  
    70  		testutil.HandleTestingErr(db.ClearCollections(build.Collection, task.Collection), t,
    71  			"Error clearing test collection")
    72  		b := &build.Build{
    73  			Id: "build",
    74  			Tasks: []build.TaskCache{
    75  				{
    76  					Id:        "task1",
    77  					Status:    evergreen.TaskSucceeded,
    78  					Activated: true,
    79  				},
    80  				{
    81  					Id:        "task2",
    82  					Status:    evergreen.TaskDispatched,
    83  					Activated: true,
    84  				},
    85  				{
    86  					Id:        "task3",
    87  					Status:    evergreen.TaskDispatched,
    88  					Activated: true,
    89  				},
    90  				{
    91  					Id:        "task4",
    92  					Status:    evergreen.TaskDispatched,
    93  					Activated: true,
    94  				},
    95  			},
    96  		}
    97  		So(b.Insert(), ShouldBeNil)
    98  
    99  		Convey("with task abort should update the status of"+
   100  			" non in-progress tasks and abort in-progress ones", func() {
   101  
   102  			taskOne := &task.Task{
   103  				Id:          "task1",
   104  				DisplayName: "task1",
   105  				BuildId:     b.Id,
   106  				Status:      evergreen.TaskSucceeded,
   107  			}
   108  			So(taskOne.Insert(), ShouldBeNil)
   109  
   110  			taskTwo := &task.Task{
   111  				Id:          "task2",
   112  				DisplayName: "task2",
   113  				BuildId:     b.Id,
   114  				Status:      evergreen.TaskDispatched,
   115  			}
   116  			So(taskTwo.Insert(), ShouldBeNil)
   117  
   118  			So(RestartBuild(b.Id, []string{"task1", "task2"}, true, evergreen.DefaultTaskActivator), ShouldBeNil)
   119  			b, err := build.FindOne(build.ById(b.Id))
   120  			So(err, ShouldBeNil)
   121  			So(b.Status, ShouldEqual, evergreen.BuildCreated)
   122  			So(b.Activated, ShouldEqual, true)
   123  			So(b.Tasks[0].Status, ShouldEqual, evergreen.TaskUndispatched)
   124  			So(b.Tasks[1].Status, ShouldEqual, evergreen.TaskDispatched)
   125  			So(b.Tasks[0].Activated, ShouldEqual, true)
   126  			So(b.Tasks[1].Activated, ShouldEqual, true)
   127  			taskOne, err = task.FindOne(task.ById("task1"))
   128  			So(err, ShouldBeNil)
   129  			So(taskOne.Status, ShouldEqual, evergreen.TaskUndispatched)
   130  			taskTwo, err = task.FindOne(task.ById("task2"))
   131  			So(err, ShouldBeNil)
   132  			So(taskTwo.Aborted, ShouldEqual, true)
   133  		})
   134  
   135  		Convey("without task abort should update the status"+
   136  			" of only those build tasks not in-progress", func() {
   137  
   138  			taskThree := &task.Task{
   139  				Id:          "task3",
   140  				DisplayName: "task3",
   141  				BuildId:     b.Id,
   142  				Status:      evergreen.TaskSucceeded,
   143  			}
   144  			So(taskThree.Insert(), ShouldBeNil)
   145  
   146  			taskFour := &task.Task{
   147  				Id:          "task4",
   148  				DisplayName: "task4",
   149  				BuildId:     b.Id,
   150  				Status:      evergreen.TaskDispatched,
   151  			}
   152  			So(taskFour.Insert(), ShouldBeNil)
   153  
   154  			So(RestartBuild(b.Id, []string{"task3", "task4"}, false, evergreen.DefaultTaskActivator), ShouldBeNil)
   155  			b, err := build.FindOne(build.ById(b.Id))
   156  			So(err, ShouldBeNil)
   157  			So(err, ShouldBeNil)
   158  			So(b.Status, ShouldEqual, evergreen.BuildCreated)
   159  			So(b.Activated, ShouldEqual, true)
   160  			So(b.Tasks[2].Status, ShouldEqual, evergreen.TaskUndispatched)
   161  			So(b.Tasks[3].Status, ShouldEqual, evergreen.TaskDispatched)
   162  			So(b.Tasks[2].Activated, ShouldEqual, true)
   163  			So(b.Tasks[3].Activated, ShouldEqual, true)
   164  			taskThree, err = task.FindOne(task.ById("task3"))
   165  			So(err, ShouldBeNil)
   166  			So(taskThree.Status, ShouldEqual, evergreen.TaskUndispatched)
   167  			taskFour, err = task.FindOne(task.ById("task4"))
   168  			So(err, ShouldBeNil)
   169  			So(taskFour.Aborted, ShouldEqual, false)
   170  			So(taskFour.Status, ShouldEqual, evergreen.TaskDispatched)
   171  		})
   172  
   173  	})
   174  }
   175  
   176  func TestBuildMarkAborted(t *testing.T) {
   177  	Convey("With a build", t, func() {
   178  
   179  		testutil.HandleTestingErr(db.ClearCollections(build.Collection, task.Collection, version.Collection), t,
   180  			"Error clearing test collection")
   181  
   182  		v := &version.Version{
   183  			Id: "v",
   184  			BuildVariants: []version.BuildStatus{
   185  				{
   186  					BuildVariant: "bv",
   187  					Activated:    true,
   188  				},
   189  			},
   190  		}
   191  
   192  		So(v.Insert(), ShouldBeNil)
   193  
   194  		b := &build.Build{
   195  			Id:           "build",
   196  			Activated:    true,
   197  			BuildVariant: "bv",
   198  			Version:      "v",
   199  		}
   200  		So(b.Insert(), ShouldBeNil)
   201  
   202  		Convey("when marking it as aborted", func() {
   203  
   204  			Convey("it should be deactivated", func() {
   205  				So(AbortBuild(b.Id, evergreen.DefaultTaskActivator), ShouldBeNil)
   206  				b, err := build.FindOne(build.ById(b.Id))
   207  				So(err, ShouldBeNil)
   208  				So(b.Activated, ShouldBeFalse)
   209  			})
   210  
   211  			Convey("all abortable tasks for it should be aborted", func() {
   212  
   213  				// insert two abortable tasks and one non-abortable task
   214  				// for the correct build, and one abortable task for
   215  				// a different build
   216  
   217  				abortableOne := &task.Task{
   218  					Id:      "abortableOne",
   219  					BuildId: b.Id,
   220  					Status:  evergreen.TaskStarted,
   221  				}
   222  				So(abortableOne.Insert(), ShouldBeNil)
   223  
   224  				abortableTwo := &task.Task{
   225  					Id:      "abortableTwo",
   226  					BuildId: b.Id,
   227  					Status:  evergreen.TaskDispatched,
   228  				}
   229  				So(abortableTwo.Insert(), ShouldBeNil)
   230  
   231  				notAbortable := &task.Task{
   232  					Id:      "notAbortable",
   233  					BuildId: b.Id,
   234  					Status:  evergreen.TaskSucceeded,
   235  				}
   236  				So(notAbortable.Insert(), ShouldBeNil)
   237  
   238  				wrongBuildId := &task.Task{
   239  					Id:      "wrongBuildId",
   240  					BuildId: "blech",
   241  					Status:  evergreen.TaskStarted,
   242  				}
   243  				So(wrongBuildId.Insert(), ShouldBeNil)
   244  
   245  				// aborting the build should mark only the two abortable tasks
   246  				// with the correct build id as aborted
   247  
   248  				So(AbortBuild(b.Id, evergreen.DefaultTaskActivator), ShouldBeNil)
   249  
   250  				abortedTasks, err := task.Find(task.ByAborted(true))
   251  				So(err, ShouldBeNil)
   252  				So(len(abortedTasks), ShouldEqual, 2)
   253  				So(taskIdInSlice(abortedTasks, abortableOne.Id), ShouldBeTrue)
   254  				So(taskIdInSlice(abortedTasks, abortableTwo.Id), ShouldBeTrue)
   255  			})
   256  		})
   257  	})
   258  }
   259  
   260  func TestBuildSetActivated(t *testing.T) {
   261  	Convey("With a build", t, func() {
   262  
   263  		testutil.HandleTestingErr(db.ClearCollections(build.Collection, task.Collection), t,
   264  			"Error clearing test collection")
   265  
   266  		Convey("when changing the activated status of the build to true", func() {
   267  			Convey("the activated status of the build and all undispatched"+
   268  				" tasks that are part of it should be set", func() {
   269  
   270  				user := "differentUser"
   271  
   272  				b := &build.Build{
   273  					Id:           "build",
   274  					Activated:    true,
   275  					BuildVariant: "bv",
   276  				}
   277  				So(b.Insert(), ShouldBeNil)
   278  
   279  				// insert three tasks, with only one of them undispatched and
   280  				// belonging to the correct build
   281  
   282  				wrongBuildId := &task.Task{
   283  					Id:        "wrongBuildId",
   284  					BuildId:   "blech",
   285  					Status:    evergreen.TaskUndispatched,
   286  					Activated: true,
   287  				}
   288  				So(wrongBuildId.Insert(), ShouldBeNil)
   289  
   290  				wrongStatus := &task.Task{
   291  					Id:        "wrongStatus",
   292  					BuildId:   b.Id,
   293  					Status:    evergreen.TaskDispatched,
   294  					Activated: true,
   295  				}
   296  				So(wrongStatus.Insert(), ShouldBeNil)
   297  
   298  				matching := &task.Task{
   299  					Id:        "matching",
   300  					BuildId:   b.Id,
   301  					Status:    evergreen.TaskUndispatched,
   302  					Activated: true,
   303  				}
   304  
   305  				So(matching.Insert(), ShouldBeNil)
   306  
   307  				differentUser := &task.Task{
   308  					Id:          "differentUser",
   309  					BuildId:     b.Id,
   310  					Status:      evergreen.TaskUndispatched,
   311  					Activated:   true,
   312  					ActivatedBy: user,
   313  				}
   314  
   315  				So(differentUser.Insert(), ShouldBeNil)
   316  
   317  				So(SetBuildActivation(b.Id, false, evergreen.DefaultTaskActivator), ShouldBeNil)
   318  				// the build should have been updated in the db
   319  				b, err := build.FindOne(build.ById(b.Id))
   320  				So(err, ShouldBeNil)
   321  				So(b.Activated, ShouldBeFalse)
   322  				So(b.ActivatedBy, ShouldEqual, evergreen.DefaultTaskActivator)
   323  
   324  				// only the matching task should have been updated that has not been set by a user
   325  				deactivatedTasks, err := task.Find(task.ByActivation(false))
   326  				So(err, ShouldBeNil)
   327  				So(len(deactivatedTasks), ShouldEqual, 1)
   328  				So(deactivatedTasks[0].Id, ShouldEqual, matching.Id)
   329  
   330  				// task with the different user activating should be activated with that user
   331  				differentUserTask, err := task.FindOne(task.ById(differentUser.Id))
   332  				So(err, ShouldBeNil)
   333  				So(differentUserTask.Activated, ShouldBeTrue)
   334  				So(differentUserTask.ActivatedBy, ShouldEqual, user)
   335  
   336  			})
   337  
   338  			Convey("all of the undispatched task caches within the build"+
   339  				" should be updated, both in memory and in the"+
   340  				" database", func() {
   341  
   342  				b := &build.Build{
   343  					Id:           "build",
   344  					Activated:    true,
   345  					BuildVariant: "foo",
   346  					Tasks: []build.TaskCache{
   347  						{
   348  							Id:        "tc1",
   349  							Status:    evergreen.TaskUndispatched,
   350  							Activated: true,
   351  						},
   352  						{
   353  							Id:        "tc2",
   354  							Status:    evergreen.TaskDispatched,
   355  							Activated: true,
   356  						},
   357  						{
   358  							Id:        "tc3",
   359  							Status:    evergreen.TaskUndispatched,
   360  							Activated: true,
   361  						},
   362  						{
   363  							Id:        "tc4",
   364  							Status:    evergreen.TaskUndispatched,
   365  							Activated: true,
   366  						},
   367  					},
   368  				}
   369  				So(b.Insert(), ShouldBeNil)
   370  
   371  				t1 := &task.Task{Id: "tc1", DisplayName: "tc1", BuildId: b.Id, Status: evergreen.TaskUndispatched, Activated: true}
   372  				t2 := &task.Task{Id: "tc2", DisplayName: "tc2", BuildId: b.Id, Status: evergreen.TaskDispatched, Activated: true}
   373  				t3 := &task.Task{Id: "tc3", DisplayName: "tc3", BuildId: b.Id, Status: evergreen.TaskUndispatched, Activated: true}
   374  				t4 := &task.Task{Id: "tc4", DisplayName: "tc4", BuildId: b.Id, Status: evergreen.TaskUndispatched, Activated: true, ActivatedBy: "anotherUser"}
   375  				So(t1.Insert(), ShouldBeNil)
   376  				So(t2.Insert(), ShouldBeNil)
   377  				So(t3.Insert(), ShouldBeNil)
   378  				So(t4.Insert(), ShouldBeNil)
   379  
   380  				So(SetBuildActivation(b.Id, false, evergreen.DefaultTaskActivator), ShouldBeNil)
   381  				// refresh from the database and check again
   382  				b, err := build.FindOne(build.ById(b.Id))
   383  				So(err, ShouldBeNil)
   384  				So(b.Activated, ShouldBeFalse)
   385  				So(b.Tasks[0].Activated, ShouldBeFalse)
   386  				So(b.Tasks[1].Activated, ShouldBeTrue)
   387  				So(b.Tasks[2].Activated, ShouldBeFalse)
   388  				So(b.Tasks[3].Activated, ShouldBeTrue)
   389  			})
   390  
   391  			Convey("if a build is activated by a user it should not be able to be deactivated by evergreen", func() {
   392  				user := "differentUser"
   393  
   394  				b := &build.Build{
   395  					Id:           "anotherBuild",
   396  					Activated:    true,
   397  					BuildVariant: "bv",
   398  				}
   399  
   400  				So(b.Insert(), ShouldBeNil)
   401  
   402  				matching := &task.Task{
   403  					Id:        "matching",
   404  					BuildId:   b.Id,
   405  					Status:    evergreen.TaskUndispatched,
   406  					Activated: false,
   407  				}
   408  				So(matching.Insert(), ShouldBeNil)
   409  
   410  				matching2 := &task.Task{
   411  					Id:        "matching2",
   412  					BuildId:   b.Id,
   413  					Status:    evergreen.TaskUndispatched,
   414  					Activated: false,
   415  				}
   416  				So(matching2.Insert(), ShouldBeNil)
   417  
   418  				// have a user set the build activation to true
   419  				So(SetBuildActivation(b.Id, true, user), ShouldBeNil)
   420  
   421  				// task with the different user activating should be activated with that user
   422  				task1, err := task.FindOne(task.ById(matching.Id))
   423  				So(err, ShouldBeNil)
   424  				So(task1.Activated, ShouldBeTrue)
   425  				So(task1.ActivatedBy, ShouldEqual, user)
   426  
   427  				// task with the different user activating should be activated with that user
   428  				task2, err := task.FindOne(task.ById(matching2.Id))
   429  				So(err, ShouldBeNil)
   430  				So(task2.Activated, ShouldBeTrue)
   431  				So(task2.ActivatedBy, ShouldEqual, user)
   432  
   433  				// refresh from the database and check again
   434  				b, err = build.FindOne(build.ById(b.Id))
   435  				So(err, ShouldBeNil)
   436  				So(b.Activated, ShouldBeTrue)
   437  				So(b.ActivatedBy, ShouldEqual, user)
   438  
   439  				// deactivate the task from evergreen and nothing should be deactivated.
   440  				So(SetBuildActivation(b.Id, false, evergreen.DefaultTaskActivator), ShouldBeNil)
   441  
   442  				// refresh from the database and check again
   443  				b, err = build.FindOne(build.ById(b.Id))
   444  				So(err, ShouldBeNil)
   445  				So(b.Activated, ShouldBeTrue)
   446  				So(b.ActivatedBy, ShouldEqual, user)
   447  
   448  				// task with the different user activating should be activated with that user
   449  				task1, err = task.FindOne(task.ById(matching.Id))
   450  				So(err, ShouldBeNil)
   451  				So(task1.Activated, ShouldBeTrue)
   452  				So(task1.ActivatedBy, ShouldEqual, user)
   453  
   454  				// task with the different user activating should be activated with that user
   455  				task2, err = task.FindOne(task.ById(matching2.Id))
   456  				So(err, ShouldBeNil)
   457  				So(task2.Activated, ShouldBeTrue)
   458  				So(task2.ActivatedBy, ShouldEqual, user)
   459  
   460  			})
   461  		})
   462  
   463  	})
   464  }
   465  
   466  func TestBuildMarkStarted(t *testing.T) {
   467  
   468  	Convey("With a build", t, func() {
   469  
   470  		testutil.HandleTestingErr(db.Clear(build.Collection), t, "Error clearing"+
   471  			" '%v' collection", build.Collection)
   472  
   473  		b := &build.Build{
   474  			Id:     "build",
   475  			Status: evergreen.BuildCreated,
   476  		}
   477  		So(b.Insert(), ShouldBeNil)
   478  
   479  		Convey("marking it as started should update the status and"+
   480  			" start time, both in memory and in the database", func() {
   481  
   482  			startTime := time.Now()
   483  			So(build.TryMarkStarted(b.Id, startTime), ShouldBeNil)
   484  
   485  			// refresh from db and check again
   486  			b, err := build.FindOne(build.ById(b.Id))
   487  			So(err, ShouldBeNil)
   488  			So(b.Status, ShouldEqual, evergreen.BuildStarted)
   489  			So(b.StartTime.Round(time.Second).Equal(
   490  				startTime.Round(time.Second)), ShouldBeTrue)
   491  		})
   492  	})
   493  }
   494  
   495  func TestBuildMarkFinished(t *testing.T) {
   496  
   497  	Convey("With a build", t, func() {
   498  
   499  		testutil.HandleTestingErr(db.Clear(build.Collection), t, "Error clearing"+
   500  			" '%v' collection", build.Collection)
   501  
   502  		startTime := time.Now()
   503  		b := &build.Build{
   504  			Id:        "build",
   505  			StartTime: startTime,
   506  		}
   507  		So(b.Insert(), ShouldBeNil)
   508  
   509  		Convey("marking it as finished should update the status,"+
   510  			" finish time, and duration, both in memory and in the"+
   511  			" database", func() {
   512  
   513  			finishTime := time.Now()
   514  			So(b.MarkFinished(evergreen.BuildSucceeded, finishTime), ShouldBeNil)
   515  			So(b.Status, ShouldEqual, evergreen.BuildSucceeded)
   516  			So(b.FinishTime.Equal(finishTime), ShouldBeTrue)
   517  			So(b.TimeTaken, ShouldEqual, finishTime.Sub(startTime))
   518  
   519  			// refresh from db and check again
   520  
   521  			b, err := build.FindOne(build.ById(b.Id))
   522  			So(err, ShouldBeNil)
   523  			So(b.Status, ShouldEqual, evergreen.BuildSucceeded)
   524  			So(b.FinishTime.Round(time.Second).Equal(
   525  				finishTime.Round(time.Second)), ShouldBeTrue)
   526  			So(b.TimeTaken, ShouldEqual, finishTime.Sub(startTime))
   527  		})
   528  	})
   529  }
   530  
   531  func TestCreateBuildFromVersion(t *testing.T) {
   532  
   533  	Convey("When creating a build from a version", t, func() {
   534  
   535  		testutil.HandleTestingErr(db.ClearCollections(build.Collection, task.Collection), t,
   536  			"Error clearing test collection")
   537  
   538  		// the mock build variant we'll be using. runs all three tasks
   539  		buildVar1 := BuildVariant{
   540  			Name:        "buildVar",
   541  			DisplayName: "Build Variant",
   542  			Tasks: []BuildVariantTask{
   543  				{Name: "taskA"}, {Name: "taskB"}, {Name: "taskC"}, {Name: "taskD"},
   544  			},
   545  		}
   546  		buildVar2 := BuildVariant{
   547  			Name:        "buildVar2",
   548  			DisplayName: "Build Variant 2",
   549  			Tasks: []BuildVariantTask{
   550  				{Name: "taskA"}, {Name: "taskB"}, {Name: "taskC"}, {Name: "taskE"},
   551  			},
   552  		}
   553  		buildVar3 := BuildVariant{
   554  			Name:        "buildVar3",
   555  			DisplayName: "Build Variant 3",
   556  			Tasks: []BuildVariantTask{
   557  				{
   558  					// wait for the first BV's taskA to complete
   559  					Name:      "taskA",
   560  					DependsOn: []TaskDependency{{Name: "taskA", Variant: "buildVar"}},
   561  				},
   562  			},
   563  		}
   564  
   565  		project := &Project{
   566  			Tasks: []ProjectTask{
   567  				{
   568  					Name:      "taskA",
   569  					Priority:  5,
   570  					Tags:      []string{"tag1", "tag2"},
   571  					DependsOn: []TaskDependency{},
   572  				},
   573  				{
   574  					Name:      "taskB",
   575  					Tags:      []string{"tag1", "tag2"},
   576  					DependsOn: []TaskDependency{{Name: "taskA", Variant: "buildVar"}},
   577  				},
   578  				{
   579  					Name: "taskC",
   580  					Tags: []string{"tag1", "tag2"},
   581  					DependsOn: []TaskDependency{
   582  						{Name: "taskA"},
   583  						{Name: "taskB"},
   584  					},
   585  				},
   586  				{
   587  					Name:      "taskD",
   588  					Tags:      []string{"tag1", "tag2"},
   589  					DependsOn: []TaskDependency{{Name: AllDependencies}},
   590  				},
   591  				{
   592  					Name: "taskE",
   593  					Tags: []string{"tag1", "tag2"},
   594  					DependsOn: []TaskDependency{
   595  						{
   596  							Name:    AllDependencies,
   597  							Variant: AllVariants,
   598  						},
   599  					},
   600  				},
   601  			},
   602  			BuildVariants: []BuildVariant{buildVar1, buildVar2, buildVar3},
   603  		}
   604  
   605  		// the mock version we'll be using
   606  		v := &version.Version{
   607  			Id:                  "versionId",
   608  			CreateTime:          time.Now(),
   609  			Revision:            "foobar",
   610  			RevisionOrderNumber: 500,
   611  			Requester:           evergreen.RepotrackerVersionRequester,
   612  			BuildVariants: []version.BuildStatus{
   613  				{
   614  					BuildVariant: buildVar1.Name,
   615  					Activated:    false,
   616  				},
   617  				{
   618  					BuildVariant: buildVar2.Name,
   619  					Activated:    false,
   620  				},
   621  				{
   622  					BuildVariant: buildVar3.Name,
   623  					Activated:    false,
   624  				},
   625  			},
   626  		}
   627  
   628  		tt := NewTaskIdTable(project, v)
   629  
   630  		Convey("the task id table should be well-formed", func() {
   631  			So(tt.GetId("buildVar", "taskA"), ShouldNotEqual, "")
   632  			So(tt.GetId("buildVar", "taskB"), ShouldNotEqual, "")
   633  			So(tt.GetId("buildVar", "taskC"), ShouldNotEqual, "")
   634  			So(tt.GetId("buildVar", "taskD"), ShouldNotEqual, "")
   635  			So(tt.GetId("buildVar2", "taskA"), ShouldNotEqual, "")
   636  			So(tt.GetId("buildVar2", "taskB"), ShouldNotEqual, "")
   637  			So(tt.GetId("buildVar2", "taskC"), ShouldNotEqual, "")
   638  			So(tt.GetId("buildVar2", "taskE"), ShouldNotEqual, "")
   639  			So(tt.GetId("buildVar3", "taskA"), ShouldNotEqual, "")
   640  
   641  			Convey(`and incorrect GetId() calls should return ""`, func() {
   642  				So(tt.GetId("buildVar", "taskF"), ShouldEqual, "")
   643  				So(tt.GetId("buildVar2", "taskD"), ShouldEqual, "")
   644  				So(tt.GetId("buildVar7", "taskA"), ShouldEqual, "")
   645  			})
   646  		})
   647  
   648  		Convey("if a non-existent build variant is passed in, an error should be returned", func() {
   649  
   650  			buildId, err := CreateBuildFromVersion(project, v, tt, "blecch", false, []string{})
   651  			So(err, ShouldNotBeNil)
   652  			So(buildId, ShouldEqual, "")
   653  
   654  		})
   655  
   656  		Convey("if no task names are passed in to be used, all of the default"+
   657  			" tasks for the build variant should be created", func() {
   658  
   659  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false, nil)
   660  			So(err, ShouldBeNil)
   661  			So(buildId, ShouldNotEqual, "")
   662  			buildId2, err := CreateBuildFromVersion(project, v, tt, buildVar2.Name, false, nil)
   663  			So(err, ShouldBeNil)
   664  			So(buildId2, ShouldNotEqual, "")
   665  
   666  			// find the tasks, make sure they were all created
   667  			tasks, err := task.Find(task.All)
   668  			So(err, ShouldBeNil)
   669  			So(len(tasks), ShouldEqual, 8)
   670  			So(len(tasks[0].Tags), ShouldEqual, 2)
   671  
   672  		})
   673  
   674  		Convey("if a non-empty list of task names is passed in, only the"+
   675  			" specified tasks should be created", func() {
   676  
   677  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false,
   678  				[]string{"taskA", "taskB"})
   679  			So(err, ShouldBeNil)
   680  			So(buildId, ShouldNotEqual, "")
   681  
   682  			// find the tasks, make sure they were all created
   683  			tasks, err := task.Find(task.All)
   684  			So(err, ShouldBeNil)
   685  			So(len(tasks), ShouldEqual, 2)
   686  
   687  		})
   688  
   689  		Convey("the build should contain task caches that correspond exactly"+
   690  			" to the tasks created", func() {
   691  
   692  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false, nil)
   693  			So(err, ShouldBeNil)
   694  			So(buildId, ShouldNotEqual, "")
   695  
   696  			// find the tasks, make sure they were all created
   697  			tasks, err := task.Find(task.All)
   698  			So(err, ShouldBeNil)
   699  			So(len(tasks), ShouldEqual, 4)
   700  
   701  			// find the build from the db
   702  			b, err := build.FindOne(build.ById(buildId))
   703  			So(err, ShouldBeNil)
   704  			So(len(b.Tasks), ShouldEqual, 4)
   705  
   706  			// make sure the task caches are correct.  they should also appear
   707  			// in the same order that they appear in the project file
   708  			So(b.Tasks[0].Id, ShouldNotEqual, "")
   709  			So(b.Tasks[0].DisplayName, ShouldEqual, "taskA")
   710  			So(b.Tasks[0].Status, ShouldEqual, evergreen.TaskUndispatched)
   711  			So(b.Tasks[1].Id, ShouldNotEqual, "")
   712  			So(b.Tasks[1].DisplayName, ShouldEqual, "taskB")
   713  			So(b.Tasks[1].Status, ShouldEqual, evergreen.TaskUndispatched)
   714  			So(b.Tasks[2].Id, ShouldNotEqual, "")
   715  			So(b.Tasks[2].DisplayName, ShouldEqual, "taskC")
   716  			So(b.Tasks[2].Status, ShouldEqual, evergreen.TaskUndispatched)
   717  			So(b.Tasks[3].Id, ShouldNotEqual, "")
   718  			So(b.Tasks[3].DisplayName, ShouldEqual, "taskD")
   719  			So(b.Tasks[3].Status, ShouldEqual, evergreen.TaskUndispatched)
   720  
   721  		})
   722  
   723  		Convey("all of the tasks created should have the dependencies"+
   724  			"and priorities specified in the project", func() {
   725  
   726  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false, nil)
   727  			So(err, ShouldBeNil)
   728  			So(buildId, ShouldNotEqual, "")
   729  			buildId2, err := CreateBuildFromVersion(project, v, tt, buildVar2.Name, false, nil)
   730  			So(err, ShouldBeNil)
   731  			So(buildId2, ShouldNotEqual, "")
   732  			buildId3, err := CreateBuildFromVersion(project, v, tt, buildVar3.Name, false, nil)
   733  			So(err, ShouldBeNil)
   734  			So(buildId3, ShouldNotEqual, "")
   735  
   736  			// find the tasks, make sure they were all created
   737  			tasks, err := task.Find(task.All.Sort([]string{task.DisplayNameKey, task.BuildVariantKey}))
   738  			So(err, ShouldBeNil)
   739  			So(len(tasks), ShouldEqual, 9)
   740  
   741  			// taskA
   742  			So(len(tasks[0].DependsOn), ShouldEqual, 0)
   743  			So(len(tasks[1].DependsOn), ShouldEqual, 0)
   744  			So(len(tasks[2].DependsOn), ShouldEqual, 1)
   745  			So(tasks[0].Priority, ShouldEqual, 5)
   746  			So(tasks[1].Priority, ShouldEqual, 5)
   747  			So(tasks[2].DependsOn, ShouldResemble,
   748  				[]task.Dependency{{tasks[0].Id, evergreen.TaskSucceeded}})
   749  
   750  			// taskB
   751  			So(tasks[3].DependsOn, ShouldResemble,
   752  				[]task.Dependency{{tasks[0].Id, evergreen.TaskSucceeded}})
   753  			So(tasks[4].DependsOn, ShouldResemble,
   754  				[]task.Dependency{{tasks[0].Id, evergreen.TaskSucceeded}}) //cross-variant
   755  			So(tasks[3].Priority, ShouldEqual, 0)
   756  			So(tasks[4].Priority, ShouldEqual, 0) //default priority
   757  
   758  			// taskC
   759  			So(tasks[5].DependsOn, ShouldResemble,
   760  				[]task.Dependency{
   761  					{tasks[0].Id, evergreen.TaskSucceeded},
   762  					{tasks[3].Id, evergreen.TaskSucceeded}})
   763  			So(tasks[6].DependsOn, ShouldResemble,
   764  				[]task.Dependency{
   765  					{tasks[1].Id, evergreen.TaskSucceeded},
   766  					{tasks[4].Id, evergreen.TaskSucceeded}})
   767  			So(tasks[7].DependsOn, ShouldResemble,
   768  				[]task.Dependency{
   769  					{tasks[0].Id, evergreen.TaskSucceeded},
   770  					{tasks[3].Id, evergreen.TaskSucceeded},
   771  					{tasks[5].Id, evergreen.TaskSucceeded}})
   772  			So(tasks[8].DisplayName, ShouldEqual, "taskE")
   773  			So(len(tasks[8].DependsOn), ShouldEqual, 8)
   774  		})
   775  
   776  		Convey("all of the build's essential fields should be set"+
   777  			" correctly", func() {
   778  
   779  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false, nil)
   780  			So(err, ShouldBeNil)
   781  			So(buildId, ShouldNotEqual, "")
   782  
   783  			// find the build from the db
   784  			b, err := build.FindOne(build.ById(buildId))
   785  			So(err, ShouldBeNil)
   786  
   787  			// verify all the fields are set appropriately
   788  			So(len(b.Tasks), ShouldEqual, 4)
   789  			So(b.CreateTime.Truncate(time.Second), ShouldResemble,
   790  				v.CreateTime.Truncate(time.Second))
   791  			So(b.PushTime.Truncate(time.Second), ShouldResemble,
   792  				v.CreateTime.Truncate(time.Second))
   793  			So(b.Activated, ShouldEqual, v.BuildVariants[0].Activated)
   794  			So(b.Project, ShouldEqual, project.Identifier)
   795  			So(b.Revision, ShouldEqual, v.Revision)
   796  			So(b.Status, ShouldEqual, evergreen.BuildCreated)
   797  			So(b.BuildVariant, ShouldEqual, buildVar1.Name)
   798  			So(b.Version, ShouldEqual, v.Id)
   799  			So(b.DisplayName, ShouldEqual, buildVar1.DisplayName)
   800  			So(b.RevisionOrderNumber, ShouldEqual, v.RevisionOrderNumber)
   801  			So(b.Requester, ShouldEqual, v.Requester)
   802  
   803  		})
   804  
   805  		Convey("all of the tasks' essential fields should be set"+
   806  			" correctly", func() {
   807  
   808  			buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, false, nil)
   809  			So(err, ShouldBeNil)
   810  			So(buildId, ShouldNotEqual, "")
   811  
   812  			// find the build from the db
   813  			b, err := build.FindOne(build.ById(buildId))
   814  			So(err, ShouldBeNil)
   815  
   816  			// find the tasks, make sure they were all created
   817  			tasks, err := task.Find(task.All.Sort([]string{task.DisplayNameKey}))
   818  			So(err, ShouldBeNil)
   819  			So(len(tasks), ShouldEqual, 4)
   820  
   821  			So(tasks[0].Id, ShouldNotEqual, "")
   822  			So(tasks[0].Secret, ShouldNotEqual, "")
   823  			So(tasks[0].DisplayName, ShouldEqual, "taskA")
   824  			So(tasks[0].BuildId, ShouldEqual, buildId)
   825  			So(tasks[0].DistroId, ShouldEqual, "")
   826  			So(tasks[0].BuildVariant, ShouldEqual, buildVar1.Name)
   827  			So(tasks[0].CreateTime.Truncate(time.Second), ShouldResemble,
   828  				b.CreateTime.Truncate(time.Second))
   829  			So(tasks[0].PushTime.Truncate(time.Second), ShouldResemble,
   830  				b.PushTime.Truncate(time.Second))
   831  			So(tasks[0].Status, ShouldEqual, evergreen.TaskUndispatched)
   832  			So(tasks[0].Activated, ShouldEqual, b.Activated)
   833  			So(tasks[0].RevisionOrderNumber, ShouldEqual, b.RevisionOrderNumber)
   834  			So(tasks[0].Requester, ShouldEqual, b.Requester)
   835  			So(tasks[0].Version, ShouldEqual, v.Id)
   836  			So(tasks[0].Revision, ShouldEqual, v.Revision)
   837  			So(tasks[0].Project, ShouldEqual, project.Identifier)
   838  
   839  			So(tasks[1].Id, ShouldNotEqual, "")
   840  			So(tasks[1].Secret, ShouldNotEqual, "")
   841  			So(tasks[1].DisplayName, ShouldEqual, "taskB")
   842  			So(tasks[1].BuildId, ShouldEqual, buildId)
   843  			So(tasks[1].DistroId, ShouldEqual, "")
   844  			So(tasks[1].BuildVariant, ShouldEqual, buildVar1.Name)
   845  			So(tasks[1].CreateTime.Truncate(time.Second), ShouldResemble,
   846  				b.CreateTime.Truncate(time.Second))
   847  			So(tasks[1].PushTime.Truncate(time.Second), ShouldResemble,
   848  				b.PushTime.Truncate(time.Second))
   849  			So(tasks[1].Status, ShouldEqual, evergreen.TaskUndispatched)
   850  			So(tasks[1].Activated, ShouldEqual, b.Activated)
   851  			So(tasks[1].RevisionOrderNumber, ShouldEqual, b.RevisionOrderNumber)
   852  			So(tasks[1].Requester, ShouldEqual, b.Requester)
   853  			So(tasks[1].Version, ShouldEqual, v.Id)
   854  			So(tasks[1].Revision, ShouldEqual, v.Revision)
   855  			So(tasks[1].Project, ShouldEqual, project.Identifier)
   856  
   857  			So(tasks[2].Id, ShouldNotEqual, "")
   858  			So(tasks[2].Secret, ShouldNotEqual, "")
   859  			So(tasks[2].DisplayName, ShouldEqual, "taskC")
   860  			So(tasks[2].BuildId, ShouldEqual, buildId)
   861  			So(tasks[2].DistroId, ShouldEqual, "")
   862  			So(tasks[2].BuildVariant, ShouldEqual, buildVar1.Name)
   863  			So(tasks[2].CreateTime.Truncate(time.Second), ShouldResemble,
   864  				b.CreateTime.Truncate(time.Second))
   865  			So(tasks[2].PushTime.Truncate(time.Second), ShouldResemble,
   866  				b.PushTime.Truncate(time.Second))
   867  			So(tasks[2].Status, ShouldEqual, evergreen.TaskUndispatched)
   868  			So(tasks[2].Activated, ShouldEqual, b.Activated)
   869  			So(tasks[2].RevisionOrderNumber, ShouldEqual, b.RevisionOrderNumber)
   870  			So(tasks[2].Requester, ShouldEqual, b.Requester)
   871  			So(tasks[2].Version, ShouldEqual, v.Id)
   872  			So(tasks[2].Revision, ShouldEqual, v.Revision)
   873  			So(tasks[2].Project, ShouldEqual, project.Identifier)
   874  
   875  			So(tasks[3].Id, ShouldNotEqual, "")
   876  			So(tasks[3].Secret, ShouldNotEqual, "")
   877  			So(tasks[3].DisplayName, ShouldEqual, "taskD")
   878  			So(tasks[3].BuildId, ShouldEqual, buildId)
   879  			So(tasks[3].DistroId, ShouldEqual, "")
   880  			So(tasks[3].BuildVariant, ShouldEqual, buildVar1.Name)
   881  			So(tasks[3].CreateTime.Truncate(time.Second), ShouldResemble,
   882  				b.CreateTime.Truncate(time.Second))
   883  			So(tasks[3].PushTime.Truncate(time.Second), ShouldResemble,
   884  				b.PushTime.Truncate(time.Second))
   885  			So(tasks[3].Status, ShouldEqual, evergreen.TaskUndispatched)
   886  			So(tasks[3].Activated, ShouldEqual, b.Activated)
   887  			So(tasks[3].RevisionOrderNumber, ShouldEqual, b.RevisionOrderNumber)
   888  			So(tasks[3].Requester, ShouldEqual, b.Requester)
   889  			So(tasks[3].Version, ShouldEqual, v.Id)
   890  			So(tasks[3].Revision, ShouldEqual, v.Revision)
   891  			So(tasks[3].Project, ShouldEqual, project.Identifier)
   892  		})
   893  
   894  		Convey("if the activated flag is set, the build and all its tasks should be activated",
   895  			func() {
   896  
   897  				buildId, err := CreateBuildFromVersion(project, v, tt, buildVar1.Name, true, nil)
   898  				So(err, ShouldBeNil)
   899  				So(buildId, ShouldNotEqual, "")
   900  
   901  				// find the build from the db
   902  				build, err := build.FindOne(build.ById(buildId))
   903  				So(err, ShouldBeNil)
   904  				So(build.Activated, ShouldBeTrue)
   905  
   906  				// find the tasks, make sure they were all created
   907  				tasks, err := task.Find(task.All.Sort([]string{task.DisplayNameKey}))
   908  				So(err, ShouldBeNil)
   909  				So(len(tasks), ShouldEqual, 4)
   910  
   911  				So(tasks[0].Id, ShouldNotEqual, "")
   912  				So(tasks[0].Secret, ShouldNotEqual, "")
   913  				So(tasks[0].DisplayName, ShouldEqual, "taskA")
   914  				So(tasks[0].BuildId, ShouldEqual, buildId)
   915  				So(tasks[0].DistroId, ShouldEqual, "")
   916  				So(tasks[0].BuildVariant, ShouldEqual, buildVar1.Name)
   917  				So(tasks[0].CreateTime.Truncate(time.Second), ShouldResemble,
   918  					build.CreateTime.Truncate(time.Second))
   919  				So(tasks[0].PushTime.Truncate(time.Second), ShouldResemble,
   920  					build.PushTime.Truncate(time.Second))
   921  				So(tasks[0].Status, ShouldEqual, evergreen.TaskUndispatched)
   922  				So(tasks[0].Activated, ShouldEqual, build.Activated)
   923  				So(tasks[0].RevisionOrderNumber, ShouldEqual, build.RevisionOrderNumber)
   924  				So(tasks[0].Requester, ShouldEqual, build.Requester)
   925  				So(tasks[0].Version, ShouldEqual, v.Id)
   926  				So(tasks[0].Revision, ShouldEqual, v.Revision)
   927  				So(tasks[0].Project, ShouldEqual, project.Identifier)
   928  
   929  				So(tasks[1].Id, ShouldNotEqual, "")
   930  				So(tasks[1].Secret, ShouldNotEqual, "")
   931  				So(tasks[1].DisplayName, ShouldEqual, "taskB")
   932  				So(tasks[1].BuildId, ShouldEqual, buildId)
   933  				So(tasks[1].DistroId, ShouldEqual, "")
   934  				So(tasks[1].BuildVariant, ShouldEqual, buildVar1.Name)
   935  				So(tasks[1].CreateTime.Truncate(time.Second), ShouldResemble,
   936  					build.CreateTime.Truncate(time.Second))
   937  				So(tasks[1].PushTime.Truncate(time.Second), ShouldResemble,
   938  					build.PushTime.Truncate(time.Second))
   939  				So(tasks[1].Status, ShouldEqual, evergreen.TaskUndispatched)
   940  				So(tasks[1].Activated, ShouldEqual, build.Activated)
   941  				So(tasks[1].RevisionOrderNumber, ShouldEqual, build.RevisionOrderNumber)
   942  				So(tasks[1].Requester, ShouldEqual, build.Requester)
   943  				So(tasks[1].Version, ShouldEqual, v.Id)
   944  				So(tasks[1].Revision, ShouldEqual, v.Revision)
   945  				So(tasks[1].Project, ShouldEqual, project.Identifier)
   946  
   947  				So(tasks[2].Id, ShouldNotEqual, "")
   948  				So(tasks[2].Secret, ShouldNotEqual, "")
   949  				So(tasks[2].DisplayName, ShouldEqual, "taskC")
   950  				So(tasks[2].BuildId, ShouldEqual, buildId)
   951  				So(tasks[2].DistroId, ShouldEqual, "")
   952  				So(tasks[2].BuildVariant, ShouldEqual, buildVar1.Name)
   953  				So(tasks[2].CreateTime.Truncate(time.Second), ShouldResemble,
   954  					build.CreateTime.Truncate(time.Second))
   955  				So(tasks[2].PushTime.Truncate(time.Second), ShouldResemble,
   956  					build.PushTime.Truncate(time.Second))
   957  				So(tasks[2].Status, ShouldEqual, evergreen.TaskUndispatched)
   958  				So(tasks[2].Activated, ShouldEqual, build.Activated)
   959  				So(tasks[2].RevisionOrderNumber, ShouldEqual, build.RevisionOrderNumber)
   960  				So(tasks[2].Requester, ShouldEqual, build.Requester)
   961  				So(tasks[2].Version, ShouldEqual, v.Id)
   962  				So(tasks[2].Revision, ShouldEqual, v.Revision)
   963  				So(tasks[2].Project, ShouldEqual, project.Identifier)
   964  
   965  				So(tasks[3].Id, ShouldNotEqual, "")
   966  				So(tasks[3].Secret, ShouldNotEqual, "")
   967  				So(tasks[3].DisplayName, ShouldEqual, "taskD")
   968  				So(tasks[3].BuildId, ShouldEqual, buildId)
   969  				So(tasks[3].DistroId, ShouldEqual, "")
   970  				So(tasks[3].BuildVariant, ShouldEqual, buildVar1.Name)
   971  				So(tasks[3].CreateTime.Truncate(time.Second), ShouldResemble,
   972  					build.CreateTime.Truncate(time.Second))
   973  				So(tasks[3].PushTime.Truncate(time.Second), ShouldResemble,
   974  					build.PushTime.Truncate(time.Second))
   975  				So(tasks[3].Status, ShouldEqual, evergreen.TaskUndispatched)
   976  				So(tasks[3].Activated, ShouldEqual, build.Activated)
   977  				So(tasks[3].RevisionOrderNumber, ShouldEqual, build.RevisionOrderNumber)
   978  				So(tasks[3].Requester, ShouldEqual, build.Requester)
   979  				So(tasks[3].Version, ShouldEqual, v.Id)
   980  				So(tasks[3].Revision, ShouldEqual, v.Revision)
   981  				So(tasks[3].Project, ShouldEqual, project.Identifier)
   982  			})
   983  
   984  	})
   985  }
   986  
   987  func TestDeletingBuild(t *testing.T) {
   988  
   989  	Convey("With a build", t, func() {
   990  
   991  		testutil.HandleTestingErr(db.Clear(build.Collection), t, "Error clearing"+
   992  			" '%v' collection", build.Collection)
   993  
   994  		b := &build.Build{
   995  			Id: "build",
   996  		}
   997  		So(b.Insert(), ShouldBeNil)
   998  
   999  		Convey("deleting it should remove it and all its associated"+
  1000  			" tasks from the database", func() {
  1001  
  1002  			testutil.HandleTestingErr(db.ClearCollections(task.Collection), t, "Error"+
  1003  				" clearing '%v' collection", task.Collection)
  1004  
  1005  			// insert two tasks that are part of the build, and one that isn't
  1006  			matchingTaskOne := &task.Task{
  1007  				Id:      "matchingOne",
  1008  				BuildId: b.Id,
  1009  			}
  1010  			So(matchingTaskOne.Insert(), ShouldBeNil)
  1011  
  1012  			matchingTaskTwo := &task.Task{
  1013  				Id:      "matchingTwo",
  1014  				BuildId: b.Id,
  1015  			}
  1016  			So(matchingTaskTwo.Insert(), ShouldBeNil)
  1017  
  1018  			nonMatchingTask := &task.Task{
  1019  				Id:      "nonMatching",
  1020  				BuildId: "blech",
  1021  			}
  1022  			So(nonMatchingTask.Insert(), ShouldBeNil)
  1023  
  1024  			// delete the build, make sure only it and its tasks are deleted
  1025  
  1026  			So(DeleteBuild(b.Id), ShouldBeNil)
  1027  
  1028  			b, err := build.FindOne(build.ById(b.Id))
  1029  			So(err, ShouldBeNil)
  1030  			So(b, ShouldBeNil)
  1031  
  1032  			matchingTasks, err := task.Find(task.ByBuildId("build"))
  1033  			So(err, ShouldBeNil)
  1034  			So(len(matchingTasks), ShouldEqual, 0)
  1035  
  1036  			nonMatchingTask, err = task.FindOne(task.ById(nonMatchingTask.Id))
  1037  			So(err, ShouldBeNil)
  1038  			So(nonMatchingTask, ShouldNotBeNil)
  1039  		})
  1040  	})
  1041  }
  1042  
  1043  func TestSetNumDeps(t *testing.T) {
  1044  	Convey("setNumDeps correctly sets NumDependents for each task", t, func() {
  1045  		tasks := []*task.Task{
  1046  			{Id: "task1"},
  1047  			{
  1048  				Id:        "task2",
  1049  				DependsOn: []task.Dependency{{TaskId: "task1"}},
  1050  			},
  1051  			{
  1052  				Id:        "task3",
  1053  				DependsOn: []task.Dependency{{TaskId: "task1"}},
  1054  			},
  1055  			{
  1056  				Id:        "task4",
  1057  				DependsOn: []task.Dependency{{TaskId: "task2"}, {TaskId: "task3"}, {TaskId: "not_here"}},
  1058  			},
  1059  		}
  1060  		setNumDeps(tasks)
  1061  		So(len(tasks), ShouldEqual, 4)
  1062  		So(tasks[0].NumDependents, ShouldEqual, 3)
  1063  		So(tasks[1].NumDependents, ShouldEqual, 1)
  1064  		So(tasks[2].NumDependents, ShouldEqual, 1)
  1065  		So(tasks[3].NumDependents, ShouldEqual, 0)
  1066  	})
  1067  }
  1068  
  1069  func TestSortTasks(t *testing.T) {
  1070  	Convey("sortTasks topologically sorts tasks by dependency", t, func() {
  1071  		Convey("for tasks with single dependencies", func() {
  1072  			tasks := []task.Task{
  1073  				{
  1074  					Id:          "idA",
  1075  					DisplayName: "A",
  1076  					DependsOn: []task.Dependency{
  1077  						{TaskId: "idB"},
  1078  					},
  1079  				},
  1080  				{
  1081  					Id:          "idB",
  1082  					DisplayName: "B",
  1083  					DependsOn: []task.Dependency{
  1084  						{TaskId: "idC"},
  1085  					},
  1086  				},
  1087  				{
  1088  					Id:          "idC",
  1089  					DisplayName: "C",
  1090  				},
  1091  			}
  1092  
  1093  			sortedTasks := sortTasks(tasks)
  1094  			So(len(sortedTasks), ShouldEqual, 3)
  1095  			So(sortedTasks[0].DisplayName, ShouldEqual, "C")
  1096  			So(sortedTasks[1].DisplayName, ShouldEqual, "B")
  1097  			So(sortedTasks[2].DisplayName, ShouldEqual, "A")
  1098  		})
  1099  		Convey("for tasks with multiplie dependencies", func() {
  1100  			tasks := []task.Task{
  1101  				{
  1102  					Id:          "idA",
  1103  					DisplayName: "A",
  1104  					DependsOn: []task.Dependency{
  1105  						{TaskId: "idB"},
  1106  						{TaskId: "idC"},
  1107  					},
  1108  				},
  1109  				{
  1110  					Id:          "idB",
  1111  					DisplayName: "B",
  1112  					DependsOn: []task.Dependency{
  1113  						{TaskId: "idC"},
  1114  					},
  1115  				},
  1116  				{
  1117  					Id:          "idC",
  1118  					DisplayName: "C",
  1119  				},
  1120  			}
  1121  
  1122  			sortedTasks := sortTasks(tasks)
  1123  			So(len(sortedTasks), ShouldEqual, 3)
  1124  			So(sortedTasks[0].DisplayName, ShouldEqual, "C")
  1125  			So(sortedTasks[1].DisplayName, ShouldEqual, "B")
  1126  			So(sortedTasks[2].DisplayName, ShouldEqual, "A")
  1127  		})
  1128  	})
  1129  
  1130  	Convey("grouping tasks by common dependencies and sorting alphabetically within groups", t, func() {
  1131  		tasks := []task.Task{
  1132  			{
  1133  				Id:          "idA",
  1134  				DisplayName: "A",
  1135  				DependsOn: []task.Dependency{
  1136  					{TaskId: "idE"},
  1137  				},
  1138  			},
  1139  			{
  1140  				Id:          "idB",
  1141  				DisplayName: "B",
  1142  				DependsOn: []task.Dependency{
  1143  					{TaskId: "idD"},
  1144  				},
  1145  			},
  1146  			{
  1147  				Id:          "idC",
  1148  				DisplayName: "C",
  1149  				DependsOn: []task.Dependency{
  1150  					{TaskId: "idD"},
  1151  				},
  1152  			},
  1153  			{
  1154  				Id:          "idD",
  1155  				DisplayName: "D",
  1156  			},
  1157  			{
  1158  				Id:          "idE",
  1159  				DisplayName: "E",
  1160  			},
  1161  		}
  1162  
  1163  		sortedTasks := sortTasks(tasks)
  1164  		So(len(sortedTasks), ShouldEqual, 5)
  1165  		So(sortedTasks[0].DisplayName, ShouldEqual, "D")
  1166  		So(sortedTasks[1].DisplayName, ShouldEqual, "E")
  1167  		So(sortedTasks[2].DisplayName, ShouldEqual, "B")
  1168  		So(sortedTasks[3].DisplayName, ShouldEqual, "C")
  1169  		So(sortedTasks[4].DisplayName, ShouldEqual, "A")
  1170  	})
  1171  
  1172  	Convey("special-casing tasks with cross-variant dependencies to the far right", t, func() {
  1173  		tasks := []task.Task{
  1174  			{
  1175  				Id:          "idA",
  1176  				DisplayName: "A",
  1177  				DependsOn: []task.Dependency{
  1178  					{TaskId: "idB"},
  1179  					{TaskId: "idC"},
  1180  				},
  1181  			},
  1182  			{
  1183  				Id:          "idB",
  1184  				DisplayName: "B",
  1185  				DependsOn: []task.Dependency{
  1186  					{TaskId: "idC"},
  1187  				},
  1188  			},
  1189  			{
  1190  				Id:          "idC",
  1191  				DisplayName: "C",
  1192  				DependsOn: []task.Dependency{
  1193  					{TaskId: "cross-variant"},
  1194  				},
  1195  			},
  1196  			{
  1197  				Id:          "idD",
  1198  				DisplayName: "D",
  1199  			},
  1200  		}
  1201  
  1202  		sortedTasks := sortTasks(tasks)
  1203  		So(len(sortedTasks), ShouldEqual, 4)
  1204  		So(sortedTasks[0].DisplayName, ShouldEqual, "D")
  1205  		So(sortedTasks[1].DisplayName, ShouldEqual, "C")
  1206  		So(sortedTasks[2].DisplayName, ShouldEqual, "B")
  1207  		So(sortedTasks[3].DisplayName, ShouldEqual, "A")
  1208  
  1209  		Convey("when there are cross-variant dependencies on different tasks", func() {
  1210  
  1211  			tasks = append(tasks,
  1212  				task.Task{
  1213  					Id:          "idE",
  1214  					DisplayName: "E",
  1215  					DependsOn: []task.Dependency{
  1216  						{TaskId: "cross-variant2"},
  1217  					}},
  1218  				task.Task{
  1219  					Id:          "idF",
  1220  					DisplayName: "F",
  1221  					DependsOn: []task.Dependency{
  1222  						{TaskId: "idE"},
  1223  					}})
  1224  			sortedTasks = sortTasks(tasks)
  1225  			So(len(sortedTasks), ShouldEqual, 6)
  1226  			So(sortedTasks[0].DisplayName, ShouldEqual, "D")
  1227  			So(sortedTasks[1].DisplayName, ShouldEqual, "C")
  1228  			So(sortedTasks[2].DisplayName, ShouldEqual, "E")
  1229  			So(sortedTasks[3].DisplayName, ShouldEqual, "B")
  1230  			So(sortedTasks[4].DisplayName, ShouldEqual, "F")
  1231  			So(sortedTasks[5].DisplayName, ShouldEqual, "A")
  1232  		})
  1233  	})
  1234  }