github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/alerts/triggers_test.go (about)

     1  package alerts
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/evergreen-ci/evergreen"
     9  	"github.com/evergreen-ci/evergreen/db"
    10  	"github.com/evergreen-ci/evergreen/model"
    11  	"github.com/evergreen-ci/evergreen/model/alertrecord"
    12  	"github.com/evergreen-ci/evergreen/model/host"
    13  	"github.com/evergreen-ci/evergreen/model/task"
    14  	"github.com/evergreen-ci/evergreen/model/version"
    15  	"github.com/evergreen-ci/evergreen/testutil"
    16  	. "github.com/smartystreets/goconvey/convey"
    17  	"gopkg.in/mgo.v2/bson"
    18  )
    19  
    20  var (
    21  	schedulerTestConf = testutil.TestConfig()
    22  )
    23  
    24  func init() {
    25  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(schedulerTestConf))
    26  }
    27  
    28  // Used as a template for task objects inserted/queried on within this file
    29  var testTask = &task.Task{
    30  	Id:                  "testTask",
    31  	Status:              evergreen.TaskFailed,
    32  	DisplayName:         "compile",
    33  	Project:             "testProject",
    34  	BuildVariant:        "testVariant",
    35  	Version:             "testVersion",
    36  	Revision:            "aaa",
    37  	RevisionOrderNumber: 3,
    38  }
    39  
    40  var testProject = model.ProjectRef{
    41  	Identifier: "testProject",
    42  	BatchTime:  5,
    43  }
    44  
    45  var testVersion = version.Version{
    46  	Identifier: "testProject",
    47  	Requester:  evergreen.RepotrackerVersionRequester,
    48  	Revision:   "aaa",
    49  	Config: `
    50  buildvariants:
    51  - name: bv1
    52    batchtime: 60
    53  - name: bv2
    54    batchtime: 1
    55  - name: bv3
    56  `}
    57  
    58  func hasTrigger(triggers []Trigger, t Trigger) bool {
    59  	for _, trig := range triggers {
    60  		if trig.Id() == t.Id() {
    61  			return true
    62  		}
    63  	}
    64  	return false
    65  }
    66  
    67  func TestEmptyTaskTriggers(t *testing.T) {
    68  	testutil.HandleTestingErr(db.Clear(task.Collection), t, "problem clearing collection")
    69  	testutil.HandleTestingErr(db.Clear(alertrecord.Collection), t, "problem clearing collection")
    70  
    71  	Convey("With no existing tasks in the database", t, func() {
    72  		Convey("a newly failed task should return all triggers", func() {
    73  			// pre-bookkeeping
    74  			ctx, err := getTaskTriggerContext(testTask)
    75  			So(err, ShouldBeNil)
    76  			So(ctx.previousCompleted, ShouldBeNil)
    77  			triggers, err := getActiveTaskFailureTriggers(*ctx)
    78  			So(err, ShouldBeNil)
    79  			So(len(triggers), ShouldEqual, 5)
    80  			So(hasTrigger(triggers, TaskFailed{}), ShouldBeTrue)
    81  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeTrue)
    82  			So(hasTrigger(triggers, FirstFailureInVersion{}), ShouldBeTrue)
    83  			So(hasTrigger(triggers, FirstFailureInVariant{}), ShouldBeTrue)
    84  			So(hasTrigger(triggers, FirstFailureInTaskType{}), ShouldBeTrue)
    85  
    86  			// post-bookkeeping
    87  			err = storeTriggerBookkeeping(*ctx, triggers)
    88  			So(err, ShouldBeNil)
    89  			triggers, err = getActiveTaskFailureTriggers(*ctx)
    90  			So(err, ShouldBeNil)
    91  
    92  			So(len(triggers), ShouldEqual, 2)
    93  			So(hasTrigger(triggers, TaskFailed{}), ShouldBeTrue)
    94  
    95  			// The previous task doesn't exist, so this will still be true
    96  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeTrue)
    97  		})
    98  		Convey("a successful task should trigger nothing", func() {
    99  			testTask.Status = evergreen.TaskSucceeded
   100  			ctx, err := getTaskTriggerContext(testTask)
   101  			So(err, ShouldBeNil)
   102  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   103  			So(err, ShouldBeNil)
   104  			So(len(triggers), ShouldEqual, 0)
   105  		})
   106  	})
   107  }
   108  
   109  func TestExistingPassedTaskTriggers(t *testing.T) {
   110  	Convey("With a previously passing instance of task in the database", t, func() {
   111  		testutil.HandleTestingErr(db.Clear(task.Collection), t, "problem clearing collection")
   112  		testutil.HandleTestingErr(db.Clear(alertrecord.Collection), t, "problem clearing collection")
   113  		testTask.Status = evergreen.TaskSucceeded
   114  		err := testTask.Insert()
   115  		So(err, ShouldBeNil)
   116  		t2 := &task.Task{
   117  			Id:                  "testTask2",
   118  			Status:              evergreen.TaskFailed,
   119  			DisplayName:         testTask.DisplayName,
   120  			Project:             testTask.Project,
   121  			BuildVariant:        testTask.BuildVariant,
   122  			Version:             "testVersion2",
   123  			RevisionOrderNumber: testTask.RevisionOrderNumber + 2,
   124  		}
   125  		Convey("a newly failed task should trigger TaskFailed and TaskFailTransition", func() {
   126  			ctx, err := getTaskTriggerContext(t2)
   127  			So(err, ShouldBeNil)
   128  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   129  			So(err, ShouldBeNil)
   130  			So(hasTrigger(triggers, TaskFailed{}), ShouldBeTrue)
   131  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeTrue)
   132  		})
   133  		Convey("a newly failed task should not trigger TaskFailTransition after bookkeeping is already done", func() {
   134  			// Pre-bookkeeping
   135  
   136  			ctx, err := getTaskTriggerContext(t2)
   137  			So(err, ShouldBeNil)
   138  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   139  			So(err, ShouldBeNil)
   140  			So(hasTrigger(triggers, TaskFailed{}), ShouldBeTrue)
   141  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeTrue)
   142  
   143  			// Post-bookkeeping
   144  			err = storeTriggerBookkeeping(*ctx, triggers)
   145  			So(err, ShouldBeNil)
   146  			triggers, err = getActiveTaskFailureTriggers(*ctx)
   147  			So(err, ShouldBeNil)
   148  
   149  			So(hasTrigger(triggers, TaskFailed{}), ShouldBeTrue)
   150  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeFalse)
   151  		})
   152  	})
   153  }
   154  
   155  func TestExistingFailedTaskTriggers(t *testing.T) {
   156  	Convey("With a previously failed instance of task in the database", t, func() {
   157  		testutil.HandleTestingErr(db.Clear(task.Collection), t, "problem clearing collection")
   158  		testutil.HandleTestingErr(db.Clear(model.ProjectRefCollection), t, "problem clearing collection")
   159  		testutil.HandleTestingErr(db.Clear(version.Collection), t, "problem clearing collection")
   160  		So(testProject.Insert(), ShouldBeNil)
   161  		So(testVersion.Insert(), ShouldBeNil)
   162  		testTask.Status = evergreen.TaskFailed
   163  		err := testTask.Insert()
   164  		So(err, ShouldBeNil)
   165  		Convey("a newly failed task should trigger TaskFailed but *not* TaskFailTransition", func() {
   166  			t2 := &task.Task{
   167  				Id:                  "testTask2",
   168  				Status:              evergreen.TaskFailed,
   169  				DisplayName:         testTask.DisplayName,
   170  				Project:             testTask.Project,
   171  				BuildVariant:        testTask.BuildVariant,
   172  				Version:             "testVersion2",
   173  				RevisionOrderNumber: testTask.RevisionOrderNumber + 2,
   174  			}
   175  			ctx, err := getTaskTriggerContext(t2)
   176  			So(err, ShouldBeNil)
   177  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   178  			So(err, ShouldBeNil)
   179  			So(len(triggers), ShouldEqual, 1)
   180  			So(triggers[0].Id(), ShouldEqual, TaskFailed{}.Id())
   181  		})
   182  	})
   183  }
   184  
   185  func TestTransitionResend(t *testing.T) {
   186  	Convey("With a set of failures that previously transitioned", t, func() {
   187  		So(db.Clear(task.Collection), ShouldBeNil)
   188  		So(db.Clear(alertrecord.Collection), ShouldBeNil)
   189  		So(db.Clear(model.ProjectRefCollection), ShouldBeNil)
   190  		So(db.Clear(version.Collection), ShouldBeNil)
   191  		// insert 15 failed task documents
   192  		ts := []task.Task{
   193  			*testTask,
   194  			*testTask,
   195  			*testTask,
   196  		}
   197  		for i := range ts {
   198  			ts[i].Id = fmt.Sprintf("t%v", i)
   199  			ts[i].BuildVariant = fmt.Sprintf("bv%v", i+1)
   200  			ts[i].FinishTime = time.Now().Add(-10 * time.Minute)
   201  			So(ts[i].Insert(), ShouldBeNil)
   202  		}
   203  		pastAlert := alertrecord.AlertRecord{
   204  			Id:        bson.NewObjectId(),
   205  			Type:      alertrecord.TaskFailTransitionId,
   206  			TaskName:  ts[0].DisplayName,
   207  			ProjectId: ts[0].Project,
   208  		}
   209  		So(testProject.Insert(), ShouldBeNil)
   210  		So(testVersion.Insert(), ShouldBeNil)
   211  		Convey("a failure 10 minutes ago with a batch time of 60 should not retrigger", func() {
   212  			pastAlert.TaskId = ts[0].Id
   213  			pastAlert.Variant = ts[0].BuildVariant
   214  			So(pastAlert.Insert(), ShouldBeNil)
   215  			ctx, err := getTaskTriggerContext(&ts[0])
   216  			So(err, ShouldBeNil)
   217  			ctx.previousCompleted = &task.Task{Status: evergreen.TaskFailed}
   218  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   219  			So(err, ShouldBeNil)
   220  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeFalse)
   221  		})
   222  		Convey("a failure 10 minutes ago with a batch time of 1 should retrigger", func() {
   223  			pastAlert.TaskId = ts[1].Id
   224  			pastAlert.Variant = ts[1].BuildVariant
   225  			So(pastAlert.Insert(), ShouldBeNil)
   226  			ctx, err := getTaskTriggerContext(&ts[1])
   227  			So(err, ShouldBeNil)
   228  			ctx.previousCompleted = &task.Task{Status: evergreen.TaskFailed}
   229  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   230  			So(err, ShouldBeNil)
   231  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeTrue)
   232  		})
   233  		Convey("a failure 10 minutes ago with a batch time of 5 should not retrigger", func() {
   234  			pastAlert.TaskId = ts[2].Id
   235  			pastAlert.Variant = ts[2].BuildVariant
   236  			So(pastAlert.Insert(), ShouldBeNil)
   237  			ctx, err := getTaskTriggerContext(&ts[2])
   238  			So(err, ShouldBeNil)
   239  			ctx.previousCompleted = &task.Task{Status: evergreen.TaskFailed}
   240  			triggers, err := getActiveTaskFailureTriggers(*ctx)
   241  			So(err, ShouldBeNil)
   242  			So(hasTrigger(triggers, TaskFailTransition{}), ShouldBeFalse)
   243  		})
   244  	})
   245  }
   246  
   247  func TestSpawnExpireWarningTrigger(t *testing.T) {
   248  	Convey("With a spawnhost due to expire in two hours", t, func() {
   249  		So(db.Clear(host.Collection), ShouldBeNil)
   250  		testHost := host.Host{
   251  			Id:             "testhost",
   252  			StartedBy:      "test_user",
   253  			Status:         "running",
   254  			ExpirationTime: time.Now().Add(1 * time.Hour),
   255  		}
   256  
   257  		trigger := SpawnTwoHourWarning{}
   258  		ctx := triggerContext{host: &testHost}
   259  		shouldExec, err := trigger.ShouldExecute(ctx)
   260  		So(err, ShouldBeNil)
   261  		So(shouldExec, ShouldBeTrue)
   262  
   263  		// run bookkeeping
   264  		err = storeTriggerBookkeeping(ctx, []Trigger{trigger})
   265  		So(err, ShouldBeNil)
   266  
   267  		// should exec should now return false
   268  		shouldExec, err = trigger.ShouldExecute(ctx)
   269  		So(err, ShouldBeNil)
   270  		So(shouldExec, ShouldBeFalse)
   271  	})
   272  }
   273  
   274  func TestReachedFailureLimit(t *testing.T) {
   275  	Convey("With 3 failed task and relevant project variants", t, func() {
   276  		testutil.HandleTestingErr(db.Clear(task.Collection), t, "problem clearing collection")
   277  		testutil.HandleTestingErr(db.Clear(model.ProjectRefCollection), t, "problem clearing collection")
   278  		testutil.HandleTestingErr(db.Clear(version.Collection), t, "problem clearing collection")
   279  		t := task.Task{
   280  			Id:           "t1",
   281  			Revision:     "aaa",
   282  			Project:      "testProject",
   283  			BuildVariant: "bv1",
   284  			FinishTime:   time.Now().Add(-time.Hour),
   285  		}
   286  		So(t.Insert(), ShouldBeNil)
   287  		t.Id = "t2"
   288  		t.BuildVariant = "bv2"
   289  		So(t.Insert(), ShouldBeNil)
   290  		t.Id = "t3"
   291  		t.BuildVariant = "bv3"
   292  		So(t.Insert(), ShouldBeNil)
   293  		So(testProject.Insert(), ShouldBeNil)
   294  		So(testVersion.Insert(), ShouldBeNil)
   295  
   296  		Convey("a variant with batchtime of 60 should not hit the limit", func() {
   297  			out, err := reachedFailureLimit("t1")
   298  			So(err, ShouldBeNil)
   299  			So(out, ShouldBeFalse)
   300  		})
   301  		Convey("a variant with batchtime of 1 should hit the limit", func() {
   302  			out, err := reachedFailureLimit("t2")
   303  			So(err, ShouldBeNil)
   304  			So(out, ShouldBeTrue)
   305  		})
   306  		Convey("a fallback batchtime of 5 should hit the limit", func() {
   307  			out, err := reachedFailureLimit("t3")
   308  			So(err, ShouldBeNil)
   309  			So(out, ShouldBeTrue)
   310  		})
   311  	})
   312  }