import groovy.mock.interceptor.MockFor import static groovy.test.GroovyAssert.* import groovy.util.GroovyTestCase import org.wikimedia.integration.Pipeline import org.wikimedia.integration.PipelineRunner import org.wikimedia.integration.PipelineStage import org.wikimedia.integration.ExecutionGraph import org.wikimedia.integration.ExecutionContext class PipelineStageTest extends GroovyTestCase { private class WorkflowScript {} // Mock for Jenkins Pipeline workflow context void testDefaultConfig_shortHand() { // shorthand with just name is: build and run a variant def cfg = [name: "foo"] assert PipelineStage.defaultConfig(cfg) == [ name: "foo", build: '${.stage}', run: [ image: '${.imageID}', arguments: [], ], ] } void testDefaultConfig_run() { def cfg = [ name: "foo", build: "foo", run: true, ] assert PipelineStage.defaultConfig(cfg) == [ name: "foo", build: "foo", // run: true means run the built image run: [ image: '${.imageID}', arguments: [], ], ] } void testDefaultConfig_publish() { def cfg = [ publish: [ image: true, ], ] assert PipelineStage.defaultConfig(cfg) == [ publish: [ image: [ // defaults to the previously built image id: '${.imageID}', // defaults to the project name name: '${setup.project}', // defaults to the pipeline start timestamp and this stage name tag: '${setup.timestamp}-${.stage}', // defaults to [] tags: [], ], ], ] } void testDefaultConfig_deploy() { def cfg = [ deploy: [ chart: "chart.tar.gz", ], ] assert PipelineStage.defaultConfig(cfg) == [ deploy: [ chart: "chart.tar.gz", // defaults to the previously published image image: '${.publishedImage}', // defaults to "ci" cluster: "ci", // defaults to true test: true, ], ] cfg = [ deploy: [ chart: "chart.tar.gz", image: "fooimage", cluster: "foocluster", test: true, ], ] assert PipelineStage.defaultConfig(cfg) == [ deploy: [ chart: "chart.tar.gz", image: "fooimage", cluster: "foocluster", test: true, ], ] } void testBuild() { def pipeline = new Pipeline("foo", [ stages: [ [ name: "foo", build: "foovariant", ], ] ]) def mockRunner = new MockFor(PipelineRunner) def stack = pipeline.stack() def stage = stack[1][0] stubStageContexts(stack, [ setup: { ctx -> ctx["imageLabels"] = [foo: "foolabel", bar: "barlabel"] }, ]) mockRunner.demand.build { variant, labels -> assert variant == "foovariant" assert labels == [foo: "foolabel", bar: "barlabel"] "foo-image-id" } mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.build(ws, runner) } assert stage.context["imageID"] == "foo-image-id" } void testDeploy_withTest() { def pipeline = new Pipeline("foo", [ stages: [ [ name: "foo", build: "foovariant", publish: [ image: true, ], ], [ name: "bar", deploy: [ image: '${foo.publishedImage}', chart: 'https://an.example/chart/foo-${.stage}.tar.gz', tag: '${.stage}-tag', test: true, ], ], ] ]) def mockRunner = new MockFor(PipelineRunner) def stack = pipeline.stack() def stage = stack[2][0] stubStageContexts(stack, [ foo: { ctx -> ctx["imageID"] = "foo-image-id" ctx["imageName"] = "foo-project" ctx["imageFullName"] = "registry.example/bar/foo-project" ctx["imageTag"] = "0000-00-00-000000-foo" ctx["imageTags"] = ["0000-00-00-000000-foo"] ctx["publishedImage"] = "registry.example/bar/foo-project:0000-00-00-000000-foo" }, ]) mockRunner.demand.deployWithChart { chart, image, tag -> assert chart == "https://an.example/chart/foo-bar.tar.gz" assert image == "registry.example/bar/foo-project:0000-00-00-000000-foo" assert tag == "bar-tag" "bar-release-name" } mockRunner.demand.testRelease { releaseName -> assert releaseName == "bar-release-name" } mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.deploy(ws, runner) } assert stage.context["releaseName"] == "bar-release-name" } void testDeploy_withoutTest() { def pipeline = new Pipeline("foo", [ stages: [ [ name: "foo", build: "foovariant", publish: [ image: true, ], ], [ name: "bar", deploy: [ image: '${foo.publishedImage}', chart: 'https://an.example/chart/foo-${.stage}.tar.gz', tag: '${.stage}-tag', test: false, ], ], ] ]) def mockRunner = new MockFor(PipelineRunner) def stack = pipeline.stack() def stage = stack[2][0] stubStageContexts(stack, [ foo: { ctx -> ctx["imageID"] = "foo-image-id" ctx["imageName"] = "foo-project" ctx["imageFullName"] = "registry.example/bar/foo-project" ctx["imageTag"] = "0000-00-00-000000-foo" ctx["imageTags"] = ["0000-00-00-000000-foo"] ctx["publishedImage"] = "registry.example/bar/foo-project:0000-00-00-000000-foo" }, ]) mockRunner.demand.deployWithChart { chart, image, tag -> assert chart == "https://an.example/chart/foo-bar.tar.gz" assert image == "registry.example/bar/foo-project:0000-00-00-000000-foo" assert tag == "bar-tag" "bar-release-name" } mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.deploy(ws, runner) } assert stage.context["releaseName"] == "bar-release-name" } void testExports() { def pipeline = new Pipeline("foo", [ stages: [ [ name: "foo", build: "foovariant", exports: [ thing: '${.imageID}-${setup.timestamp}', ], ], ] ]) def mockRunner = new MockFor(PipelineRunner) def mockWS = new MockFor(WorkflowScript) def stack = pipeline.stack() def stage = stack[1][0] stubStageContexts(stack, [ setup: { ctx -> ctx["timestamp"] = "0000-00-00-000000" }, foo: { ctx -> ctx["imageID"] = "foo-image-id" }, ]) mockWS.demand.echo { msg -> assert msg == "exported foo.thing='foo-image-id-0000-00-00-000000'" } mockWS.use { mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.exports(ws, runner) } } assert stage.context["thing"] == "foo-image-id-0000-00-00-000000" } void testPublish() { def pipeline = new Pipeline("foopipeline", [ stages: [ [ name: "built", build: "foovariant", ], [ name: "published", publish: [ image: [ id: '${built.imageID}', ], ], ] ] ]) def mockRunner = new MockFor(PipelineRunner) def stack = pipeline.stack() def stage = stack[2][0] stubStageContexts(stack, [ setup: { ctx -> ctx["project"] = "foo-project" ctx["timestamp"] = "0000-00-00-000000" ctx["imageLabels"] = [] }, built: { ctx -> ctx["imageID"] = "foo-image-id" }, ]) mockRunner.demand.registerAs { imageID, imageName, tag -> assert imageID == "foo-image-id" assert imageName == "foo-project" assert tag == "0000-00-00-000000-published" } mockRunner.demand.qualifyRegistryPath { imageName -> assert imageName == "foo-project" "registry.example/bar/foo-project" } mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.publish(ws, runner) } assert stage.context["imageName"] == "foo-project" assert stage.context["imageFullName"] == "registry.example/bar/foo-project" assert stage.context["imageTag"] == "0000-00-00-000000-published" assert stage.context["imageTags"] == ["0000-00-00-000000-published"] assert stage.context["publishedImage"] == "registry.example/bar/foo-project:0000-00-00-000000-published" } void testRun() { def pipeline = new Pipeline("foo", [ stages: [ [ name: "foo", build: "foovariant", ], [ name: "bar", run: [ image: '${foo.imageID}', arguments: ['${.stage}arg'], ], ], ] ]) def mockRunner = new MockFor(PipelineRunner) def stack = pipeline.stack() def stage = stack[2][0] stubStageContexts(stack, [ foo: { ctx -> ctx["imageID"] = "foo-image-id" }, ]) mockRunner.demand.run { image, args -> assert image == "foo-image-id" assert args == ["bararg"] } mockRunner.use { def ws = new WorkflowScript() def runner = new PipelineRunner(ws) stage.run(ws, runner) } } private void stubStageContexts(stack, stubs) { stack.each { stages -> stages.each { stage -> stage.context["stage"] = stage.name stubs.each { stageName, stub -> if (stage.name == stageName) { stub(stage.context) } } } } } }