github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/engine/engine_test.go (about) 1 package engine_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "code.cloudfoundry.org/lager" 11 "code.cloudfoundry.org/lager/lagerctx" 12 "code.cloudfoundry.org/lager/lagertest" 13 "github.com/pf-qiu/concourse/v6/atc" 14 "github.com/pf-qiu/concourse/v6/atc/creds/credsfakes" 15 "github.com/pf-qiu/concourse/v6/atc/db" 16 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 17 "github.com/pf-qiu/concourse/v6/atc/db/lock/lockfakes" 18 . "github.com/pf-qiu/concourse/v6/atc/engine" 19 "github.com/pf-qiu/concourse/v6/atc/engine/enginefakes" 20 "github.com/pf-qiu/concourse/v6/atc/event" 21 "github.com/pf-qiu/concourse/v6/atc/exec" 22 "github.com/pf-qiu/concourse/v6/atc/exec/execfakes" 23 "github.com/pf-qiu/concourse/v6/vars" 24 25 . "github.com/onsi/ginkgo" 26 . "github.com/onsi/gomega" 27 ) 28 29 var _ = Describe("Engine", func() { 30 var ( 31 fakeBuild *dbfakes.FakeBuild 32 fakeStepperFactory *enginefakes.FakeStepperFactory 33 34 fakeGlobalCreds *credsfakes.FakeSecrets 35 fakeVarSourcePool *credsfakes.FakeVarSourcePool 36 ) 37 38 BeforeEach(func() { 39 fakeBuild = new(dbfakes.FakeBuild) 40 fakeBuild.IDReturns(128) 41 42 fakeStepperFactory = new(enginefakes.FakeStepperFactory) 43 44 fakeGlobalCreds = new(credsfakes.FakeSecrets) 45 fakeVarSourcePool = new(credsfakes.FakeVarSourcePool) 46 }) 47 48 Describe("NewBuild", func() { 49 var ( 50 build Runnable 51 engine Engine 52 ) 53 54 BeforeEach(func() { 55 engine = NewEngine(fakeStepperFactory, fakeGlobalCreds, fakeVarSourcePool) 56 }) 57 58 JustBeforeEach(func() { 59 build = engine.NewBuild(fakeBuild) 60 }) 61 62 It("returns a build", func() { 63 Expect(build).NotTo(BeNil()) 64 }) 65 }) 66 67 Describe("Build", func() { 68 var ( 69 build Runnable 70 release chan bool 71 waitGroup *sync.WaitGroup 72 ) 73 74 BeforeEach(func() { 75 76 release = make(chan bool) 77 trackedStates := new(sync.Map) 78 waitGroup = new(sync.WaitGroup) 79 80 build = NewBuild( 81 fakeBuild, 82 fakeStepperFactory, 83 fakeGlobalCreds, 84 fakeVarSourcePool, 85 release, 86 trackedStates, 87 waitGroup, 88 ) 89 }) 90 91 Describe("Run", func() { 92 var ( 93 logger lager.Logger 94 ctx context.Context 95 ) 96 97 BeforeEach(func() { 98 logger = lagertest.NewTestLogger("test") 99 ctx = context.Background() 100 }) 101 102 JustBeforeEach(func() { 103 build.Run(lagerctx.NewContext(ctx, logger)) 104 }) 105 106 Context("when acquiring the lock succeeds", func() { 107 var fakeLock *lockfakes.FakeLock 108 109 BeforeEach(func() { 110 fakeLock = new(lockfakes.FakeLock) 111 112 fakeBuild.AcquireTrackingLockReturns(fakeLock, true, nil) 113 }) 114 115 Context("when the build is active", func() { 116 BeforeEach(func() { 117 fakeBuild.IsRunningReturns(true) 118 fakeBuild.ReloadReturns(true, nil) 119 }) 120 121 Context("when listening for aborts succeeds", func() { 122 var abort chan struct{} 123 var fakeNotifier *dbfakes.FakeNotifier 124 125 BeforeEach(func() { 126 abort = make(chan struct{}) 127 128 fakeNotifier = new(dbfakes.FakeNotifier) 129 fakeNotifier.NotifyReturns(abort) 130 131 fakeBuild.AbortNotifierReturns(fakeNotifier, nil) 132 }) 133 134 Context("when converting the plan to a step succeeds", func() { 135 var steppedPlans chan atc.Plan 136 var fakeStep *execfakes.FakeStep 137 138 BeforeEach(func() { 139 fakeStep = new(execfakes.FakeStep) 140 fakeBuild.PrivatePlanReturns(atc.Plan{ 141 ID: "build-plan", 142 LoadVar: &atc.LoadVarPlan{ 143 Name: "some-var", 144 File: "some-file.yml", 145 }, 146 }) 147 148 steppedPlans = make(chan atc.Plan, 1) 149 fakeStepperFactory.StepperForBuildReturns(func(plan atc.Plan) exec.Step { 150 steppedPlans <- plan 151 return fakeStep 152 }, nil) 153 }) 154 155 It("releases the lock", func() { 156 waitGroup.Wait() 157 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 158 }) 159 160 It("closes the notifier", func() { 161 waitGroup.Wait() 162 Expect(fakeNotifier.CloseCallCount()).To(Equal(1)) 163 }) 164 165 It("constructs a step from the build's plan", func() { 166 plan := <-steppedPlans 167 Expect(plan).ToNot(BeZero()) 168 Expect(plan).To(Equal(fakeBuild.PrivatePlan())) //XXX 169 }) 170 171 Context("when getting the build vars succeeds", func() { 172 var invokedState chan exec.RunState 173 174 BeforeEach(func() { 175 fakeBuild.VariablesReturns(vars.StaticVariables{"foo": "bar"}, nil) 176 177 invokedState = make(chan exec.RunState, 1) 178 fakeStep.RunStub = func(ctx context.Context, state exec.RunState) (bool, error) { 179 invokedState <- state 180 return true, nil 181 } 182 }) 183 184 It("runs the step with the build variables", func() { 185 state := <-invokedState 186 187 val, found, err := state.Get(vars.Reference{Path: "foo"}) 188 Expect(err).ToNot(HaveOccurred()) 189 Expect(found).To(BeTrue()) 190 Expect(val).To(Equal("bar")) 191 }) 192 193 Context("when the build is released", func() { 194 BeforeEach(func() { 195 readyToRelease := make(chan bool) 196 197 go func() { 198 <-readyToRelease 199 release <- true 200 }() 201 202 fakeStep.RunStub = func(context.Context, exec.RunState) (bool, error) { 203 close(readyToRelease) 204 <-time.After(time.Hour) 205 return true, nil 206 } 207 }) 208 209 It("does not finish the build", func() { 210 waitGroup.Wait() 211 Expect(fakeBuild.FinishCallCount()).To(Equal(0)) 212 }) 213 }) 214 215 Context("when the build is aborted", func() { 216 BeforeEach(func() { 217 readyToAbort := make(chan bool) 218 219 go func() { 220 <-readyToAbort 221 abort <- struct{}{} 222 }() 223 224 fakeStep.RunStub = func(context.Context, exec.RunState) (bool, error) { 225 close(readyToAbort) 226 <-time.After(time.Second) 227 return true, nil 228 } 229 }) 230 231 It("cancels the context given to the step", func() { 232 waitGroup.Wait() 233 stepCtx, _ := fakeStep.RunArgsForCall(0) 234 Expect(stepCtx.Done()).To(BeClosed()) 235 }) 236 }) 237 238 Context("when the build finishes successfully", func() { 239 BeforeEach(func() { 240 fakeStep.RunReturns(true, nil) 241 }) 242 243 It("finishes the build", func() { 244 waitGroup.Wait() 245 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 246 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusSucceeded)) 247 }) 248 }) 249 250 Context("when the build finishes woefully", func() { 251 BeforeEach(func() { 252 fakeStep.RunReturns(false, nil) 253 }) 254 255 It("finishes the build", func() { 256 waitGroup.Wait() 257 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 258 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusFailed)) 259 }) 260 }) 261 262 Context("when the build finishes with error", func() { 263 BeforeEach(func() { 264 fakeStep.RunReturns(false, errors.New("nope")) 265 }) 266 267 It("finishes the build", func() { 268 waitGroup.Wait() 269 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 270 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusErrored)) 271 }) 272 }) 273 274 Context("when the build finishes with cancelled error", func() { 275 BeforeEach(func() { 276 fakeStep.RunReturns(false, context.Canceled) 277 }) 278 279 It("finishes the build", func() { 280 waitGroup.Wait() 281 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 282 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusAborted)) 283 }) 284 }) 285 286 Context("when the build finishes with a wrapped cancelled error", func() { 287 BeforeEach(func() { 288 fakeStep.RunReturns(false, fmt.Errorf("but im not a wrapper: %w", context.Canceled)) 289 }) 290 291 It("finishes the build", func() { 292 waitGroup.Wait() 293 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 294 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusAborted)) 295 }) 296 }) 297 298 Context("when the build panics", func() { 299 BeforeEach(func() { 300 fakeStep.RunStub = func(context.Context, exec.RunState) (bool, error) { 301 panic("something went wrong") 302 } 303 }) 304 305 It("finishes the build with error", func() { 306 waitGroup.Wait() 307 Expect(fakeBuild.FinishCallCount()).To(Equal(1)) 308 Expect(fakeBuild.FinishArgsForCall(0)).To(Equal(db.BuildStatusErrored)) 309 }) 310 }) 311 }) 312 313 Context("when getting the build vars fails", func() { 314 BeforeEach(func() { 315 fakeBuild.VariablesReturns(nil, errors.New("ruh roh")) 316 }) 317 318 It("releases the lock", func() { 319 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 320 }) 321 322 It("closes the notifier", func() { 323 Expect(fakeNotifier.CloseCallCount()).To(Equal(1)) 324 }) 325 326 It("saves an error event", func() { 327 Expect(fakeBuild.SaveEventCallCount()).To(Equal(1)) 328 Expect(fakeBuild.SaveEventArgsForCall(0).EventType()).To(Equal(event.EventTypeError)) 329 }) 330 }) 331 }) 332 333 Context("when converting the plan to a step fails", func() { 334 BeforeEach(func() { 335 fakeStepperFactory.StepperForBuildReturns(nil, errors.New("nope")) 336 }) 337 338 It("releases the lock", func() { 339 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 340 }) 341 342 It("closes the notifier", func() { 343 Expect(fakeNotifier.CloseCallCount()).To(Equal(1)) 344 }) 345 346 It("saves an error event", func() { 347 Expect(fakeBuild.SaveEventCallCount()).To(Equal(1)) 348 Expect(fakeBuild.SaveEventArgsForCall(0).EventType()).To(Equal(event.EventTypeError)) 349 }) 350 }) 351 }) 352 353 Context("when listening for aborts fails", func() { 354 BeforeEach(func() { 355 fakeBuild.AbortNotifierReturns(nil, errors.New("nope")) 356 }) 357 358 It("does not build the step", func() { 359 Expect(fakeStepperFactory.StepperForBuildCallCount()).To(BeZero()) 360 }) 361 362 It("releases the lock", func() { 363 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 364 }) 365 }) 366 }) 367 368 Context("when the build is not yet active", func() { 369 BeforeEach(func() { 370 fakeBuild.ReloadReturns(true, nil) 371 }) 372 373 It("does not build the step", func() { 374 Expect(fakeStepperFactory.StepperForBuildCallCount()).To(BeZero()) 375 }) 376 377 It("releases the lock", func() { 378 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 379 }) 380 }) 381 382 Context("when the build has already finished", func() { 383 BeforeEach(func() { 384 fakeBuild.ReloadReturns(true, nil) 385 fakeBuild.StatusReturns(db.BuildStatusSucceeded) 386 }) 387 388 It("does not build the step", func() { 389 Expect(fakeStepperFactory.StepperForBuildCallCount()).To(BeZero()) 390 }) 391 392 It("releases the lock", func() { 393 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 394 }) 395 }) 396 397 Context("when the build is no longer in the database", func() { 398 BeforeEach(func() { 399 fakeBuild.ReloadReturns(false, nil) 400 }) 401 402 It("does not build the step", func() { 403 Expect(fakeStepperFactory.StepperForBuildCallCount()).To(BeZero()) 404 }) 405 406 It("releases the lock", func() { 407 Expect(fakeLock.ReleaseCallCount()).To(Equal(1)) 408 }) 409 }) 410 }) 411 412 Context("when acquiring the lock fails", func() { 413 BeforeEach(func() { 414 fakeBuild.AcquireTrackingLockReturns(nil, false, errors.New("no lock for you")) 415 }) 416 417 It("does not build the step", func() { 418 Expect(fakeStepperFactory.StepperForBuildCallCount()).To(BeZero()) 419 }) 420 }) 421 }) 422 }) 423 })