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

     1  package scheduler
     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"
    10  	"github.com/evergreen-ci/evergreen/model/task"
    11  	"github.com/evergreen-ci/evergreen/testutil"
    12  	"github.com/mongodb/grip"
    13  	. "github.com/smartystreets/goconvey/convey"
    14  )
    15  
    16  var taskDurationEstimatorTestConf = testutil.TestConfig()
    17  
    18  func init() {
    19  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(taskDurationEstimatorTestConf))
    20  	grip.CatchError(grip.SetSender(testutil.SetupTestSender(taskDurationEstimatorTestConf.Scheduler.LogFile)))
    21  }
    22  
    23  func TestDBTaskDurationEstimator(t *testing.T) {
    24  	var taskDurationEstimator *DBTaskDurationEstimator
    25  	var displayNames []string
    26  	var buildVariants []string
    27  	var projects []string
    28  	var taskIds []string
    29  	var timeTaken []time.Duration
    30  	var runnableTasks []task.Task
    31  
    32  	Convey("With a DBTaskDurationEstimator...", t,
    33  		func() {
    34  			taskDurationEstimator = &DBTaskDurationEstimator{}
    35  			taskIds = []string{"t1", "t2", "t3", "t4", "t5", "t6",
    36  				"t7", "t8", "t9"}
    37  			displayNames = []string{"dn1", "dn2", "dn3", "dn4", "dn5", "dn6"}
    38  			buildVariants = []string{"bv1", "bv2", "bv3", "bv4", "bv5", "bv6"}
    39  			projects = []string{"p1", "p2", "p3", "p4", "p5", "p6"}
    40  			timeTaken = []time.Duration{
    41  				time.Duration(1) * time.Minute, time.Duration(2) * time.Minute,
    42  				time.Duration(3) * time.Minute, time.Duration(4) * time.Minute,
    43  				time.Duration(5) * time.Minute, time.Duration(6) * time.Minute,
    44  			}
    45  
    46  			Convey("the expected task duration for tasks with different "+
    47  				"display names, same project and buildvariant, should be "+
    48  				"distinct",
    49  				func() {
    50  					runnableTasks = []task.Task{
    51  						// task 0/7 are from the same project and buildvariant
    52  						// and have different display names - expected duration
    53  						// should not be averaged between these two
    54  						{
    55  							Id:           taskIds[0],
    56  							DisplayName:  displayNames[0],
    57  							BuildVariant: buildVariants[0],
    58  							TimeTaken:    timeTaken[0],
    59  							Project:      projects[0],
    60  							Status:       evergreen.TaskSucceeded,
    61  							StartTime:    time.Now().Add(-timeTaken[0]),
    62  							FinishTime:   time.Now(),
    63  						},
    64  						{
    65  							Id:           taskIds[7],
    66  							DisplayName:  displayNames[1],
    67  							BuildVariant: buildVariants[0],
    68  							TimeTaken:    timeTaken[4],
    69  							Project:      projects[0],
    70  							Status:       evergreen.TaskSucceeded,
    71  							StartTime:    time.Now().Add(-timeTaken[4]),
    72  							FinishTime:   time.Now(),
    73  						},
    74  					}
    75  
    76  					So(db.Clear(task.Collection), ShouldBeNil)
    77  
    78  					// insert all the test tasks
    79  					for _, testTask := range runnableTasks {
    80  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
    81  							"insert task")
    82  					}
    83  
    84  					taskDurations, err := taskDurationEstimator.
    85  						GetExpectedDurations(runnableTasks)
    86  
    87  					So(err, ShouldEqual, nil)
    88  
    89  					projectDurations := taskDurations.TaskDurationByProject
    90  					// we only have 1 project for all runnable tasks
    91  					So(len(projectDurations), ShouldEqual, 1)
    92  
    93  					bvDurs := projectDurations[projects[0]].
    94  						TaskDurationByBuildVariant
    95  					// we only have 1 buildvariant for all runnable tasks
    96  					So(len(bvDurs), ShouldEqual, 1)
    97  
    98  					tkDurs := bvDurs[buildVariants[0]].TaskDurationByDisplayName
    99  					// we have 2 runnable tasks
   100  					So(len(tkDurs), ShouldEqual, 2)
   101  
   102  					// ensure the expected durations are as expected
   103  					So(tkDurs[displayNames[0]], ShouldEqual, timeTaken[0])
   104  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[4])
   105  				},
   106  			)
   107  
   108  			Convey("the expected task duration for tasks with same display "+
   109  				"name same project but different buildvariant should be "+
   110  				"distinct",
   111  				func() {
   112  					runnableTasks = []task.Task{
   113  						// task 1/4 are from the same project but from different
   114  						// buildvariants and have the same display names -
   115  						// expected duration should not be averaged between
   116  						// these two
   117  						{
   118  							Id:           taskIds[1],
   119  							DisplayName:  displayNames[1],
   120  							BuildVariant: buildVariants[3],
   121  							TimeTaken:    timeTaken[2],
   122  							Project:      projects[2],
   123  							Status:       evergreen.TaskSucceeded,
   124  							StartTime:    time.Now().Add(-timeTaken[2]),
   125  							FinishTime:   time.Now(),
   126  						},
   127  						{
   128  							Id:           taskIds[4],
   129  							DisplayName:  displayNames[1],
   130  							BuildVariant: buildVariants[4],
   131  							TimeTaken:    timeTaken[0],
   132  							Project:      projects[2],
   133  							Status:       evergreen.TaskSucceeded,
   134  							StartTime:    time.Now().Add(-timeTaken[0]),
   135  							FinishTime:   time.Now(),
   136  						},
   137  					}
   138  
   139  					So(db.Clear(task.Collection), ShouldBeNil)
   140  
   141  					// insert all the test tasks
   142  					for _, testTask := range runnableTasks {
   143  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   144  							" insert task")
   145  					}
   146  
   147  					taskDurations, err := taskDurationEstimator.
   148  						GetExpectedDurations(runnableTasks)
   149  
   150  					So(err, ShouldEqual, nil)
   151  
   152  					projectDurations := taskDurations.TaskDurationByProject
   153  					// we only have 1 project for all runnable tasks
   154  					So(len(projectDurations), ShouldEqual, 1)
   155  
   156  					bvDurs := projectDurations[projects[2]].
   157  						TaskDurationByBuildVariant
   158  					// we have 2 buildvariants for project 2
   159  					So(len(bvDurs), ShouldEqual, 2)
   160  
   161  					tkDurs := bvDurs[buildVariants[3]].TaskDurationByDisplayName
   162  					// we have 1 runnable task for buildvariant 3
   163  					So(len(tkDurs), ShouldEqual, 1)
   164  
   165  					// ensure the expected duration is as expected
   166  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[2])
   167  
   168  					tkDurs = bvDurs[buildVariants[4]].TaskDurationByDisplayName
   169  					// we have 1 runnable task for buildvariant 4
   170  					So(len(tkDurs), ShouldEqual, 1)
   171  
   172  					// ensure the expected duration is as expected
   173  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[0])
   174  				},
   175  			)
   176  
   177  			Convey("the expected task duration for tasks with same display "+
   178  				"name same buildvariant and from same project should be "+
   179  				"averaged",
   180  				func() {
   181  					runnableTasks = []task.Task{
   182  						// task 2/3 are from the same buildvariant, have the
   183  						// same display name amd are from same projects -
   184  						// expected duration should be average across those
   185  						// projects
   186  						{
   187  							Id:           taskIds[3],
   188  							DisplayName:  displayNames[3],
   189  							BuildVariant: buildVariants[3],
   190  							TimeTaken:    timeTaken[3],
   191  							Project:      projects[3],
   192  							Status:       evergreen.TaskSucceeded,
   193  							StartTime:    time.Now().Add(-timeTaken[3]),
   194  							FinishTime:   time.Now(),
   195  						},
   196  						{
   197  							Id:           taskIds[4],
   198  							DisplayName:  displayNames[3],
   199  							BuildVariant: buildVariants[3],
   200  							TimeTaken:    timeTaken[4],
   201  							Project:      projects[3],
   202  							Status:       evergreen.TaskSucceeded,
   203  							StartTime:    time.Now().Add(-timeTaken[4]),
   204  							FinishTime:   time.Now(),
   205  						},
   206  					}
   207  
   208  					So(db.Clear(task.Collection), ShouldBeNil)
   209  
   210  					// insert all the test tasks
   211  					for _, testTask := range runnableTasks {
   212  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   213  							"insert task")
   214  					}
   215  
   216  					taskDurations, err := taskDurationEstimator.
   217  						GetExpectedDurations(runnableTasks)
   218  
   219  					So(err, ShouldEqual, nil)
   220  
   221  					projectDurations := taskDurations.TaskDurationByProject
   222  					// we have 1 project for all runnable tasks
   223  					So(len(projectDurations), ShouldEqual, 1)
   224  
   225  					// check the results for project 4
   226  					bvDurs := projectDurations[projects[3]].
   227  						TaskDurationByBuildVariant
   228  					// we have 1 buildvariant for project 3
   229  					So(len(bvDurs), ShouldEqual, 1)
   230  
   231  					tkDurs := bvDurs[buildVariants[3]].TaskDurationByDisplayName
   232  					// we have 1 task duration for display name tasks
   233  					// for buildvariant 3
   234  					So(len(tkDurs), ShouldEqual, 1)
   235  
   236  					// ensure the expected duration is as expected
   237  					expDur := (timeTaken[3] + timeTaken[4]) / 2
   238  					So(tkDurs[displayNames[3]], ShouldEqual, expDur)
   239  				},
   240  			)
   241  
   242  			Convey("the expected task duration for tasks with same display "+
   243  				"name same buildvariant but from different project should be "+
   244  				"distinct",
   245  				func() {
   246  					runnableTasks = []task.Task{
   247  						// task 4/5 are from the same buildvariant, have the
   248  						// same display name but are from different projects -
   249  						// expected duration should be distinct across those
   250  						// projects
   251  						{
   252  							Id:           taskIds[4],
   253  							DisplayName:  displayNames[1],
   254  							BuildVariant: buildVariants[1],
   255  							TimeTaken:    timeTaken[4],
   256  							Project:      projects[4],
   257  							Status:       evergreen.TaskSucceeded,
   258  							StartTime:    time.Now().Add(-timeTaken[4]),
   259  							FinishTime:   time.Now(),
   260  						},
   261  						{
   262  							Id:           taskIds[5],
   263  							DisplayName:  displayNames[1],
   264  							BuildVariant: buildVariants[1],
   265  							TimeTaken:    timeTaken[5],
   266  							Project:      projects[5],
   267  							Status:       evergreen.TaskSucceeded,
   268  							StartTime:    time.Now().Add(-timeTaken[5]),
   269  							FinishTime:   time.Now(),
   270  						},
   271  					}
   272  
   273  					So(db.Clear(task.Collection), ShouldBeNil)
   274  
   275  					// insert all the test tasks
   276  					for _, testTask := range runnableTasks {
   277  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   278  							"insert task")
   279  					}
   280  
   281  					taskDurations, err := taskDurationEstimator.
   282  						GetExpectedDurations(runnableTasks)
   283  
   284  					So(err, ShouldEqual, nil)
   285  
   286  					projectDurations := taskDurations.TaskDurationByProject
   287  					// we have 2 projects for all runnable tasks
   288  					So(len(projectDurations), ShouldEqual, 2)
   289  
   290  					// check the results for project 4
   291  					bvDurs := projectDurations[projects[4]].
   292  						TaskDurationByBuildVariant
   293  					// we have 1 buildvariant for project 4
   294  					So(len(bvDurs), ShouldEqual, 1)
   295  
   296  					tkDurs := bvDurs[buildVariants[1]].TaskDurationByDisplayName
   297  					// we have 1 runnable task for buildvariant 1
   298  					So(len(tkDurs), ShouldEqual, 1)
   299  
   300  					// ensure the expected duration is as expected
   301  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[4])
   302  
   303  					// check the results for project 5
   304  					bvDurs = projectDurations[projects[5]].
   305  						TaskDurationByBuildVariant
   306  					// we have 1 buildvariant for project 4
   307  					So(len(bvDurs), ShouldEqual, 1)
   308  
   309  					tkDurs = bvDurs[buildVariants[1]].TaskDurationByDisplayName
   310  					// we have 1 runnable task for buildvariant 1
   311  					So(len(tkDurs), ShouldEqual, 1)
   312  
   313  					// ensure the expected duration is as expected
   314  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[5])
   315  				},
   316  			)
   317  
   318  			Convey("the expected task duration for tasks should only pick up "+
   319  				"tasks that are completed",
   320  				func() {
   321  					runnableTasks = []task.Task{
   322  						// task 4/5 are from the same buildvariant, have the
   323  						// same display name and are from the same project -
   324  						// however task 4 is not yet dispatched so shouldn't
   325  						// count toward the expected duration
   326  						{
   327  							Id:           taskIds[4],
   328  							DisplayName:  displayNames[1],
   329  							BuildVariant: buildVariants[1],
   330  							TimeTaken:    timeTaken[4],
   331  							Project:      projects[4],
   332  							Status:       evergreen.TaskUndispatched,
   333  							StartTime:    time.Now().Add(-timeTaken[4]),
   334  							FinishTime:   time.Now(),
   335  						},
   336  						{
   337  							Id:           taskIds[5],
   338  							DisplayName:  displayNames[1],
   339  							BuildVariant: buildVariants[1],
   340  							TimeTaken:    timeTaken[5],
   341  							Project:      projects[4],
   342  							Status:       evergreen.TaskSucceeded,
   343  							StartTime:    time.Now().Add(-timeTaken[5]),
   344  							FinishTime:   time.Now(),
   345  						},
   346  					}
   347  
   348  					So(db.Clear(task.Collection), ShouldBeNil)
   349  
   350  					// insert all the test tasks
   351  					for _, testTask := range runnableTasks {
   352  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   353  							"insert task")
   354  					}
   355  
   356  					taskDurations, err := taskDurationEstimator.
   357  						GetExpectedDurations(runnableTasks)
   358  
   359  					So(err, ShouldEqual, nil)
   360  
   361  					projectDurations := taskDurations.TaskDurationByProject
   362  					// we have 1 project for all runnable tasks
   363  					So(len(projectDurations), ShouldEqual, 1)
   364  
   365  					// check the results for project 4
   366  					bvDurs := projectDurations[projects[4]].
   367  						TaskDurationByBuildVariant
   368  
   369  					// we have 1 buildvariant for project 4
   370  					So(len(bvDurs), ShouldEqual, 1)
   371  
   372  					tkDurs := bvDurs[buildVariants[1]].TaskDurationByDisplayName
   373  					// we have 1 runnable task for buildvariant 1
   374  					// since task 4 is undispatched
   375  					So(len(tkDurs), ShouldEqual, 1)
   376  
   377  					// ensure the expected duration is that of task 5
   378  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[5])
   379  				},
   380  			)
   381  
   382  			Convey("the expected task duration for tasks should only pick up "+
   383  				"tasks that are completed within the specified window",
   384  				func() {
   385  					runnableTasks = []task.Task{
   386  						// task 4/5 are from the same buildvariant, have the
   387  						// same display name and are from the same project -
   388  						// however task 4 did not finish within the specified
   389  						// window and should thus, not count toward the expected
   390  						//  duration
   391  						{
   392  							Id:           taskIds[4],
   393  							DisplayName:  displayNames[1],
   394  							BuildVariant: buildVariants[1],
   395  							TimeTaken:    timeTaken[4],
   396  							Project:      projects[4],
   397  							Status:       evergreen.TaskSucceeded,
   398  							StartTime:    time.Now().Add(-timeTaken[4]),
   399  							FinishTime: time.Now().
   400  								Add(-model.TaskCompletionEstimateWindow).
   401  								Add(-model.TaskCompletionEstimateWindow),
   402  						},
   403  						{
   404  							Id:           taskIds[5],
   405  							DisplayName:  displayNames[1],
   406  							BuildVariant: buildVariants[1],
   407  							TimeTaken:    timeTaken[3],
   408  							Project:      projects[4],
   409  							Status:       evergreen.TaskSucceeded,
   410  							StartTime:    time.Now().Add(-timeTaken[3]),
   411  							FinishTime:   time.Now(),
   412  						},
   413  					}
   414  
   415  					So(db.Clear(task.Collection), ShouldBeNil)
   416  
   417  					// insert all the test tasks
   418  					for _, testTask := range runnableTasks {
   419  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   420  							"insert task")
   421  					}
   422  
   423  					taskDurations, err := taskDurationEstimator.
   424  						GetExpectedDurations(runnableTasks)
   425  
   426  					So(err, ShouldEqual, nil)
   427  
   428  					projectDurations := taskDurations.TaskDurationByProject
   429  					// we have 1 project for all runnable tasks
   430  					So(len(projectDurations), ShouldEqual, 1)
   431  
   432  					// check the results for project 4
   433  					bvDurs := projectDurations[projects[4]].
   434  						TaskDurationByBuildVariant
   435  
   436  					// we have 1 buildvariant for project 4
   437  					So(len(bvDurs), ShouldEqual, 1)
   438  
   439  					tkDurs := bvDurs[buildVariants[1]].TaskDurationByDisplayName
   440  					// we have 1 task for buildvariant 1
   441  					// since task 4 is did not finish within the window
   442  					So(len(tkDurs), ShouldEqual, 1)
   443  
   444  					// ensure the expected duration is that of task 5
   445  					So(tkDurs[displayNames[1]], ShouldEqual, timeTaken[3])
   446  				},
   447  			)
   448  
   449  			Convey("the expected task duration for tasks where several "+
   450  				"projects that contain a mix of completed/uncompleted, for \n"+
   451  				"several buildvariants/tasks should ignore uncompleted tasks "+
   452  				"and average tasks with the same display name",
   453  				func() {
   454  					runnableTasks = []task.Task{
   455  						// task 0/7 are from the same project and buildvariant
   456  						// and have different display names - expected duration
   457  						// should thus be averaged between these two - even
   458  						// though task 0 succeeded and 7 failed
   459  						{
   460  							Id:           taskIds[0],
   461  							DisplayName:  displayNames[0],
   462  							BuildVariant: buildVariants[0],
   463  							TimeTaken:    timeTaken[0],
   464  							Project:      projects[0],
   465  							Status:       evergreen.TaskSucceeded,
   466  							StartTime:    time.Now().Add(-timeTaken[0]),
   467  							FinishTime:   time.Now(),
   468  						},
   469  						{
   470  							Id:           taskIds[7],
   471  							DisplayName:  displayNames[0],
   472  							BuildVariant: buildVariants[0],
   473  							TimeTaken:    timeTaken[4],
   474  							Project:      projects[0],
   475  							Status:       evergreen.TaskFailed,
   476  							StartTime:    time.Now().Add(-timeTaken[4]),
   477  							FinishTime:   time.Now(),
   478  						},
   479  						// task 1 is the only runnable task with this project,
   480  						// buildvariant & display name
   481  						// should be exactly equal to its time taken
   482  						{
   483  							Id:           taskIds[1],
   484  							DisplayName:  displayNames[1],
   485  							BuildVariant: buildVariants[1],
   486  							TimeTaken:    timeTaken[1],
   487  							Project:      projects[1],
   488  							Status:       evergreen.TaskSucceeded,
   489  							StartTime:    time.Now().Add(-timeTaken[1]),
   490  							FinishTime:   time.Now(),
   491  						},
   492  						// task 2 is the only runnable task with this project,
   493  						// buildvariant & display name - so the expected
   494  						// duration should be exactly equal to its time taken
   495  						{
   496  							Id:           taskIds[2],
   497  							DisplayName:  displayNames[2],
   498  							BuildVariant: buildVariants[2],
   499  							TimeTaken:    timeTaken[2],
   500  							Project:      projects[2],
   501  							Status:       evergreen.TaskFailed,
   502  							StartTime:    time.Now().Add(-timeTaken[2]),
   503  							FinishTime:   time.Now(),
   504  						},
   505  						// task 3/6 are from the same project and buildvariant
   506  						// but have different display names - expected duration
   507  						// should thus not be averaged between these two
   508  						{
   509  							Id:           taskIds[3],
   510  							DisplayName:  displayNames[3],
   511  							BuildVariant: buildVariants[3],
   512  							TimeTaken:    timeTaken[3],
   513  							Project:      projects[3],
   514  							Status:       evergreen.TaskSucceeded,
   515  							StartTime:    time.Now().Add(-timeTaken[3]),
   516  							FinishTime:   time.Now(),
   517  						},
   518  						{
   519  							Id:           taskIds[6],
   520  							DisplayName:  displayNames[4],
   521  							BuildVariant: buildVariants[3],
   522  							TimeTaken:    timeTaken[5],
   523  							Project:      projects[3],
   524  							Status:       evergreen.TaskSucceeded,
   525  							StartTime:    time.Now().Add(-timeTaken[5]),
   526  							FinishTime:   time.Now(),
   527  						},
   528  						// task 4/5 are from the same project and buildvariant
   529  						// and have the same display name - expected duration
   530  						// should thus be averaged between these two
   531  						{
   532  							Id:           taskIds[4],
   533  							DisplayName:  displayNames[4],
   534  							BuildVariant: buildVariants[4],
   535  							TimeTaken:    timeTaken[4],
   536  							Project:      projects[4],
   537  							Status:       evergreen.TaskFailed,
   538  							StartTime:    time.Now().Add(-timeTaken[4]),
   539  							FinishTime:   time.Now(),
   540  						},
   541  						{
   542  							Id:           taskIds[5],
   543  							DisplayName:  displayNames[4],
   544  							BuildVariant: buildVariants[4],
   545  							TimeTaken:    timeTaken[5],
   546  							Project:      projects[4],
   547  							Status:       evergreen.TaskFailed,
   548  							StartTime:    time.Now().Add(-timeTaken[5]),
   549  							FinishTime:   time.Now(),
   550  						},
   551  						// task 9 is undispatched so shouldn't
   552  						// count toward the expected duration for
   553  						// project/buildvariant/display name 4
   554  						{
   555  							Id:           taskIds[8],
   556  							DisplayName:  displayNames[4],
   557  							BuildVariant: buildVariants[4],
   558  							TimeTaken:    timeTaken[5],
   559  							Project:      projects[4],
   560  							Status:       evergreen.TaskUndispatched,
   561  							StartTime:    time.Now().Add(-timeTaken[5]),
   562  							FinishTime:   time.Now(),
   563  						},
   564  					}
   565  
   566  					So(db.Clear(task.Collection), ShouldBeNil)
   567  
   568  					// insert all the test tasks
   569  					for _, testTask := range runnableTasks {
   570  						testutil.HandleTestingErr(testTask.Insert(), t, "failed to "+
   571  							"insert task")
   572  					}
   573  
   574  					taskDurations, err := taskDurationEstimator.
   575  						GetExpectedDurations(runnableTasks)
   576  					testutil.HandleTestingErr(err, t, "failed to get task "+
   577  						"durations")
   578  					projectDurations := taskDurations.
   579  						TaskDurationByProject
   580  
   581  					// we have five projects for all runnable tasks
   582  					So(len(projectDurations), ShouldEqual, 5)
   583  					taskDuration := projectDurations[projects[0]].
   584  						TaskDurationByBuildVariant[buildVariants[0]].
   585  						TaskDurationByDisplayName[displayNames[0]]
   586  					So(taskDuration, ShouldEqual,
   587  						(timeTaken[0]+timeTaken[4])/2)
   588  					taskDuration = projectDurations[projects[1]].
   589  						TaskDurationByBuildVariant[buildVariants[1]].
   590  						TaskDurationByDisplayName[displayNames[1]]
   591  					So(taskDuration, ShouldEqual, timeTaken[1])
   592  					taskDuration = projectDurations[projects[2]].
   593  						TaskDurationByBuildVariant[buildVariants[2]].
   594  						TaskDurationByDisplayName[displayNames[2]]
   595  					So(taskDuration, ShouldEqual, timeTaken[2])
   596  					taskDuration = projectDurations[projects[3]].
   597  						TaskDurationByBuildVariant[buildVariants[3]].
   598  						TaskDurationByDisplayName[displayNames[3]]
   599  					So(taskDuration, ShouldEqual, timeTaken[3])
   600  					taskDuration = projectDurations[projects[4]].
   601  						TaskDurationByBuildVariant[buildVariants[4]].
   602  						TaskDurationByDisplayName[displayNames[4]]
   603  					// should be avearge of tasks 4/5
   604  					So(taskDuration, ShouldEqual,
   605  						(timeTaken[4]+timeTaken[5])/2)
   606  				},
   607  			)
   608  		},
   609  	)
   610  }