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 }