github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/across_step_test.go (about) 1 package exec_test 2 3 import ( 4 "context" 5 6 "code.cloudfoundry.org/lager/lagerctx" 7 "github.com/pf-qiu/concourse/v6/atc" 8 "github.com/pf-qiu/concourse/v6/atc/exec" 9 "github.com/pf-qiu/concourse/v6/atc/exec/execfakes" 10 "github.com/pf-qiu/concourse/v6/vars" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/gomega" 13 "github.com/onsi/gomega/gbytes" 14 ) 15 16 var _ = Describe("AcrossStep", func() { 17 type vals [3]interface{} 18 19 var ( 20 ctx context.Context 21 cancel func() 22 23 fakeDelegateFactory *execfakes.FakeBuildStepDelegateFactory 24 fakeDelegate *execfakes.FakeBuildStepDelegate 25 26 step exec.AcrossStep 27 28 acrossVars []atc.AcrossVar 29 steps []exec.ScopedStep 30 state exec.RunState 31 failFast bool 32 33 allVals []vals 34 35 started chan vals 36 terminate map[vals]chan error 37 38 stepMetadata = exec.StepMetadata{ 39 TeamID: 123, 40 TeamName: "some-team", 41 BuildID: 42, 42 BuildName: "some-build", 43 PipelineID: 4567, 44 PipelineName: "some-pipeline", 45 } 46 47 stderr *gbytes.Buffer 48 ) 49 50 stepRun := func(succeeded bool, values vals) func(context.Context, exec.RunState) (bool, error) { 51 started := started 52 terminate := terminate 53 54 return func(ctx context.Context, childState exec.RunState) (bool, error) { 55 defer GinkgoRecover() 56 57 By("having the correct var values") 58 for i, v := range acrossVars { 59 val, found, _ := childState.Get(vars.Reference{Source: ".", Path: v.Var}) 60 Expect(found).To(BeTrue(), "unset variable "+v.Var) 61 Expect(val).To(Equal(values[i]), "invalid value for variable "+v.Var) 62 } 63 64 By("running with a child scope") 65 Expect(childState.Parent()).To(Equal(state)) 66 67 started <- values 68 if c, ok := terminate[values]; ok { 69 select { 70 case err := <-c: 71 return false, err 72 case <-ctx.Done(): 73 return false, ctx.Err() 74 } 75 } 76 return succeeded, nil 77 } 78 } 79 80 scopedStepFactory := func(acrossVars []atc.AcrossVar, values vals) exec.ScopedStep { 81 s := new(execfakes.FakeStep) 82 s.RunStub = stepRun(true, values) 83 return exec.ScopedStep{ 84 Values: values[:], 85 Step: s, 86 } 87 } 88 89 BeforeEach(func() { 90 ctx, cancel = context.WithCancel(context.Background()) 91 ctx = lagerctx.NewContext(ctx, testLogger) 92 93 state = exec.NewRunState(noopStepper, vars.StaticVariables{}, false) 94 95 stderr = gbytes.NewBuffer() 96 97 fakeDelegate = new(execfakes.FakeBuildStepDelegate) 98 fakeDelegate.StderrReturns(stderr) 99 100 fakeDelegateFactory = new(execfakes.FakeBuildStepDelegateFactory) 101 fakeDelegateFactory.BuildStepDelegateReturns(fakeDelegate) 102 103 acrossVars = []atc.AcrossVar{ 104 { 105 Var: "var1", 106 Values: []interface{}{"a1", "a2"}, 107 MaxInFlight: &atc.MaxInFlightConfig{All: true}, 108 }, 109 { 110 Var: "var2", 111 Values: []interface{}{"b1", "b2"}, 112 }, 113 { 114 Var: "var3", 115 Values: []interface{}{"c1", "c2"}, 116 MaxInFlight: &atc.MaxInFlightConfig{Limit: 2}, 117 }, 118 } 119 started = make(chan vals, 8) 120 terminate = map[vals]chan error{} 121 122 allVals = []vals{ 123 {"a1", "b1", "c1"}, 124 {"a1", "b1", "c2"}, 125 126 {"a1", "b2", "c1"}, 127 {"a1", "b2", "c2"}, 128 129 {"a2", "b1", "c1"}, 130 {"a2", "b1", "c2"}, 131 132 {"a2", "b2", "c1"}, 133 {"a2", "b2", "c2"}, 134 } 135 136 steps = make([]exec.ScopedStep, len(allVals)) 137 for i, v := range allVals { 138 steps[i] = scopedStepFactory(acrossVars, v) 139 } 140 141 failFast = false 142 }) 143 144 AfterEach(func() { 145 cancel() 146 }) 147 148 JustBeforeEach(func() { 149 step = exec.Across( 150 acrossVars, 151 steps, 152 failFast, 153 fakeDelegateFactory, 154 stepMetadata, 155 ) 156 }) 157 158 It("logs a warning to stderr", func() { 159 _, err := step.Run(ctx, state) 160 Expect(err).ToNot(HaveOccurred()) 161 162 Expect(stderr).To(gbytes.Say("WARNING: the across step is experimental")) 163 Expect(stderr).To(gbytes.Say("follow RFC #29 for updates")) 164 }) 165 166 It("initializes the step", func() { 167 step.Run(ctx, state) 168 169 Expect(fakeDelegate.InitializingCallCount()).To(Equal(1)) 170 }) 171 172 It("starts the step", func() { 173 step.Run(ctx, state) 174 175 Expect(fakeDelegate.StartingCallCount()).To(Equal(1)) 176 }) 177 178 It("finishes the step", func() { 179 step.Run(ctx, state) 180 181 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 182 }) 183 184 Context("when a var shadows an existing local var", func() { 185 BeforeEach(func() { 186 state.AddLocalVar("var2", 123, false) 187 }) 188 189 It("logs a warning to stderr", func() { 190 _, err := step.Run(ctx, state) 191 Expect(err).ToNot(HaveOccurred()) 192 193 Expect(stderr).To(gbytes.Say("WARNING: across step shadows local var 'var2'")) 194 }) 195 }) 196 197 Describe("parallel execution", func() { 198 BeforeEach(func() { 199 for _, v := range allVals { 200 terminate[v] = make(chan error, 1) 201 } 202 }) 203 204 It("steps are run in parallel according to the MaxInFlight for each var", func() { 205 go step.Run(ctx, state) 206 207 By("running the first stage") 208 var receivedVals []vals 209 for i := 0; i < 4; i++ { 210 receivedVals = append(receivedVals, <-started) 211 } 212 Expect(receivedVals).To(ConsistOf( 213 vals{"a1", "b1", "c1"}, 214 vals{"a1", "b1", "c2"}, 215 vals{"a2", "b1", "c1"}, 216 vals{"a2", "b1", "c2"}, 217 )) 218 Consistently(started).ShouldNot(Receive()) 219 220 By("the first stage completing successfully") 221 for _, v := range receivedVals { 222 terminate[v] <- nil 223 } 224 225 By("running the second stage") 226 receivedVals = []vals{} 227 for i := 0; i < 4; i++ { 228 receivedVals = append(receivedVals, <-started) 229 } 230 Expect(receivedVals).To(ConsistOf( 231 vals{"a1", "b2", "c1"}, 232 vals{"a1", "b2", "c2"}, 233 vals{"a2", "b2", "c1"}, 234 vals{"a2", "b2", "c2"}, 235 )) 236 }) 237 238 Context("when fail fast is true", func() { 239 BeforeEach(func() { 240 failFast = true 241 }) 242 243 It("stops running steps after a failure", func() { 244 By("a step in the first stage failing") 245 terminate[allVals[1]] <- nil 246 steps[1].Step.(*execfakes.FakeStep).RunStub = stepRun(false, allVals[1]) 247 248 By("running the step") 249 ok, err := step.Run(ctx, state) 250 Expect(err).ToNot(HaveOccurred()) 251 Expect(ok).To(BeFalse()) 252 253 By("ensuring not all steps were started") 254 Expect(started).ToNot(HaveLen(8)) 255 }) 256 }) 257 258 Context("when fail fast is false", func() { 259 BeforeEach(func() { 260 failFast = false 261 }) 262 263 It("allows all steps to run before failing", func() { 264 By("a step in the first stage failing") 265 steps[1].Step.(*execfakes.FakeStep).RunStub = stepRun(false, allVals[1]) 266 267 for _, v := range allVals { 268 terminate[v] <- nil 269 } 270 271 By("running the step") 272 ok, err := step.Run(ctx, state) 273 Expect(err).ToNot(HaveOccurred()) 274 Expect(ok).To(BeFalse()) 275 276 By("ensuring all steps were run") 277 Expect(started).To(HaveLen(8)) 278 }) 279 }) 280 }) 281 282 Describe("panic recovery", func() { 283 Context("when one step panics", func() { 284 BeforeEach(func() { 285 steps[1].Step.(*execfakes.FakeStep).RunStub = func(context.Context, exec.RunState) (bool, error) { 286 panic("something went wrong") 287 } 288 }) 289 290 It("handles it gracefully", func() { 291 _, err := step.Run(ctx, state) 292 Expect(err).To(HaveOccurred()) 293 Expect(err.Error()).To(ContainSubstring("something went wrong")) 294 }) 295 }) 296 }) 297 })