Context("when attempt 1 succeeds", func() { BeforeEach(func() { attempt1Step.ResultStub = successResult(true) }) Describe("Run", func() { var process ifrit.Process JustBeforeEach(func() { process = ifrit.Invoke(step) }) It("returns nil having only run the first attempt", func() { Expect(<-process.Wait()).ToNot(HaveOccurred()) Expect(attempt1Step.RunCallCount()).To(Equal(1)) Expect(attempt2Step.RunCallCount()).To(Equal(0)) Expect(attempt3Step.RunCallCount()).To(Equal(0)) }) Describe("Result", func() { It("delegates to attempt 1", func() { <-process.Wait() // internal check for success within retry loop Expect(attempt1Step.ResultCallCount()).To(Equal(1)) attempt1Step.ResultReturns(true) var foo interface{} destination := &foo
Location: &atc.Location{}, Task: &atc.TaskPlan{ Name: "some-resource", Config: &atc.TaskConfig{}, }, }, }, } build, err := execEngine.CreateBuild(logger, buildModel, plan) Expect(err).NotTo(HaveOccurred()) build.Resume(logger) Expect(inputStep.RunCallCount()).To(Equal(1)) Expect(inputStep.ReleaseCallCount()).To(Equal(1)) Expect(taskStep.RunCallCount()).To(Equal(1)) Expect(taskStep.ReleaseCallCount()).To(Equal(1)) }) It("runs the success hooks, and completion hooks", func() { plan := atc.Plan{ Location: &atc.Location{}, Ensure: &atc.EnsurePlan{ Step: atc.Plan{ OnSuccess: &atc.OnSuccessPlan{ Step: atc.Plan{ Location: &atc.Location{}, Get: &atc.GetPlan{
argsPrev, argsRepo := hookFactory.UsingArgsForCall(0) Expect(argsPrev).To(Equal(step)) Expect(argsRepo).To(Equal(repo)) Eventually(process.Wait()).Should(Receive(noError())) }) It("runs the ensured hook even if the step errors", func() { step.RunReturns(errors.New("disaster")) process := ifrit.Background(ensureStep) Eventually(step.RunCallCount).Should(Equal(1)) Eventually(process.Wait()).Should(Receive(errorMatching(ContainSubstring("disaster")))) Expect(hook.RunCallCount()).To(Equal(1)) }) It("propagates signals to the first step when first step is running", func() { step.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { close(ready) <-signals return errors.New("interrupted") } process := ifrit.Background(ensureStep) process.Signal(os.Kill) Eventually(step.RunCallCount).Should(Equal(1))
}) Context("when the input succeeds", func() { BeforeEach(func() { inputStep.RunReturns(nil) }) Context("when executing the task errors", func() { disaster := errors.New("oh no!") BeforeEach(func() { taskStep.RunReturns(disaster) }) It("does not run any outputs", func() { Ω(outputStep.RunCallCount()).Should(BeZero()) }) It("finishes with error", func() { Ω(fakeDelegate.FinishCallCount()).Should(Equal(1)) _, cbErr, _, _ := fakeDelegate.FinishArgsForCall(0) Ω(cbErr).Should(MatchError(ContainSubstring(disaster.Error()))) }) }) Context("when executing the task succeeds", func() { BeforeEach(func() { taskStep.RunReturns(nil) taskStep.ResultStub = successResult(true) })
argsPrev, argsRepo := successFactory.UsingArgsForCall(0) Ω(argsPrev).Should(Equal(step)) Ω(argsRepo).Should(Equal(repo)) Eventually(process.Wait()).Should(Receive(noError())) }) It("does not run the success hook if the step errors", func() { step.RunReturns(errors.New("disaster")) process := ifrit.Background(onSuccessStep) Eventually(step.RunCallCount).Should(Equal(1)) Eventually(process.Wait()).Should(Receive(errorMatching("disaster"))) Ω(hook.RunCallCount()).Should(Equal(0)) }) It("does not run the success hook if the step fails", func() { step.ResultStub = successResult(false) process := ifrit.Background(onSuccessStep) Eventually(step.RunCallCount).Should(Equal(1)) Eventually(process.Wait()).Should(Receive(noError())) Ω(hook.RunCallCount()).Should(Equal(0)) }) It("propagates signals to the first step when first step is running", func() { step.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { close(ready)
wg.Done() wg.Wait() close(ready) return nil } outStepB.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { wg.Done() wg.Wait() close(ready) return nil } }) It("happens concurrently", func() { Ω(outStepA.RunCallCount()).Should(Equal(1)) Ω(outStepB.RunCallCount()).Should(Equal(1)) }) }) Describe("signalling", func() { var receivedSignals chan os.Signal BeforeEach(func() { receivedSignals = make(chan os.Signal, 2) outStepA.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { close(ready) receivedSignals <- <-signals return ErrInterrupted }
Location: &atc.Location{}, Task: &atc.TaskPlan{ Name: "some-resource", Config: &atc.TaskConfig{}, }, }, }, } build, err := execEngine.CreateBuild(buildModel, plan) Ω(err).ShouldNot(HaveOccurred()) build.Resume(logger) Ω(inputStep.RunCallCount()).Should(Equal(1)) Ω(inputStep.ReleaseCallCount()).Should(BeNumerically(">", 0)) Ω(taskStep.RunCallCount()).Should(Equal(1)) Ω(taskStep.ReleaseCallCount()).Should(BeNumerically(">", 0)) }) It("runs the success hooks, and completion hooks", func() { plan := atc.Plan{ Location: &atc.Location{}, Ensure: &atc.EnsurePlan{ Step: atc.Plan{ OnSuccess: &atc.OnSuccessPlan{ Step: atc.Plan{ Location: &atc.Location{}, Get: &atc.GetPlan{
Location: &atc.Location{}, Task: &atc.TaskPlan{ Name: "some-resource", Config: &atc.TaskConfig{}, }, }, }, } build, err := execEngine.CreateBuild(buildModel, plan) Ω(err).ShouldNot(HaveOccurred()) build.Resume(logger) Ω(inputStep.RunCallCount()).Should(Equal(1)) Ω(inputStep.ReleaseCallCount()).Should((BeNumerically(">", 0))) Ω(taskStep.RunCallCount()).Should(Equal(0)) Ω(fakeDelegate.FinishCallCount()).Should(Equal(1)) _, err, succeeded, aborted := fakeDelegate.FinishArgsForCall(0) Ω(err.Error()).Should(ContainSubstring(exec.ErrStepTimedOut.Error())) Ω(succeeded).Should(Equal(exec.Success(false))) Ω(aborted).Should(BeFalse()) }) }) }) })
inStep = new(fakes.FakeStep) repo = NewSourceRepository() }) JustBeforeEach(func() { step = identity.Using(inStep, repo) }) Describe("Run", func() { It("is a no-op", func() { ready := make(chan struct{}) signals := make(chan os.Signal) err := step.Run(signals, ready) Ω(err).ShouldNot(HaveOccurred()) Ω(inStep.RunCallCount()).Should(BeZero()) }) }) Describe("Result", func() { It("calls through to the input source", func() { var result int step.Result(&result) Ω(inStep.ResultCallCount()).Should(Equal(1)) Ω(inStep.ResultArgsForCall(0)).Should(Equal(&result)) }) }) })
}) It("runs the next step", func() { plan := planFactory.NewPlan(atc.OnSuccessPlan{ Step: planFactory.NewPlan(atc.TryPlan{ Step: planFactory.NewPlan(atc.GetPlan{ Name: "some-input", }), }), Next: planFactory.NewPlan(atc.TaskPlan{ Name: "some-resource", Config: &atc.TaskConfig{}, }), }) build, err := execEngine.CreateBuild(logger, buildModel, plan) Expect(err).NotTo(HaveOccurred()) build.Resume(logger) Expect(inputStep.RunCallCount()).To(Equal(1)) Expect(inputStep.ReleaseCallCount()).To(BeNumerically(">", 0)) Expect(taskStep.RunCallCount()).To(Equal(1)) Expect(inputStep.ReleaseCallCount()).To(BeNumerically(">", 0)) }) }) }) })
wg.Done() wg.Wait() close(ready) return nil } outStepB.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { wg.Done() wg.Wait() close(ready) return nil } }) It("happens concurrently", func() { Expect(outStepA.RunCallCount()).To(Equal(1)) Expect(outStepB.RunCallCount()).To(Equal(1)) }) }) Describe("signalling", func() { var receivedSignals chan os.Signal var actuallyExit chan struct{} BeforeEach(func() { receivedSignals = make(chan os.Signal, 2) actuallyExit = make(chan struct{}, 1) outStepA.RunStub = func(signals <-chan os.Signal, ready chan<- struct{}) error { close(ready) receivedSignals <- <-signals
inStep = new(fakes.FakeStep) repo = NewSourceRepository() }) JustBeforeEach(func() { step = identity.Using(inStep, repo) }) Describe("Run", func() { It("is a no-op", func() { ready := make(chan struct{}) signals := make(chan os.Signal) err := step.Run(signals, ready) Expect(err).NotTo(HaveOccurred()) Expect(inStep.RunCallCount()).To(BeZero()) }) }) Describe("Result", func() { It("calls through to the input source", func() { var result int step.Result(&result) Expect(inStep.ResultCallCount()).To(Equal(1)) Expect(inStep.ResultArgsForCall(0)).To(Equal(&result)) }) }) })