github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/put_step_test.go (about) 1 package exec_test 2 3 import ( 4 "context" 5 "errors" 6 7 "github.com/pf-qiu/concourse/v6/tracing" 8 . "github.com/onsi/ginkgo" 9 . "github.com/onsi/gomega" 10 "github.com/onsi/gomega/gbytes" 11 "go.opentelemetry.io/otel/api/trace" 12 "go.opentelemetry.io/otel/api/trace/tracetest" 13 14 "github.com/pf-qiu/concourse/v6/atc" 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/exec" 18 "github.com/pf-qiu/concourse/v6/atc/exec/build" 19 "github.com/pf-qiu/concourse/v6/atc/exec/execfakes" 20 "github.com/pf-qiu/concourse/v6/atc/resource" 21 "github.com/pf-qiu/concourse/v6/atc/resource/resourcefakes" 22 "github.com/pf-qiu/concourse/v6/atc/runtime" 23 "github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes" 24 "github.com/pf-qiu/concourse/v6/atc/worker" 25 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 26 "github.com/pf-qiu/concourse/v6/vars" 27 ) 28 29 var _ = Describe("PutStep", func() { 30 var ( 31 ctx context.Context 32 cancel func() 33 34 fakeWorker *workerfakes.FakeWorker 35 fakeClient *workerfakes.FakeClient 36 fakeStrategy *workerfakes.FakeContainerPlacementStrategy 37 fakeResourceFactory *resourcefakes.FakeResourceFactory 38 fakeResource *resourcefakes.FakeResource 39 fakeResourceConfigFactory *dbfakes.FakeResourceConfigFactory 40 fakeDelegate *execfakes.FakePutDelegate 41 fakeDelegateFactory *execfakes.FakePutDelegateFactory 42 43 spanCtx context.Context 44 45 putPlan *atc.PutPlan 46 47 fakeArtifact *runtimefakes.FakeArtifact 48 fakeOtherArtifact *runtimefakes.FakeArtifact 49 fakeMountedArtifact *runtimefakes.FakeArtifact 50 51 interpolatedResourceTypes atc.VersionedResourceTypes 52 53 containerMetadata = db.ContainerMetadata{ 54 WorkingDirectory: resource.ResourcesDir("put"), 55 Type: db.ContainerTypePut, 56 StepName: "some-step", 57 } 58 59 stepMetadata = exec.StepMetadata{ 60 TeamID: 123, 61 TeamName: "some-team", 62 BuildID: 42, 63 BuildName: "some-build", 64 PipelineID: 4567, 65 PipelineName: "some-pipeline", 66 } 67 68 repo *build.Repository 69 state *execfakes.FakeRunState 70 71 putStep exec.Step 72 stepOk bool 73 stepErr error 74 75 stdoutBuf *gbytes.Buffer 76 stderrBuf *gbytes.Buffer 77 78 planID atc.PlanID 79 80 versionResult runtime.VersionResult 81 clientErr error 82 someExitStatus int 83 ) 84 85 BeforeEach(func() { 86 ctx, cancel = context.WithCancel(context.Background()) 87 88 planID = atc.PlanID("some-plan-id") 89 90 fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy) 91 fakeClient = new(workerfakes.FakeClient) 92 fakeWorker = new(workerfakes.FakeWorker) 93 fakeResourceFactory = new(resourcefakes.FakeResourceFactory) 94 fakeResourceConfigFactory = new(dbfakes.FakeResourceConfigFactory) 95 96 fakeDelegate = new(execfakes.FakePutDelegate) 97 stdoutBuf = gbytes.NewBuffer() 98 stderrBuf = gbytes.NewBuffer() 99 fakeDelegate.StdoutReturns(stdoutBuf) 100 fakeDelegate.StderrReturns(stderrBuf) 101 102 fakeDelegateFactory = new(execfakes.FakePutDelegateFactory) 103 fakeDelegateFactory.PutDelegateReturns(fakeDelegate) 104 105 spanCtx = context.Background() 106 fakeDelegate.StartSpanReturns(spanCtx, trace.NoopSpan{}) 107 108 versionResult = runtime.VersionResult{ 109 Version: atc.Version{"some": "version"}, 110 Metadata: []atc.MetadataField{{Name: "some", Value: "metadata"}}, 111 } 112 113 fakeResource = new(resourcefakes.FakeResource) 114 fakeResource.PutReturns(versionResult, nil) 115 116 repo = build.NewRepository() 117 state = new(execfakes.FakeRunState) 118 state.ArtifactRepositoryReturns(repo) 119 120 state.GetStub = vars.StaticVariables{ 121 "source-var": "super-secret-source", 122 "params-var": "super-secret-params", 123 }.Get 124 125 uninterpolatedResourceTypes := atc.VersionedResourceTypes{ 126 { 127 ResourceType: atc.ResourceType{ 128 Name: "some-custom-type", 129 Type: "another-custom-type", 130 Source: atc.Source{"some-custom": "((source-var))"}, 131 Params: atc.Params{"some-custom": "((params-var))"}, 132 }, 133 Version: atc.Version{"some-custom": "version"}, 134 }, 135 { 136 ResourceType: atc.ResourceType{ 137 Name: "another-custom-type", 138 Type: "registry-image", 139 Source: atc.Source{"another-custom": "((source-var))"}, 140 Privileged: true, 141 }, 142 Version: atc.Version{"another-custom": "version"}, 143 }, 144 } 145 146 interpolatedResourceTypes = atc.VersionedResourceTypes{ 147 { 148 ResourceType: atc.ResourceType{ 149 Name: "some-custom-type", 150 Type: "another-custom-type", 151 Source: atc.Source{"some-custom": "super-secret-source"}, 152 153 // params don't need to be interpolated because it's used for 154 // fetching, not constructing the resource config 155 Params: atc.Params{"some-custom": "((params-var))"}, 156 }, 157 Version: atc.Version{"some-custom": "version"}, 158 }, 159 { 160 ResourceType: atc.ResourceType{ 161 Name: "another-custom-type", 162 Type: "registry-image", 163 Source: atc.Source{"another-custom": "super-secret-source"}, 164 Privileged: true, 165 }, 166 Version: atc.Version{"another-custom": "version"}, 167 }, 168 } 169 170 putPlan = &atc.PutPlan{ 171 Name: "some-name", 172 Resource: "some-resource", 173 Type: "some-resource-type", 174 Source: atc.Source{"some": "((source-var))"}, 175 Params: atc.Params{"some": "((params-var))"}, 176 VersionedResourceTypes: uninterpolatedResourceTypes, 177 } 178 179 fakeArtifact = new(runtimefakes.FakeArtifact) 180 fakeOtherArtifact = new(runtimefakes.FakeArtifact) 181 fakeMountedArtifact = new(runtimefakes.FakeArtifact) 182 183 repo.RegisterArtifact("some-source", fakeArtifact) 184 repo.RegisterArtifact("some-other-source", fakeOtherArtifact) 185 repo.RegisterArtifact("some-mounted-source", fakeMountedArtifact) 186 187 fakeResourceFactory.NewResourceReturns(fakeResource) 188 189 someExitStatus = 0 190 clientErr = nil 191 }) 192 193 AfterEach(func() { 194 cancel() 195 }) 196 197 JustBeforeEach(func() { 198 plan := atc.Plan{ 199 ID: atc.PlanID(planID), 200 Put: putPlan, 201 } 202 203 fakeClient.RunPutStepReturns( 204 worker.PutResult{ExitStatus: someExitStatus, VersionResult: versionResult}, 205 clientErr, 206 ) 207 208 putStep = exec.NewPutStep( 209 plan.ID, 210 *plan.Put, 211 stepMetadata, 212 containerMetadata, 213 fakeResourceFactory, 214 fakeResourceConfigFactory, 215 fakeStrategy, 216 fakeClient, 217 fakeDelegateFactory, 218 ) 219 220 stepOk, stepErr = putStep.Run(ctx, state) 221 }) 222 223 Context("inputs", func() { 224 Context("when inputs are specified with 'all' keyword", func() { 225 BeforeEach(func() { 226 putPlan.Inputs = &atc.InputsConfig{ 227 All: true, 228 } 229 }) 230 231 It("calls RunPutStep with all inputs", func() { 232 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 233 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3)) 234 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact)) 235 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact)) 236 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 237 }) 238 }) 239 240 Context("when inputs are left blank", func() { 241 It("calls RunPutStep with all inputs", func() { 242 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 243 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3)) 244 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact)) 245 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact)) 246 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 247 }) 248 }) 249 250 Context("when only some inputs are specified ", func() { 251 BeforeEach(func() { 252 putPlan.Inputs = &atc.InputsConfig{ 253 Specified: []string{"some-source", "some-other-source"}, 254 } 255 }) 256 257 It("calls RunPutStep with specified inputs", func() { 258 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 259 Expect(containerSpec.ArtifactByPath).To(HaveLen(2)) 260 Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact)) 261 Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 262 }) 263 }) 264 265 Context("when the inputs are detected", func() { 266 BeforeEach(func() { 267 putPlan.Inputs = &atc.InputsConfig{ 268 Detect: true, 269 } 270 }) 271 272 Context("when the params are only strings", func() { 273 BeforeEach(func() { 274 putPlan.Params = atc.Params{ 275 "some-param": "some-source/source", 276 "another-param": "does-not-exist", 277 "number-param": 123, 278 } 279 }) 280 281 It("calls RunPutStep with detected inputs", func() { 282 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 283 Expect(containerSpec.ArtifactByPath).To(HaveLen(1)) 284 Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 285 }) 286 }) 287 288 Context("when the params have maps and slices", func() { 289 BeforeEach(func() { 290 putPlan.Params = atc.Params{ 291 "some-slice": []interface{}{ 292 []interface{}{"some-source/source", "does-not-exist", 123}, 293 []interface{}{"does not exist-2"}, 294 }, 295 "some-map": map[string]interface{}{ 296 "key": "some-other-source/source", 297 }, 298 } 299 }) 300 301 It("calls RunPutStep with detected inputs", func() { 302 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 303 Expect(containerSpec.ArtifactByPath).To(HaveLen(2)) 304 Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact)) 305 Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 306 }) 307 }) 308 }) 309 }) 310 311 It("calls workerClient -> RunPutStep with the appropriate arguments", func() { 312 Expect(fakeClient.RunPutStepCallCount()).To(Equal(1)) 313 actualContext, _, actualOwner, actualContainerSpec, actualWorkerSpec, actualStrategy, actualContainerMetadata, actualProcessSpec, actualEventDelegate, actualResource := fakeClient.RunPutStepArgsForCall(0) 314 315 Expect(actualContext).To(Equal(spanCtx)) 316 Expect(actualOwner).To(Equal(db.NewBuildStepContainerOwner(42, atc.PlanID(planID), 123))) 317 Expect(actualContainerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 318 ResourceType: "some-resource-type", 319 })) 320 Expect(actualContainerSpec.TeamID).To(Equal(123)) 321 Expect(actualContainerSpec.Env).To(Equal(stepMetadata.Env())) 322 Expect(actualContainerSpec.Dir).To(Equal("/tmp/build/put")) 323 324 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3)) 325 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact)) 326 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact)) 327 Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact)) 328 329 Expect(actualWorkerSpec).To(Equal(worker.WorkerSpec{ 330 TeamID: 123, 331 ResourceType: "some-resource-type", 332 })) 333 Expect(actualStrategy).To(Equal(fakeStrategy)) 334 335 Expect(actualContainerMetadata).To(Equal(containerMetadata)) 336 337 Expect(actualProcessSpec).To(Equal( 338 runtime.ProcessSpec{ 339 Path: "/opt/resource/out", 340 Args: []string{resource.ResourcesDir("put")}, 341 StdoutWriter: stdoutBuf, 342 StderrWriter: stderrBuf, 343 })) 344 Expect(actualEventDelegate).To(Equal(fakeDelegate)) 345 Expect(actualResource).To(Equal(fakeResource)) 346 }) 347 348 Context("when using a custom resource type", func() { 349 var fakeImageSpec worker.ImageSpec 350 351 BeforeEach(func() { 352 putPlan.Type = "some-custom-type" 353 354 fakeImageSpec = worker.ImageSpec{ 355 ImageArtifact: new(runtimefakes.FakeArtifact), 356 } 357 358 fakeDelegate.FetchImageReturns(fakeImageSpec, nil) 359 }) 360 361 It("fetches the resource type image and uses it for the container", func() { 362 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 363 364 _, imageResource, types, privileged := fakeDelegate.FetchImageArgsForCall(0) 365 366 By("fetching the type image") 367 Expect(imageResource).To(Equal(atc.ImageResource{ 368 Name: "some-custom-type", 369 Type: "another-custom-type", 370 Source: atc.Source{"some-custom": "((source-var))"}, 371 Params: atc.Params{"some-custom": "((params-var))"}, 372 Version: atc.Version{"some-custom": "version"}, 373 })) 374 375 By("excluding the type from the FetchImage call") 376 Expect(types).To(Equal(atc.VersionedResourceTypes{ 377 { 378 ResourceType: atc.ResourceType{ 379 Name: "another-custom-type", 380 Type: "registry-image", 381 Source: atc.Source{"another-custom": "((source-var))"}, 382 Privileged: true, 383 }, 384 Version: atc.Version{"another-custom": "version"}, 385 }, 386 })) 387 388 By("not being privileged") 389 Expect(privileged).To(BeFalse()) 390 }) 391 392 It("sets the bottom-most type in the worker spec", func() { 393 _, _, _, _, workerSpec, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 394 Expect(workerSpec).To(Equal(worker.WorkerSpec{ 395 TeamID: stepMetadata.TeamID, 396 ResourceType: "registry-image", 397 })) 398 }) 399 400 It("sets the image spec in the container spec", func() { 401 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 402 Expect(containerSpec.ImageSpec).To(Equal(fakeImageSpec)) 403 }) 404 405 Context("when the resource type is privileged", func() { 406 BeforeEach(func() { 407 putPlan.Type = "another-custom-type" 408 }) 409 410 It("fetches the image with privileged", func() { 411 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 412 _, _, _, privileged := fakeDelegate.FetchImageArgsForCall(0) 413 Expect(privileged).To(BeTrue()) 414 }) 415 }) 416 417 Context("when the plan configures tags", func() { 418 BeforeEach(func() { 419 putPlan.Tags = atc.Tags{"plan", "tags"} 420 }) 421 422 It("fetches using the tags", func() { 423 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 424 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 425 Expect(imageResource.Tags).To(Equal(atc.Tags{"plan", "tags"})) 426 }) 427 }) 428 429 Context("when the resource type configures tags", func() { 430 BeforeEach(func() { 431 taggedType, found := putPlan.VersionedResourceTypes.Lookup("some-custom-type") 432 Expect(found).To(BeTrue()) 433 434 taggedType.Tags = atc.Tags{"type", "tags"} 435 436 newTypes := putPlan.VersionedResourceTypes.Without("some-custom-type") 437 newTypes = append(newTypes, taggedType) 438 439 putPlan.VersionedResourceTypes = newTypes 440 }) 441 442 It("fetches using the type tags", func() { 443 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 444 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 445 Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"})) 446 }) 447 448 Context("when the plan ALSO configures tags", func() { 449 BeforeEach(func() { 450 putPlan.Tags = atc.Tags{"plan", "tags"} 451 }) 452 453 It("fetches using only the type tags", func() { 454 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 455 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 456 Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"})) 457 }) 458 }) 459 }) 460 }) 461 462 Context("when the plan specifies tags", func() { 463 BeforeEach(func() { 464 putPlan.Tags = atc.Tags{"some", "tags"} 465 }) 466 467 It("sets them in the WorkerSpec", func() { 468 _, _, _, _, workerSpec, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 469 Expect(workerSpec.Tags).To(Equal([]string{"some", "tags"})) 470 }) 471 }) 472 473 Context("when tracing is enabled", func() { 474 var buildSpan trace.Span 475 476 BeforeEach(func() { 477 tracing.ConfigureTraceProvider(tracetest.NewProvider()) 478 479 spanCtx, buildSpan = tracing.StartSpan(ctx, "build", nil) 480 fakeDelegate.StartSpanReturns(spanCtx, buildSpan) 481 }) 482 483 AfterEach(func() { 484 tracing.Configured = false 485 }) 486 487 It("propagates span context to the worker client", func() { 488 actualCtx, _, _, _, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 489 Expect(actualCtx).To(Equal(spanCtx)) 490 }) 491 492 It("populates the TRACEPARENT env var", func() { 493 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0) 494 Expect(actualContainerSpec.Env).To(ContainElement(MatchRegexp(`TRACEPARENT=.+`))) 495 }) 496 }) 497 498 Context("when creds tracker can initialize the resource", func() { 499 var ( 500 fakeResourceConfig *dbfakes.FakeResourceConfig 501 ) 502 503 BeforeEach(func() { 504 fakeResourceConfig = new(dbfakes.FakeResourceConfig) 505 fakeResourceConfig.IDReturns(1) 506 507 fakeResourceConfigFactory.FindOrCreateResourceConfigReturns(fakeResourceConfig, nil) 508 509 fakeWorker.NameReturns("some-worker") 510 }) 511 512 It("creates a resource with the correct source and params", func() { 513 actualSource, actualParams, _ := fakeResourceFactory.NewResourceArgsForCall(0) 514 Expect(actualSource).To(Equal(atc.Source{"some": "super-secret-source"})) 515 Expect(actualParams).To(Equal(atc.Params{"some": "super-secret-params"})) 516 517 _, _, _, _, _, _, _, _, _, actualResource := fakeClient.RunPutStepArgsForCall(0) 518 Expect(actualResource).To(Equal(fakeResource)) 519 }) 520 521 }) 522 523 It("saves the build output", func() { 524 Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(1)) 525 526 _, plan, actualSource, actualResourceTypes, info := fakeDelegate.SaveOutputArgsForCall(0) 527 Expect(plan.Name).To(Equal("some-name")) 528 Expect(plan.Type).To(Equal("some-resource-type")) 529 Expect(plan.Resource).To(Equal("some-resource")) 530 Expect(actualSource).To(Equal(atc.Source{"some": "super-secret-source"})) 531 Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes)) 532 Expect(info.Version).To(Equal(atc.Version{"some": "version"})) 533 Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}})) 534 }) 535 536 Context("when the step.Plan.Resource is blank", func() { 537 BeforeEach(func() { 538 putPlan.Resource = "" 539 }) 540 541 It("is successful", func() { 542 Expect(stepOk).To(BeTrue()) 543 }) 544 545 It("does not save the build output", func() { 546 Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(0)) 547 }) 548 }) 549 550 Context("when RunPutStep succeeds", func() { 551 It("finishes via the delegate", func() { 552 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 553 _, status, info := fakeDelegate.FinishedArgsForCall(0) 554 Expect(status).To(Equal(exec.ExitStatus(0))) 555 Expect(info.Version).To(Equal(atc.Version{"some": "version"})) 556 Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}})) 557 }) 558 559 It("stores the version result as the step result", func() { 560 Expect(state.StoreResultCallCount()).To(Equal(1)) 561 sID, sVal := state.StoreResultArgsForCall(0) 562 Expect(sID).To(Equal(planID)) 563 Expect(sVal).To(Equal(versionResult)) 564 }) 565 566 It("is successful", func() { 567 Expect(stepOk).To(BeTrue()) 568 }) 569 }) 570 571 Context("when RunPutStep exits unsuccessfully", func() { 572 BeforeEach(func() { 573 versionResult = runtime.VersionResult{} 574 someExitStatus = 42 575 }) 576 577 It("finishes the step via the delegate", func() { 578 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 579 _, status, info := fakeDelegate.FinishedArgsForCall(0) 580 Expect(status).To(Equal(exec.ExitStatus(42))) 581 Expect(info).To(BeZero()) 582 }) 583 584 It("returns nil", func() { 585 Expect(stepErr).ToNot(HaveOccurred()) 586 }) 587 588 It("is not successful", func() { 589 Expect(stepOk).To(BeFalse()) 590 }) 591 }) 592 593 Context("when RunPutStep exits with an error", func() { 594 disaster := errors.New("oh no") 595 596 BeforeEach(func() { 597 versionResult = runtime.VersionResult{} 598 clientErr = disaster 599 }) 600 601 It("does not finish the step via the delegate", func() { 602 Expect(fakeDelegate.FinishedCallCount()).To(Equal(0)) 603 }) 604 605 It("returns the error", func() { 606 Expect(stepErr).To(Equal(disaster)) 607 }) 608 609 It("is not successful", func() { 610 Expect(stepOk).To(BeFalse()) 611 }) 612 }) 613 })