diff --git a/Jenkinsfile b/Jenkinsfile index 93414b8..e412710 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -55,6 +55,7 @@ pipeline { // Validate the example jobs. This will only work for declarative validationErrors += validatePipeline('exampleJobs/parallel/Jenkinsfile') validationErrors += validatePipeline('exampleJobs/globalVariable/Jenkinsfile') + validationErrors += validatePipeline('exampleJobs/whenConditions/Jenkinsfile') // Validate this job validationErrors += validatePipeline('Jenkinsfile') diff --git a/exampleJobs/whenConditions/Jenkinsfile b/exampleJobs/whenConditions/Jenkinsfile new file mode 100644 index 0000000..edb2ae4 --- /dev/null +++ b/exampleJobs/whenConditions/Jenkinsfile @@ -0,0 +1,53 @@ +/** + * Declarative pipeline + */ +pipeline { + + agent none + + stages { + + stage('When branch') { + when { + beforeAgent true + beforeInput true + branch 'master' + } + steps { + echo "This step is run when the branch is 'master'." + } + } + + stage('When buildingTag') { + when { + buildingTag() + } + steps { + echo "This step is run when run from a tag." + } + } + + stage('When on a specific tag') { + when { + tag "release-*" + } + steps { + echo "This step is run when run from a specific tag." + } + } + + stage('When allOf expression') { + when { + allOf { + expression { + return true + } + } + } + steps { + echo "This step is run when the expression is true." + } + } + + } +} \ No newline at end of file diff --git a/pipelineTests/groovy/testSupport/PipelineTestHelper.groovy b/pipelineTests/groovy/testSupport/PipelineTestHelper.groovy index 0b10afe..1886aac 100644 --- a/pipelineTests/groovy/testSupport/PipelineTestHelper.groovy +++ b/pipelineTests/groovy/testSupport/PipelineTestHelper.groovy @@ -117,7 +117,7 @@ class PipelineTestHelper extends BasePipelineTest { // Returned from the stage def stageResult - // Handling of the when. Only supporting expression right now + // Handling of the when. helper.registerAllowedMethod('when', [Closure.class], { Closure whenBody -> // Handle a when expression @@ -131,8 +131,54 @@ class PipelineTestHelper extends BasePipelineTest { return expressionResult }) + // Handle a when branch + helper.registerAllowedMethod('branch', [String.class], { String input -> + + String branchPattern = convertPatternToRegex(input) + def patternResult = (binding.getVariable('BRANCH_NAME') ==~ /${branchPattern}/) + if(patternResult == false) { + throw new WhenExitException("Stage '${stgName}' skipped due to when branch is false") + } + return patternResult + }) + + // Handle a when buildingTag + helper.registerAllowedMethod('buildingTag', [], { + + String tagName = binding.getVariable('TAG_NAME') + if(tagName == null || tagName == '' ) { + throw new WhenExitException("Stage '${stgName}' skipped due to not building a tag") + } + return true + }) + + // Handle a when tag + helper.registerAllowedMethod('tag', [String.class], { String input -> + + String tagPattern = convertPatternToRegex(input) + String tagName = binding.getVariable('TAG_NAME') + if(tagName != null && tagName != '' ) { + def patternResult = (tagName ==~ /${tagPattern}/) + if (patternResult == true) { + return true + } + } + throw new WhenExitException("Stage '${stgName}' skipped due to no building a specific tag") + }) + + // Handle a when beforeAgent + helper.registerAllowedMethod('beforeAgent', [Boolean.class], null ) + + // Handle a when beforeInput + helper.registerAllowedMethod('beforeInput', [Boolean.class], null ) + + // Handle the boolean when cases without actually checking them + helper.registerAllowedMethod('not', [Closure.class], null ) + helper.registerAllowedMethod('allOf', [Closure.class], null ) + helper.registerAllowedMethod('anyOf', [Closure.class], null ) + + // TODO - handle other when clauses in the when - // branch : 'when { branch 'master' }' // environment : 'when { environment name: 'DEPLOY_TO', value: 'production' }' // Run the when body and return any result @@ -164,8 +210,16 @@ class PipelineTestHelper extends BasePipelineTest { } // Unregister - helper.unRegisterAllowedMethod('when', [Closure.class.class]) + helper.unRegisterAllowedMethod('when', [Closure.class]) helper.unRegisterAllowedMethod('expression', [Closure.class]) + helper.unRegisterAllowedMethod('branch', [Closure.class]) + helper.unRegisterAllowedMethod('buildingTag', [Closure.class]) + helper.unRegisterAllowedMethod('tag', [Closure.class]) + helper.unRegisterAllowedMethod('beforeAgent', [Closure.class]) + helper.unRegisterAllowedMethod('beforeInput', [Closure.class]) + helper.unRegisterAllowedMethod('not', [Closure.class]) + helper.unRegisterAllowedMethod('allOf', [Closure.class]) + helper.unRegisterAllowedMethod('anyOf', [Closure.class]) return stageResult }) @@ -292,6 +346,12 @@ class PipelineTestHelper extends BasePipelineTest { */ binding.setVariable('PATH', '/some/path') + /** + * BRANCH_NAME && TAG_NAME + */ + binding.setVariable('BRANCH_NAME', 'master') + binding.setVariable('TAG_NAME', null) + /** * Initialize a basic Env passed in from Jenkins - may need to override in specific tests */ @@ -333,4 +393,11 @@ class PipelineTestHelper extends BasePipelineTest { def env = binding.getVariable('env') as Expando env[name] = val } + + /** + * Helper for turning a GLOB-style matcher into a java Matcher + */ + def convertPatternToRegex(String pattern) { + return pattern.replaceAll('\\*', '.*') + } } diff --git a/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_null_gradle_null.txt b/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_null_gradle_null.txt index 59f77c6..2f02f3c 100644 --- a/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_null_gradle_null.txt +++ b/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_null_gradle_null.txt @@ -22,6 +22,7 @@ Jenkinsfile.script(groovy.lang.Closure) Jenkinsfile.validateDeclarativePipeline(exampleJobs/parallel/Jenkinsfile) Jenkinsfile.validateDeclarativePipeline(exampleJobs/globalVariable/Jenkinsfile) + Jenkinsfile.validateDeclarativePipeline(exampleJobs/whenConditions/Jenkinsfile) Jenkinsfile.validateDeclarativePipeline(Jenkinsfile) Jenkinsfile.stage(build, groovy.lang.Closure) Jenkinsfile.steps(groovy.lang.Closure) diff --git a/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_true_gradle_test.txt b/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_true_gradle_test.txt index 243c803..4426cd0 100644 --- a/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_true_gradle_test.txt +++ b/pipelineTests/groovy/tests/callstacks/JenkinsfileTestSpec_Jenkinsfile_Should_Run_Gradle_validate_true_gradle_test.txt @@ -22,6 +22,7 @@ Jenkinsfile.script(groovy.lang.Closure) Jenkinsfile.validateDeclarativePipeline(exampleJobs/parallel/Jenkinsfile) Jenkinsfile.validateDeclarativePipeline(exampleJobs/globalVariable/Jenkinsfile) + Jenkinsfile.validateDeclarativePipeline(exampleJobs/whenConditions/Jenkinsfile) Jenkinsfile.validateDeclarativePipeline(Jenkinsfile) Jenkinsfile.stage(build, groovy.lang.Closure) Jenkinsfile.steps(groovy.lang.Closure) diff --git a/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_master.txt b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_master.txt new file mode 100644 index 0000000..ce75b5c --- /dev/null +++ b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_master.txt @@ -0,0 +1,23 @@ + Jenkinsfile.run() + Jenkinsfile.pipeline(groovy.lang.Closure) + Jenkinsfile.agent(groovy.lang.Closure) + Jenkinsfile.stages(groovy.lang.Closure) + Jenkinsfile.stage(When branch, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.beforeAgent(true) + Jenkinsfile.beforeInput(true) + Jenkinsfile.branch(master) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the branch is 'master'.) + Jenkinsfile.stage(When buildingTag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.buildingTag() + Jenkinsfile.stage(When on a specific tag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.tag(release-*) + Jenkinsfile.stage(When allOf expression, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.allOf(groovy.lang.Closure) + Jenkinsfile.expression(groovy.lang.Closure) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the expression is true.) diff --git a/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_notmaster.txt b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_notmaster.txt new file mode 100644 index 0000000..9a2a094 --- /dev/null +++ b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_branch_is_notmaster.txt @@ -0,0 +1,21 @@ + Jenkinsfile.run() + Jenkinsfile.pipeline(groovy.lang.Closure) + Jenkinsfile.agent(groovy.lang.Closure) + Jenkinsfile.stages(groovy.lang.Closure) + Jenkinsfile.stage(When branch, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.beforeAgent(true) + Jenkinsfile.beforeInput(true) + Jenkinsfile.branch(master) + Jenkinsfile.stage(When buildingTag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.buildingTag() + Jenkinsfile.stage(When on a specific tag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.tag(release-*) + Jenkinsfile.stage(When allOf expression, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.allOf(groovy.lang.Closure) + Jenkinsfile.expression(groovy.lang.Closure) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the expression is true.) diff --git a/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_specific_tag.txt b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_specific_tag.txt new file mode 100644 index 0000000..e97c0c8 --- /dev/null +++ b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_specific_tag.txt @@ -0,0 +1,27 @@ + Jenkinsfile.run() + Jenkinsfile.pipeline(groovy.lang.Closure) + Jenkinsfile.agent(groovy.lang.Closure) + Jenkinsfile.stages(groovy.lang.Closure) + Jenkinsfile.stage(When branch, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.beforeAgent(true) + Jenkinsfile.beforeInput(true) + Jenkinsfile.branch(master) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the branch is 'master'.) + Jenkinsfile.stage(When buildingTag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.buildingTag() + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when run from a tag.) + Jenkinsfile.stage(When on a specific tag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.tag(release-*) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when run from a specific tag.) + Jenkinsfile.stage(When allOf expression, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.allOf(groovy.lang.Closure) + Jenkinsfile.expression(groovy.lang.Closure) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the expression is true.) diff --git a/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_tag.txt b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_tag.txt new file mode 100644 index 0000000..54a0850 --- /dev/null +++ b/pipelineTests/groovy/tests/callstacks/WhenConditionsJobTestSpec_when_on_a_tag.txt @@ -0,0 +1,25 @@ + Jenkinsfile.run() + Jenkinsfile.pipeline(groovy.lang.Closure) + Jenkinsfile.agent(groovy.lang.Closure) + Jenkinsfile.stages(groovy.lang.Closure) + Jenkinsfile.stage(When branch, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.beforeAgent(true) + Jenkinsfile.beforeInput(true) + Jenkinsfile.branch(master) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the branch is 'master'.) + Jenkinsfile.stage(When buildingTag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.buildingTag() + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when run from a tag.) + Jenkinsfile.stage(When on a specific tag, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.tag(release-*) + Jenkinsfile.stage(When allOf expression, groovy.lang.Closure) + Jenkinsfile.when(groovy.lang.Closure) + Jenkinsfile.allOf(groovy.lang.Closure) + Jenkinsfile.expression(groovy.lang.Closure) + Jenkinsfile.steps(groovy.lang.Closure) + Jenkinsfile.echo(This step is run when the expression is true.) diff --git a/pipelineTests/groovy/tests/job/exampleJobs/whenConditions/WhenConditionsJobTestSpec.groovy b/pipelineTests/groovy/tests/job/exampleJobs/whenConditions/WhenConditionsJobTestSpec.groovy new file mode 100644 index 0000000..73ad316 --- /dev/null +++ b/pipelineTests/groovy/tests/job/exampleJobs/whenConditions/WhenConditionsJobTestSpec.groovy @@ -0,0 +1,65 @@ +package tests.job.exampleJobs.whenConditions + +import spock.lang.* +import testSupport.PipelineSpockTestBase + +class WhenConditionsJobTestSpec extends PipelineSpockTestBase { + + def "whenConditions Jenkinsfile test"() { + + when: + runScript('exampleJobs/whenConditions/Jenkinsfile') + + then: + printCallStack() + assertJobStatusSuccess() + + } + + @Unroll + def 'when branch is #name'() { + + given: + binding.setVariable('BRANCH_NAME', name) + + when: + runScript('exampleJobs/whenConditions/Jenkinsfile') + + then: + printCallStack() + assertJobStatusSuccess() + + then: + testNonRegression("when_branch_is_${name}") + + where: + name | _ + 'master' | _ + 'notmaster' | _ + + } + + @Unroll + def 'when running on #scenario'() { + + given: + binding.setVariable('TAG_NAME', tag) + + when: + runScript('exampleJobs/whenConditions/Jenkinsfile') + + then: + printCallStack() + assertJobStatusSuccess() + + then: + testNonRegression("when_on_${scenario.replaceAll(' ', '_')}") + + where: + tag | scenario + 'this-is-a-tag' | 'a tag' + 'release-1.0.0' | 'a specific tag' + + } + +}