1package exec_test 2 3import ( 4 "context" 5 "errors" 6 7 . "github.com/concourse/concourse/atc/exec" 8 "github.com/concourse/concourse/atc/exec/build" 9 "github.com/concourse/concourse/atc/exec/execfakes" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12) 13 14var _ = Describe("Retry Step", func() { 15 var ( 16 ctx context.Context 17 cancel func() 18 19 attempt1 *execfakes.FakeStep 20 attempt2 *execfakes.FakeStep 21 attempt3 *execfakes.FakeStep 22 23 repo *build.Repository 24 state *execfakes.FakeRunState 25 26 step Step 27 ) 28 29 BeforeEach(func() { 30 ctx, cancel = context.WithCancel(context.Background()) 31 32 attempt1 = new(execfakes.FakeStep) 33 attempt2 = new(execfakes.FakeStep) 34 attempt3 = new(execfakes.FakeStep) 35 36 repo = build.NewRepository() 37 state = new(execfakes.FakeRunState) 38 state.ArtifactRepositoryReturns(repo) 39 40 step = Retry(attempt1, attempt2, attempt3) 41 }) 42 43 Context("when calling succeeded before running", func() { 44 Context("when the RetryStep is given no attempts", func() { 45 BeforeEach(func() { 46 step = Retry() 47 }) 48 49 Describe("Succeeded", func() { 50 It("should return false", func() { 51 Expect(step.Succeeded()).To(BeFalse()) 52 }) 53 }) 54 }) 55 56 Context("when the RetryStep is given attempts", func() { 57 Describe("Succeeded", func() { 58 It("should return false", func() { 59 Expect(step.Succeeded()).To(BeFalse()) 60 }) 61 }) 62 }) 63 }) 64 Context("when attempt 1 succeeds", func() { 65 BeforeEach(func() { 66 attempt1.SucceededReturns(true) 67 }) 68 69 Describe("Run", func() { 70 var stepErr error 71 72 JustBeforeEach(func() { 73 stepErr = step.Run(ctx, state) 74 }) 75 76 It("returns nil having only run the first attempt", func() { 77 Expect(stepErr).ToNot(HaveOccurred()) 78 79 Expect(attempt1.RunCallCount()).To(Equal(1)) 80 Expect(attempt2.RunCallCount()).To(Equal(0)) 81 Expect(attempt3.RunCallCount()).To(Equal(0)) 82 }) 83 84 Describe("Succeeded", func() { 85 It("delegates to attempt 1", func() { 86 // internal check for success within retry loop 87 Expect(attempt1.SucceededCallCount()).To(Equal(1)) 88 89 attempt1.SucceededReturns(true) 90 91 Expect(step.Succeeded()).To(BeTrue()) 92 93 Expect(attempt1.SucceededCallCount()).To(Equal(2)) 94 }) 95 }) 96 }) 97 }) 98 99 Context("when attempt 1 fails, and attempt 2 succeeds", func() { 100 BeforeEach(func() { 101 attempt1.SucceededReturns(false) 102 attempt2.SucceededReturns(true) 103 }) 104 105 Describe("Run", func() { 106 var stepErr error 107 108 JustBeforeEach(func() { 109 stepErr = step.Run(ctx, state) 110 }) 111 112 It("returns nil having only run the first and second attempts", func() { 113 Expect(stepErr).ToNot(HaveOccurred()) 114 115 Expect(attempt1.RunCallCount()).To(Equal(1)) 116 Expect(attempt2.RunCallCount()).To(Equal(1)) 117 Expect(attempt3.RunCallCount()).To(Equal(0)) 118 }) 119 120 Describe("Succeeded", func() { 121 It("delegates to attempt 2", func() { 122 // internal check for success within retry loop 123 Expect(attempt2.SucceededCallCount()).To(Equal(1)) 124 125 attempt2.SucceededReturns(true) 126 127 Expect(step.Succeeded()).To(BeTrue()) 128 129 Expect(attempt2.SucceededCallCount()).To(Equal(2)) 130 }) 131 }) 132 }) 133 }) 134 135 Context("when attempt 1 errors, and attempt 2 succeeds", func() { 136 BeforeEach(func() { 137 attempt1.RunReturns(errors.New("nope")) 138 attempt2.SucceededReturns(true) 139 }) 140 141 Describe("Run", func() { 142 var stepErr error 143 144 JustBeforeEach(func() { 145 stepErr = step.Run(ctx, state) 146 }) 147 148 It("returns nil having only run the first and second attempts", func() { 149 Expect(stepErr).ToNot(HaveOccurred()) 150 151 Expect(attempt1.RunCallCount()).To(Equal(1)) 152 Expect(attempt2.RunCallCount()).To(Equal(1)) 153 Expect(attempt3.RunCallCount()).To(Equal(0)) 154 }) 155 156 Describe("Succeeded", func() { 157 It("delegates to attempt 2", func() { 158 // internal check for success within retry loop 159 Expect(attempt2.SucceededCallCount()).To(Equal(1)) 160 161 attempt2.SucceededReturns(true) 162 163 Expect(step.Succeeded()).To(BeTrue()) 164 165 Expect(attempt2.SucceededCallCount()).To(Equal(2)) 166 }) 167 }) 168 }) 169 }) 170 171 Context("when attempt 1 errors, and attempt 2 is interrupted", func() { 172 BeforeEach(func() { 173 attempt1.RunReturns(errors.New("nope")) 174 attempt2.RunStub = func(c context.Context, r RunState) error { 175 cancel() 176 return c.Err() 177 } 178 }) 179 180 Describe("Run", func() { 181 var stepErr error 182 183 JustBeforeEach(func() { 184 stepErr = step.Run(ctx, state) 185 }) 186 187 It("returns the context error having only run the first and second attempts", func() { 188 Expect(stepErr).To(Equal(context.Canceled)) 189 190 Expect(attempt1.RunCallCount()).To(Equal(1)) 191 Expect(attempt2.RunCallCount()).To(Equal(1)) 192 Expect(attempt3.RunCallCount()).To(Equal(0)) 193 }) 194 195 Describe("Succeeded", func() { 196 It("delegates to attempt 2", func() { 197 // internal check for success within retry loop 198 Expect(attempt2.SucceededCallCount()).To(Equal(0)) 199 200 attempt2.SucceededReturns(true) 201 202 Expect(step.Succeeded()).To(BeTrue()) 203 204 Expect(attempt2.SucceededCallCount()).To(Equal(1)) 205 }) 206 }) 207 }) 208 }) 209 210 Context("when attempt 1 errors, attempt 2 times out, and attempt 3 succeeds", func() { 211 BeforeEach(func() { 212 attempt1.RunReturns(errors.New("nope")) 213 attempt2.RunStub = func(c context.Context, r RunState) error { 214 timeout, subCancel := context.WithTimeout(c, 0) 215 defer subCancel() 216 <-timeout.Done() 217 return timeout.Err() 218 } 219 }) 220 221 Describe("Run", func() { 222 var stepErr error 223 224 JustBeforeEach(func() { 225 stepErr = step.Run(ctx, state) 226 }) 227 228 It("returns nil after running all 3 steps", func() { 229 Expect(stepErr).ToNot(HaveOccurred()) 230 231 Expect(attempt1.RunCallCount()).To(Equal(1)) 232 Expect(attempt2.RunCallCount()).To(Equal(1)) 233 Expect(attempt3.RunCallCount()).To(Equal(1)) 234 }) 235 236 Describe("Succeeded", func() { 237 It("delegates to attempt 3", func() { 238 // internal check for success within retry loop 239 Expect(attempt3.SucceededCallCount()).To(Equal(1)) 240 241 attempt3.SucceededReturns(true) 242 243 Expect(step.Succeeded()).To(BeTrue()) 244 245 Expect(attempt3.SucceededCallCount()).To(Equal(2)) 246 }) 247 }) 248 }) 249 }) 250 251 Context("when attempt 1 fails, attempt 2 fails, and attempt 3 succeeds", func() { 252 BeforeEach(func() { 253 attempt1.SucceededReturns(false) 254 attempt2.SucceededReturns(false) 255 attempt3.SucceededReturns(true) 256 }) 257 258 Describe("Run", func() { 259 var stepErr error 260 261 JustBeforeEach(func() { 262 stepErr = step.Run(ctx, state) 263 }) 264 265 It("returns nil after running all 3 steps", func() { 266 Expect(stepErr).ToNot(HaveOccurred()) 267 268 Expect(attempt1.RunCallCount()).To(Equal(1)) 269 Expect(attempt2.RunCallCount()).To(Equal(1)) 270 Expect(attempt3.RunCallCount()).To(Equal(1)) 271 }) 272 273 Describe("Succeeded", func() { 274 It("delegates to attempt 3", func() { 275 // internal check for success within retry loop 276 Expect(attempt3.SucceededCallCount()).To(Equal(1)) 277 278 attempt3.SucceededReturns(true) 279 280 Expect(step.Succeeded()).To(BeTrue()) 281 282 Expect(attempt3.SucceededCallCount()).To(Equal(2)) 283 }) 284 }) 285 }) 286 }) 287 288 Context("when attempt 1 fails, attempt 2 fails, and attempt 3 errors", func() { 289 disaster := errors.New("nope") 290 291 BeforeEach(func() { 292 attempt1.SucceededReturns(false) 293 attempt2.SucceededReturns(false) 294 attempt3.RunReturns(disaster) 295 }) 296 297 Describe("Run", func() { 298 var stepErr error 299 300 JustBeforeEach(func() { 301 stepErr = step.Run(ctx, state) 302 }) 303 304 It("returns the error", func() { 305 Expect(stepErr).To(Equal(disaster)) 306 307 Expect(attempt1.RunCallCount()).To(Equal(1)) 308 Expect(attempt2.RunCallCount()).To(Equal(1)) 309 Expect(attempt3.RunCallCount()).To(Equal(1)) 310 }) 311 312 Describe("Succeeded", func() { 313 It("delegates to attempt 3", func() { 314 // no internal check for success within retry loop, since it errored 315 Expect(attempt3.SucceededCallCount()).To(Equal(0)) 316 317 attempt3.SucceededReturns(true) 318 319 Expect(step.Succeeded()).To(BeTrue()) 320 321 Expect(attempt3.SucceededCallCount()).To(Equal(1)) 322 }) 323 }) 324 }) 325 }) 326 327 Context("when attempt 1 fails, attempt 2 fails, and attempt 3 fails", func() { 328 BeforeEach(func() { 329 attempt1.SucceededReturns(false) 330 attempt2.SucceededReturns(false) 331 attempt3.SucceededReturns(true) 332 }) 333 334 Describe("Run", func() { 335 var stepErr error 336 337 JustBeforeEach(func() { 338 stepErr = step.Run(ctx, state) 339 }) 340 341 It("returns nil having only run the first and second attempts", func() { 342 Expect(stepErr).ToNot(HaveOccurred()) 343 344 Expect(attempt1.RunCallCount()).To(Equal(1)) 345 Expect(attempt2.RunCallCount()).To(Equal(1)) 346 Expect(attempt3.RunCallCount()).To(Equal(1)) 347 }) 348 349 Describe("Succeeded", func() { 350 It("delegates to attempt 3", func() { 351 // internal check for success within retry loop 352 Expect(attempt3.SucceededCallCount()).To(Equal(1)) 353 354 attempt3.SucceededReturns(true) 355 356 Expect(step.Succeeded()).To(BeTrue()) 357 358 Expect(attempt3.SucceededCallCount()).To(Equal(2)) 359 }) 360 }) 361 }) 362 }) 363}) 364