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  })