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

     1  package task
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/evergreen-ci/evergreen"
     8  	"github.com/evergreen-ci/evergreen/apimodels"
     9  	"github.com/evergreen-ci/evergreen/db"
    10  	"github.com/evergreen-ci/evergreen/model/build"
    11  	"github.com/evergreen-ci/evergreen/model/distro"
    12  	"github.com/evergreen-ci/evergreen/model/host"
    13  	"github.com/evergreen-ci/evergreen/testutil"
    14  	"github.com/evergreen-ci/evergreen/util"
    15  	"github.com/mongodb/grip"
    16  	. "github.com/smartystreets/goconvey/convey"
    17  	"gopkg.in/mgo.v2/bson"
    18  )
    19  
    20  var (
    21  	conf  = testutil.TestConfig()
    22  	oneMs = time.Millisecond
    23  )
    24  
    25  func init() {
    26  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(conf))
    27  	grip.CatchError(grip.SetSender(testutil.SetupTestSender("")))
    28  }
    29  
    30  var depTaskIds = []Dependency{
    31  	{"td1", evergreen.TaskSucceeded},
    32  	{"td2", evergreen.TaskSucceeded},
    33  	{"td3", ""}, // Default == "success"
    34  	{"td4", evergreen.TaskFailed},
    35  	{"td5", AllStatuses},
    36  }
    37  
    38  // update statuses of test tasks in the db
    39  func updateTestDepTasks(t *testing.T) {
    40  	// cases for success/default
    41  	for _, depTaskId := range depTaskIds[:3] {
    42  		testutil.HandleTestingErr(UpdateOne(
    43  			bson.M{"_id": depTaskId.TaskId},
    44  			bson.M{"$set": bson.M{"status": evergreen.TaskSucceeded}},
    45  		), t, "Error setting task status")
    46  	}
    47  	// cases for * and failure
    48  	for _, depTaskId := range depTaskIds[3:] {
    49  		testutil.HandleTestingErr(UpdateOne(
    50  			bson.M{"_id": depTaskId.TaskId},
    51  			bson.M{"$set": bson.M{"status": evergreen.TaskFailed}},
    52  		), t, "Error setting task status")
    53  	}
    54  }
    55  
    56  func TestDependenciesMet(t *testing.T) {
    57  
    58  	var taskId string
    59  	var task *Task
    60  	var depTasks []*Task
    61  
    62  	Convey("With a task", t, func() {
    63  
    64  		taskId = "t1"
    65  
    66  		task = &Task{
    67  			Id: taskId,
    68  		}
    69  
    70  		depTasks = []*Task{
    71  			{Id: depTaskIds[0].TaskId, Status: evergreen.TaskUndispatched},
    72  			{Id: depTaskIds[1].TaskId, Status: evergreen.TaskUndispatched},
    73  			{Id: depTaskIds[2].TaskId, Status: evergreen.TaskUndispatched},
    74  			{Id: depTaskIds[3].TaskId, Status: evergreen.TaskUndispatched},
    75  			{Id: depTaskIds[4].TaskId, Status: evergreen.TaskUndispatched},
    76  		}
    77  
    78  		So(db.Clear(Collection), ShouldBeNil)
    79  		for _, depTask := range depTasks {
    80  			So(depTask.Insert(), ShouldBeNil)
    81  		}
    82  
    83  		Convey("if the task has no dependencies its dependencies should"+
    84  			" be met by default", func() {
    85  			task.DependsOn = []Dependency{}
    86  			met, err := task.DependenciesMet(map[string]Task{})
    87  			So(err, ShouldBeNil)
    88  			So(met, ShouldBeTrue)
    89  		})
    90  
    91  		Convey("if only some of the tasks dependencies are finished"+
    92  			" successfully, then it should not think its dependencies are met",
    93  			func() {
    94  				task.DependsOn = depTaskIds
    95  				So(UpdateOne(
    96  					bson.M{"_id": depTaskIds[0].TaskId},
    97  					bson.M{
    98  						"$set": bson.M{
    99  							"status": evergreen.TaskSucceeded,
   100  						},
   101  					},
   102  				), ShouldBeNil)
   103  				met, err := task.DependenciesMet(map[string]Task{})
   104  				So(err, ShouldBeNil)
   105  				So(met, ShouldBeFalse)
   106  			})
   107  
   108  		Convey("if all of the tasks dependencies are finished properly, it"+
   109  			" should correctly believe its dependencies are met", func() {
   110  			task.DependsOn = depTaskIds
   111  			updateTestDepTasks(t)
   112  			met, err := task.DependenciesMet(map[string]Task{})
   113  			So(err, ShouldBeNil)
   114  			So(met, ShouldBeTrue)
   115  		})
   116  
   117  		Convey("tasks not in the dependency cache should be pulled into the"+
   118  			" cache during dependency checking", func() {
   119  			dependencyCache := make(map[string]Task)
   120  			task.DependsOn = depTaskIds
   121  			updateTestDepTasks(t)
   122  			met, err := task.DependenciesMet(dependencyCache)
   123  			So(err, ShouldBeNil)
   124  			So(met, ShouldBeTrue)
   125  			for _, depTaskId := range depTaskIds[:4] {
   126  				So(dependencyCache[depTaskId.TaskId].Id, ShouldEqual, depTaskId.TaskId)
   127  			}
   128  			So(dependencyCache["td5"].Id, ShouldEqual, "td5")
   129  		})
   130  
   131  		Convey("cached dependencies should be used rather than fetching them"+
   132  			" from the database", func() {
   133  			updateTestDepTasks(t)
   134  			dependencyCache := make(map[string]Task)
   135  			task.DependsOn = depTaskIds
   136  			met, err := task.DependenciesMet(dependencyCache)
   137  			So(err, ShouldBeNil)
   138  			So(met, ShouldBeTrue)
   139  
   140  			// alter the dependency cache so that it should seem as if the
   141  			// dependencies are not met
   142  			cachedTask := dependencyCache[depTaskIds[0].TaskId]
   143  			So(cachedTask.Status, ShouldEqual, evergreen.TaskSucceeded)
   144  			cachedTask.Status = evergreen.TaskFailed
   145  			dependencyCache[depTaskIds[0].TaskId] = cachedTask
   146  			met, err = task.DependenciesMet(dependencyCache)
   147  			So(err, ShouldBeNil)
   148  			So(met, ShouldBeFalse)
   149  
   150  		})
   151  
   152  		Convey("extraneous tasks in the dependency cache should be ignored",
   153  			func() {
   154  				So(UpdateOne(
   155  					bson.M{"_id": depTaskIds[0].TaskId},
   156  					bson.M{
   157  						"$set": bson.M{
   158  							"status": evergreen.TaskSucceeded,
   159  						},
   160  					},
   161  				), ShouldBeNil)
   162  				So(UpdateOne(
   163  					bson.M{"_id": depTaskIds[1].TaskId},
   164  					bson.M{
   165  						"$set": bson.M{
   166  							"status": evergreen.TaskSucceeded,
   167  						},
   168  					},
   169  				), ShouldBeNil)
   170  				So(UpdateOne(
   171  					bson.M{"_id": depTaskIds[2].TaskId},
   172  					bson.M{
   173  						"$set": bson.M{
   174  							"status": evergreen.TaskFailed,
   175  						},
   176  					},
   177  				), ShouldBeNil)
   178  
   179  				dependencyCache := make(map[string]Task)
   180  				task.DependsOn = []Dependency{depTaskIds[0], depTaskIds[1],
   181  					depTaskIds[2]}
   182  				met, err := task.DependenciesMet(dependencyCache)
   183  				So(err, ShouldBeNil)
   184  				So(met, ShouldBeFalse)
   185  
   186  				// remove the failed task from the dependencies (but not from
   187  				// the cache).  it should be ignored in the next pass
   188  				task.DependsOn = []Dependency{depTaskIds[0], depTaskIds[1]}
   189  				met, err = task.DependenciesMet(dependencyCache)
   190  				So(err, ShouldBeNil)
   191  				So(met, ShouldBeTrue)
   192  			})
   193  	})
   194  }
   195  
   196  func TestSetTasksScheduledTime(t *testing.T) {
   197  	Convey("With some tasks", t, func() {
   198  
   199  		So(db.Clear(Collection), ShouldBeNil)
   200  
   201  		tasks := []Task{
   202  			{Id: "t1", ScheduledTime: util.ZeroTime},
   203  			{Id: "t2", ScheduledTime: util.ZeroTime},
   204  			{Id: "t3", ScheduledTime: util.ZeroTime},
   205  		}
   206  		for _, task := range tasks {
   207  			So(task.Insert(), ShouldBeNil)
   208  		}
   209  		Convey("when updating ScheduledTime for some of the tasks", func() {
   210  			testTime := time.Unix(31337, 0)
   211  			So(SetTasksScheduledTime(tasks[1:], testTime), ShouldBeNil)
   212  
   213  			Convey("the tasks should be updated in memory", func() {
   214  				So(tasks[0].ScheduledTime, ShouldResemble, util.ZeroTime)
   215  				So(tasks[1].ScheduledTime, ShouldResemble, testTime)
   216  				So(tasks[2].ScheduledTime, ShouldResemble, testTime)
   217  
   218  				Convey("and in the db", func() {
   219  					// Need to use a margin of error on time tests
   220  					// because of minor differences between how mongo
   221  					// and golang store dates. The date from the db
   222  					// can be interpreted as being a few nanoseconds off
   223  					t1, err := FindOne(ById("t1"))
   224  					So(err, ShouldBeNil)
   225  					So(t1.ScheduledTime.Round(oneMs), ShouldResemble, util.ZeroTime)
   226  					t2, err := FindOne(ById("t2"))
   227  					So(err, ShouldBeNil)
   228  					So(t2.ScheduledTime.Round(oneMs), ShouldResemble, testTime)
   229  					t3, err := FindOne(ById("t3"))
   230  					So(err, ShouldBeNil)
   231  					So(t3.ScheduledTime.Round(oneMs), ShouldResemble, testTime)
   232  				})
   233  
   234  				Convey("if we update a second time", func() {
   235  					newTime := time.Unix(99999999, 0)
   236  					So(newTime, ShouldHappenAfter, testTime)
   237  					So(SetTasksScheduledTime(tasks, newTime), ShouldBeNil)
   238  
   239  					Convey("only unset scheduled times should be updated", func() {
   240  						t1, err := FindOne(ById("t1"))
   241  						So(err, ShouldBeNil)
   242  						So(t1.ScheduledTime.Round(oneMs), ShouldResemble, newTime)
   243  						t2, err := FindOne(ById("t2"))
   244  						So(err, ShouldBeNil)
   245  						So(t2.ScheduledTime.Round(oneMs), ShouldResemble, testTime)
   246  						t3, err := FindOne(ById("t3"))
   247  						So(err, ShouldBeNil)
   248  						So(t3.ScheduledTime.Round(oneMs), ShouldResemble, testTime)
   249  					})
   250  				})
   251  
   252  			})
   253  
   254  		})
   255  	})
   256  }
   257  
   258  func TestTaskSetPriority(t *testing.T) {
   259  
   260  	Convey("With a task", t, func() {
   261  
   262  		testutil.HandleTestingErr(db.Clear(Collection), t, "Error clearing"+
   263  			" '%v' collection", Collection)
   264  
   265  		tasks := []Task{
   266  			{
   267  				Id:        "one",
   268  				DependsOn: []Dependency{{"two", ""}, {"three", ""}, {"four", ""}},
   269  				Activated: true,
   270  			},
   271  			{
   272  				Id:        "two",
   273  				Priority:  5,
   274  				Activated: true,
   275  			},
   276  			{
   277  				Id:        "three",
   278  				DependsOn: []Dependency{{"five", ""}},
   279  				Activated: true,
   280  			},
   281  			{
   282  				Id:        "four",
   283  				DependsOn: []Dependency{{"five", ""}},
   284  				Activated: true,
   285  			},
   286  			{
   287  				Id:        "five",
   288  				Activated: true,
   289  			},
   290  			{
   291  				Id:        "six",
   292  				Activated: true,
   293  			},
   294  		}
   295  
   296  		for _, task := range tasks {
   297  			So(task.Insert(), ShouldBeNil)
   298  		}
   299  
   300  		Convey("setting its priority should update it in-memory"+
   301  			" and update it and all dependencies in the database", func() {
   302  
   303  			So(tasks[0].SetPriority(1), ShouldBeNil)
   304  			So(tasks[0].Priority, ShouldEqual, 1)
   305  
   306  			task, err := FindOne(ById("one"))
   307  			So(err, ShouldBeNil)
   308  			So(task, ShouldNotBeNil)
   309  			So(task.Priority, ShouldEqual, 1)
   310  
   311  			task, err = FindOne(ById("two"))
   312  			So(err, ShouldBeNil)
   313  			So(task, ShouldNotBeNil)
   314  			So(task.Priority, ShouldEqual, 5)
   315  
   316  			task, err = FindOne(ById("three"))
   317  			So(err, ShouldBeNil)
   318  			So(task, ShouldNotBeNil)
   319  			So(task.Priority, ShouldEqual, 1)
   320  
   321  			task, err = FindOne(ById("four"))
   322  			So(err, ShouldBeNil)
   323  			So(task, ShouldNotBeNil)
   324  			So(task.Id, ShouldEqual, "four")
   325  			So(task.Priority, ShouldEqual, 1)
   326  
   327  			task, err = FindOne(ById("five"))
   328  			So(err, ShouldBeNil)
   329  			So(task, ShouldNotBeNil)
   330  			So(task.Id, ShouldEqual, "five")
   331  			So(task.Priority, ShouldEqual, 1)
   332  
   333  			task, err = FindOne(ById("six"))
   334  			So(err, ShouldBeNil)
   335  			So(task, ShouldNotBeNil)
   336  			So(task.Id, ShouldEqual, "six")
   337  			So(task.Priority, ShouldEqual, 0)
   338  
   339  		})
   340  
   341  		Convey("decreasing priority should update the task but not its dependencies", func() {
   342  
   343  			So(tasks[0].SetPriority(1), ShouldBeNil)
   344  			So(tasks[0].Activated, ShouldEqual, true)
   345  			So(tasks[0].SetPriority(-1), ShouldBeNil)
   346  			So(tasks[0].Priority, ShouldEqual, -1)
   347  
   348  			task, err := FindOne(ById("one"))
   349  			So(err, ShouldBeNil)
   350  			So(task, ShouldNotBeNil)
   351  			So(task.Priority, ShouldEqual, -1)
   352  			So(task.Activated, ShouldEqual, false)
   353  
   354  			task, err = FindOne(ById("two"))
   355  			So(err, ShouldBeNil)
   356  			So(task, ShouldNotBeNil)
   357  			So(task.Priority, ShouldEqual, 5)
   358  			So(task.Activated, ShouldEqual, true)
   359  
   360  			task, err = FindOne(ById("three"))
   361  			So(err, ShouldBeNil)
   362  			So(task, ShouldNotBeNil)
   363  			So(task.Priority, ShouldEqual, 1)
   364  			So(task.Activated, ShouldEqual, true)
   365  
   366  			task, err = FindOne(ById("four"))
   367  			So(err, ShouldBeNil)
   368  			So(task, ShouldNotBeNil)
   369  			So(task.Id, ShouldEqual, "four")
   370  			So(task.Priority, ShouldEqual, 1)
   371  			So(task.Activated, ShouldEqual, true)
   372  
   373  			task, err = FindOne(ById("five"))
   374  			So(err, ShouldBeNil)
   375  			So(task, ShouldNotBeNil)
   376  			So(task.Id, ShouldEqual, "five")
   377  			So(task.Priority, ShouldEqual, 1)
   378  			So(task.Activated, ShouldEqual, true)
   379  
   380  			task, err = FindOne(ById("six"))
   381  			So(err, ShouldBeNil)
   382  			So(task, ShouldNotBeNil)
   383  			So(task.Id, ShouldEqual, "six")
   384  			So(task.Priority, ShouldEqual, 0)
   385  			So(task.Activated, ShouldEqual, true)
   386  		})
   387  	})
   388  
   389  }
   390  
   391  func TestFindTasksByIds(t *testing.T) {
   392  	Convey("When calling FindTasksByIds...", t, func() {
   393  		So(db.Clear(Collection), ShouldBeNil)
   394  		Convey("only tasks with the specified ids should be returned", func() {
   395  
   396  			tasks := []Task{
   397  				{
   398  					Id: "one",
   399  				},
   400  				{
   401  					Id: "two",
   402  				},
   403  				{
   404  					Id: "three",
   405  				},
   406  			}
   407  
   408  			for _, task := range tasks {
   409  				So(task.Insert(), ShouldBeNil)
   410  			}
   411  
   412  			dbTasks, err := Find(ByIds([]string{"one", "two"}))
   413  			So(err, ShouldBeNil)
   414  			So(len(dbTasks), ShouldEqual, 2)
   415  			So(dbTasks[0].Id, ShouldNotEqual, "three")
   416  			So(dbTasks[1].Id, ShouldNotEqual, "three")
   417  		})
   418  	})
   419  }
   420  
   421  func TestCountSimilarFailingTasks(t *testing.T) {
   422  	Convey("When calling CountSimilarFailingTasks...", t, func() {
   423  		So(db.Clear(Collection), ShouldBeNil)
   424  		Convey("only failed tasks with the same project, requester, display "+
   425  			"name and revision but different buildvariants should be returned",
   426  			func() {
   427  				project := "project"
   428  				requester := "testing"
   429  				displayName := "compile"
   430  				buildVariant := "testVariant"
   431  				revision := "asdf ;lkj asdf ;lkj "
   432  
   433  				tasks := []Task{
   434  					{
   435  						Id:           "one",
   436  						Project:      project,
   437  						DisplayName:  displayName,
   438  						BuildVariant: buildVariant + "1",
   439  						Revision:     revision,
   440  						Requester:    requester,
   441  					},
   442  					{
   443  						Id:           "two",
   444  						Project:      project,
   445  						DisplayName:  displayName,
   446  						BuildVariant: buildVariant + "2",
   447  						Revision:     revision,
   448  						Requester:    requester,
   449  						Status:       evergreen.TaskFailed,
   450  					},
   451  					// task succeeded so should not be returned
   452  					{
   453  						Id:           "three",
   454  						Project:      project,
   455  						DisplayName:  displayName,
   456  						BuildVariant: buildVariant + "2",
   457  						Revision:     revision,
   458  						Requester:    requester,
   459  						Status:       evergreen.TaskSucceeded,
   460  					},
   461  					// same buildvariant so should not be returned
   462  					{
   463  						Id:           "four",
   464  						Project:      project,
   465  						DisplayName:  displayName,
   466  						BuildVariant: buildVariant + "1",
   467  						Revision:     revision,
   468  						Requester:    requester,
   469  						Status:       evergreen.TaskFailed,
   470  					},
   471  					// different project so should not be returned
   472  					{
   473  						Id:           "five",
   474  						Project:      project + "1",
   475  						DisplayName:  displayName,
   476  						BuildVariant: buildVariant + "2",
   477  						Revision:     revision,
   478  						Requester:    requester,
   479  						Status:       evergreen.TaskFailed,
   480  					},
   481  					// different requester so should not be returned
   482  					{
   483  						Id:           "six",
   484  						Project:      project,
   485  						DisplayName:  displayName,
   486  						BuildVariant: buildVariant + "2",
   487  						Revision:     revision,
   488  						Requester:    requester + "1",
   489  						Status:       evergreen.TaskFailed,
   490  					},
   491  					// different revision so should not be returned
   492  					{
   493  						Id:           "seven",
   494  						Project:      project,
   495  						DisplayName:  displayName,
   496  						BuildVariant: buildVariant + "1",
   497  						Revision:     revision + "1",
   498  						Requester:    requester,
   499  						Status:       evergreen.TaskFailed,
   500  					},
   501  					// different display name so should not be returned
   502  					{
   503  						Id:           "eight",
   504  						Project:      project,
   505  						DisplayName:  displayName + "1",
   506  						BuildVariant: buildVariant,
   507  						Revision:     revision,
   508  						Requester:    requester,
   509  						Status:       evergreen.TaskFailed,
   510  					},
   511  				}
   512  
   513  				for _, task := range tasks {
   514  					So(task.Insert(), ShouldBeNil)
   515  				}
   516  
   517  				dbTasks, err := tasks[0].CountSimilarFailingTasks()
   518  				So(err, ShouldBeNil)
   519  				So(dbTasks, ShouldEqual, 1)
   520  			})
   521  	})
   522  }
   523  
   524  func TestMarkAsDispatched(t *testing.T) {
   525  
   526  	var (
   527  		taskId   string
   528  		hostId   string
   529  		buildId  string
   530  		distroId string
   531  		task     *Task
   532  		myHost   *host.Host
   533  		b        *build.Build
   534  	)
   535  
   536  	Convey("With a task", t, func() {
   537  
   538  		taskId = "t1"
   539  		hostId = "h1"
   540  		buildId = "b1"
   541  		distroId = "d1"
   542  
   543  		task = &Task{
   544  			Id:      taskId,
   545  			BuildId: buildId,
   546  		}
   547  
   548  		myHost = &host.Host{
   549  			Id:     hostId,
   550  			Distro: distro.Distro{Id: distroId},
   551  		}
   552  
   553  		b = &build.Build{
   554  			Id: buildId,
   555  			Tasks: []build.TaskCache{
   556  				{Id: taskId},
   557  			},
   558  		}
   559  
   560  		testutil.HandleTestingErr(
   561  			db.ClearCollections(Collection, build.Collection, host.Collection),
   562  			t, "Error clearing test collections")
   563  
   564  		So(task.Insert(), ShouldBeNil)
   565  		So(myHost.Insert(), ShouldBeNil)
   566  		So(b.Insert(), ShouldBeNil)
   567  
   568  		Convey("when marking the task as dispatched, the fields for"+
   569  			" the task, the host it is on, and the build it is a part of"+
   570  			" should be set to reflect this", func() {
   571  
   572  			// mark the task as dispatched
   573  			So(task.MarkAsDispatched(myHost.Id, myHost.Distro.Id, time.Now()), ShouldBeNil)
   574  
   575  			// make sure the task's fields were updated, both in ©memory and
   576  			// in the db
   577  			So(task.DispatchTime, ShouldNotResemble, time.Unix(0, 0))
   578  			So(task.Status, ShouldEqual, evergreen.TaskDispatched)
   579  			So(task.HostId, ShouldEqual, myHost.Id)
   580  			So(task.LastHeartbeat, ShouldResemble, task.DispatchTime)
   581  			task, err := FindOne(ById(taskId))
   582  			So(err, ShouldBeNil)
   583  			So(task.DispatchTime, ShouldNotResemble, time.Unix(0, 0))
   584  			So(task.Status, ShouldEqual, evergreen.TaskDispatched)
   585  			So(task.HostId, ShouldEqual, myHost.Id)
   586  			So(task.LastHeartbeat, ShouldResemble, task.DispatchTime)
   587  
   588  		})
   589  
   590  	})
   591  
   592  }
   593  
   594  func TestTimeAggregations(t *testing.T) {
   595  	Convey("With multiple tasks with different times", t, func() {
   596  		So(db.Clear(Collection), ShouldBeNil)
   597  		task1 := Task{Id: "bogus",
   598  			ScheduledTime: time.Unix(1000, 0),
   599  			StartTime:     time.Unix(1010, 0),
   600  			FinishTime:    time.Unix(1030, 0),
   601  			DistroId:      "osx"}
   602  		task2 := Task{Id: "fake",
   603  			ScheduledTime: time.Unix(1000, 0),
   604  			StartTime:     time.Unix(1020, 0),
   605  			FinishTime:    time.Unix(1050, 0),
   606  			DistroId:      "osx"}
   607  		task3 := Task{Id: "placeholder",
   608  			ScheduledTime: time.Unix(1000, 0),
   609  			StartTime:     time.Unix(1060, 0),
   610  			FinishTime:    time.Unix(1180, 0),
   611  			DistroId:      "templOS"}
   612  		So(task1.Insert(), ShouldBeNil)
   613  		So(task2.Insert(), ShouldBeNil)
   614  		So(task3.Insert(), ShouldBeNil)
   615  
   616  		Convey("on an aggregation on FinishTime - StartTime", func() {
   617  			timeMap, err := AverageTaskTimeDifference(
   618  				StartTimeKey,
   619  				FinishTimeKey,
   620  				DistroIdKey,
   621  				util.ZeroTime)
   622  			So(err, ShouldBeNil)
   623  
   624  			Convey("the proper averages should be computed", func() {
   625  				// osx = ([1030-1010] + [1050-1020])/2 = (20+30)/2 = 25
   626  				So(timeMap["osx"].Seconds(), ShouldEqual, 25)
   627  				// templOS = (1180 - 1060)/1 = 120/1 = 120
   628  				So(timeMap["templOS"].Seconds(), ShouldEqual, 120)
   629  			})
   630  		})
   631  
   632  		Convey("on an aggregation on StartTime - ScheduledTime", func() {
   633  			timeMap, err := AverageTaskTimeDifference(
   634  				ScheduledTimeKey,
   635  				StartTimeKey,
   636  				DistroIdKey,
   637  				util.ZeroTime)
   638  			So(err, ShouldBeNil)
   639  
   640  			Convey("the proper averages should be computed", func() {
   641  				// osx = ([1010-1000] + [1020-1000])/2 = (10+20)/2 = 15
   642  				So(timeMap["osx"].Seconds(), ShouldEqual, 15)
   643  				// templOS = (1060-1000)/1 = 60/1 = 60
   644  				So(timeMap["templOS"].Seconds(), ShouldEqual, 60)
   645  			})
   646  		})
   647  
   648  		Convey("but when given non-time fields", func() {
   649  
   650  			Convey("most cases should return an empty map", func() {
   651  				timeMap, err := AverageTaskTimeDifference(
   652  					IdKey,
   653  					DistroIdKey,
   654  					DistroIdKey,
   655  					util.ZeroTime)
   656  				So(len(timeMap), ShouldEqual, 0)
   657  				So(err, ShouldBeNil)
   658  				timeMap, err = AverageTaskTimeDifference(
   659  					DistroIdKey,
   660  					SecretKey,
   661  					DistroIdKey,
   662  					util.ZeroTime)
   663  				So(len(timeMap), ShouldEqual, 0)
   664  				So(err, ShouldBeNil)
   665  			})
   666  
   667  			Convey("special key cases should cause real agg errors", func() {
   668  				timeMap, err := AverageTaskTimeDifference(
   669  					StartTimeKey,
   670  					"$$$$$$",
   671  					DistroIdKey,
   672  					util.ZeroTime)
   673  				So(len(timeMap), ShouldEqual, 0)
   674  				So(err, ShouldNotBeNil)
   675  			})
   676  		})
   677  	})
   678  }
   679  
   680  func TestEndingTask(t *testing.T) {
   681  	Convey("With tasks that are attempting to be marked as finished", t, func() {
   682  		So(db.Clear(Collection), ShouldBeNil)
   683  		Convey("a task that has a start time set", func() {
   684  			now := time.Now()
   685  			t := &Task{
   686  				Id:        "taskId",
   687  				Status:    evergreen.TaskStarted,
   688  				StartTime: now.Add(-5 * time.Minute),
   689  			}
   690  			So(t.Insert(), ShouldBeNil)
   691  			details := &apimodels.TaskEndDetail{
   692  				Status: evergreen.TaskFailed,
   693  			}
   694  
   695  			So(t.MarkEnd(now, details), ShouldBeNil)
   696  			t, err := FindOne(ById(t.Id))
   697  			So(err, ShouldBeNil)
   698  			So(t.Status, ShouldEqual, evergreen.TaskFailed)
   699  			So(t.FinishTime.Unix(), ShouldEqual, now.Unix())
   700  			So(t.StartTime.Unix(), ShouldEqual, now.Add(-5*time.Minute).Unix())
   701  		})
   702  		Convey("a task with no start time set should have one added", func() {
   703  			now := time.Now()
   704  			Convey("a task with a create time < 2 hours should have the start time set to the create time", func() {
   705  				t := &Task{
   706  					Id:         "tid",
   707  					Status:     evergreen.TaskDispatched,
   708  					CreateTime: now.Add(-30 * time.Minute),
   709  				}
   710  				So(t.Insert(), ShouldBeNil)
   711  				details := &apimodels.TaskEndDetail{
   712  					Status: evergreen.TaskFailed,
   713  				}
   714  				So(t.MarkEnd(now, details), ShouldBeNil)
   715  				t, err := FindOne(ById(t.Id))
   716  				So(err, ShouldBeNil)
   717  				So(t.StartTime.Unix(), ShouldEqual, t.CreateTime.Unix())
   718  				So(t.FinishTime.Unix(), ShouldEqual, now.Unix())
   719  			})
   720  			Convey("a task with a create time > 2 hours should have the start time set to two hours"+
   721  				"before the finish time", func() {
   722  				t := &Task{
   723  					Id:         "tid",
   724  					Status:     evergreen.TaskDispatched,
   725  					CreateTime: now.Add(-3 * time.Hour),
   726  				}
   727  				So(t.Insert(), ShouldBeNil)
   728  				details := &apimodels.TaskEndDetail{
   729  					Status: evergreen.TaskFailed,
   730  				}
   731  				So(t.MarkEnd(now, details), ShouldBeNil)
   732  				t, err := FindOne(ById(t.Id))
   733  				So(err, ShouldBeNil)
   734  				startTime := now.Add(-2 * time.Hour)
   735  				So(t.StartTime.Unix(), ShouldEqual, startTime.Unix())
   736  				So(t.FinishTime.Unix(), ShouldEqual, now.Unix())
   737  			})
   738  
   739  		})
   740  
   741  	})
   742  }