Skip to content

Commit

Permalink
Revisit groovy delegates and property/method lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
Willem Borgesius authored and Willem Borgesius committed Sep 17, 2020
1 parent defcf58 commit a658ae4
Show file tree
Hide file tree
Showing 16 changed files with 140 additions and 149 deletions.
82 changes: 41 additions & 41 deletions src/main/groovy/com/lesfurets/jenkins/unit/BasePipelineTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,27 @@ abstract class BasePipelineTest {
helper.registerAllowedMethod("archiveArtifacts", [Map])
helper.registerAllowedMethod('archiveArtifacts', [String])
helper.registerAllowedMethod("bat", [String])
helper.registerAllowedMethod('bat', [Map.class], {m->
if(m.returnStdout){
helper.registerAllowedMethod('bat', [Map.class], { m ->
if (m.returnStdout) {
return """uno-dos@localhost> "${m.script}"\r\naaa\r\nbbb\r\nccc"""
}
return null
})
helper.registerAllowedMethod("build", [Map.class], {
[
getNumber:{100500},
getDescription:{"Dummy build description"},
getFullProjectName:{"some_dir/some_job"},
getProjectName:{"some_job"},
getNumber : { 100500 },
getDescription : { "Dummy build description" },
getFullProjectName: { "some_dir/some_job" },
getProjectName : { "some_job" },
]
})
helper.registerAllowedMethod("buildDiscarder", [Object])
helper.registerAllowedMethod("skipStagesAfterUnstable")
helper.registerAllowedMethod("checkout", [Map])
helper.registerAllowedMethod("choice", [Map])
helper.registerAllowedMethod('cifsPublisher', [Map], {true})
helper.registerAllowedMethod('cifsPublisher', [Map], { true })
helper.registerAllowedMethod('cleanWs')
helper.registerAllowedMethod('copyArtifacts', [Map], {true})
helper.registerAllowedMethod('copyArtifacts', [Map], { true })
helper.registerAllowedMethod("cron", [String])
helper.registerAllowedMethod('deleteDir')
helper.registerAllowedMethod("dir", [String, Closure], { String path, Closure c ->
Expand All @@ -145,11 +145,11 @@ abstract class BasePipelineTest {
return !System.properties['os.name'].toLowerCase().contains('windows')
})
helper.registerAllowedMethod("junit", [String])
helper.registerAllowedMethod("library", [String], {String expression ->
helper.registerAllowedMethod("library", [String], { String expression ->
helper.getLibLoader().loadImplicitLibraries()
helper.getLibLoader().loadLibrary(expression)
helper.setGlobalVars(binding)
return new LibClassLoader(helper,null)
return new LibClassLoader(helper, null)
})
helper.registerAllowedMethod("logRotator", [Map])
helper.registerAllowedMethod('mail', [Map])
Expand All @@ -160,8 +160,8 @@ abstract class BasePipelineTest {
helper.registerAllowedMethod("properties", [List])
helper.registerAllowedMethod("pwd", [], { 'workspaceDirMocked' })
helper.registerAllowedMethod("pwd", [Map], { 'tempDirMocked' })
helper.registerAllowedMethod('readFile', [Map], { args -> helper.readFile(args )})
helper.registerAllowedMethod('readFile', [String], { args -> helper.readFile(args )})
helper.registerAllowedMethod('readFile', [Map], { args -> helper.readFile(args) })
helper.registerAllowedMethod('readFile', [String], { args -> helper.readFile(args) })
helper.registerAllowedMethod("retry", [Integer, Closure], { Integer count, Closure c ->
def attempts = 0
while (attempts <= count) {
Expand All @@ -170,7 +170,7 @@ abstract class BasePipelineTest {
c.delegate = delegate
helper.callClosure(c)
break
} catch(err) {
} catch (err) {
if (attempts == count) {
throw err
}
Expand All @@ -182,7 +182,7 @@ abstract class BasePipelineTest {
helper.registerAllowedMethod('skipDefaultCheckout')
helper.registerAllowedMethod('sleep')
helper.registerAllowedMethod('specific', [String])
helper.registerAllowedMethod('sshPublisher', [Map], {true})
helper.registerAllowedMethod('sshPublisher', [Map], { true })
helper.registerAllowedMethod('stash', [Map])
helper.registerAllowedMethod("stage", [String])
helper.registerAllowedMethod("stage", [String, Closure])
Expand Down Expand Up @@ -230,36 +230,36 @@ abstract class BasePipelineTest {
binding.setVariable('env', stashedEnv)
}
})
helper.registerAllowedMethod("withKubeConfig", [Map,Closure])
helper.registerAllowedMethod("withKubeConfig", [List,Closure])
helper.registerAllowedMethod("withKubeCredentials", [Map,Closure])
helper.registerAllowedMethod("withKubeCredentials", [List,Closure])
helper.registerAllowedMethod("withKubeConfig", [Map, Closure])
helper.registerAllowedMethod("withKubeConfig", [List, Closure])
helper.registerAllowedMethod("withKubeCredentials", [Map, Closure])
helper.registerAllowedMethod("withKubeCredentials", [List, Closure])
helper.registerAllowedMethod('writeFile', [Map])
helper.registerAllowedMethod("ws", [String, Closure])
}

void setVariables() {
binding.setVariable('currentBuild', [
absoluteUrl: 'http://example.com/dummy',
buildVariables: [:],
changeSets: [],
currentResult: 'SUCCESS',
description: 'dummy',
displayName: '#1',
duration: 1,
durationString: '1 ms',
fullDisplayName: 'dummy #1',
fullProjectName: 'dummy',
id: '1',
keepLog: false,
nextBuild: null,
number: 1,
previousBuild: null,
projectName: 'dummy',
result: 'SUCCESS',
startTimeInMillis: 1,
timeInMillis: 1,
upstreamBuilds: [],
absoluteUrl : 'http://example.com/dummy',
buildVariables : [:],
changeSets : [],
currentResult : 'SUCCESS',
description : 'dummy',
displayName : '#1',
duration : 1,
durationString : '1 ms',
fullDisplayName : 'dummy #1',
fullProjectName : 'dummy',
id : '1',
keepLog : false,
nextBuild : null,
number : 1,
previousBuild : null,
projectName : 'dummy',
result : 'SUCCESS',
startTimeInMillis: 1,
timeInMillis : 1,
upstreamBuilds : [],
])
binding.setVariable('docker', new DockerMock())
binding.setVariable('env', [:])
Expand All @@ -284,7 +284,7 @@ abstract class BasePipelineTest {
binding.getVariable('currentBuild').id = 2
binding.getVariable('currentBuild').number = 2
binding.getVariable('currentBuild').previousBuild = [id: 1, number: 1, result: status]
println("previousBuild: ${ binding.getVariable('currentBuild').previousBuild}")
println("previousBuild: ${binding.getVariable('currentBuild').previousBuild}")
}

/**
Expand Down Expand Up @@ -340,8 +340,8 @@ abstract class BasePipelineTest {
// and reusing the instance of this helper class.
if (cachedStackDump == null) {
cachedStackDump = helper.callStack.stream()
.map { it -> it.toString() }
.collect(joining('\n'))
.map { it -> it.toString() }
.collect(joining('\n'))
}
return cachedStackDump
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import groovy.transform.ToString

import static com.lesfurets.jenkins.unit.declarative.DeclarativePipeline.createComponent
import static com.lesfurets.jenkins.unit.declarative.DeclarativePipeline.executeWith
import static groovy.lang.Closure.DELEGATE_ONLY
import static groovy.lang.Closure.DELEGATE_FIRST

@ToString(includePackage = false, includeNames = true, ignoreNulls = true)
class AgentDeclaration {
Expand Down Expand Up @@ -40,16 +40,16 @@ class AgentDeclaration {
this.docker = new DockerAgentDeclaration().with { it.image = image; it }
}

def docker(@DelegatesTo(strategy = DELEGATE_ONLY, value = DockerAgentDeclaration) Closure closure) {
this.docker = createComponent(DockerAgentDeclaration, closure)
def docker(@DelegatesTo(strategy = DELEGATE_FIRST, value = DockerAgentDeclaration) Closure closure) {
this.docker = createComponent(this, DockerAgentDeclaration, closure)
}

def kubernetes(Object kubernetesAgent) {
this.@kubernetes = kubernetesAgent as KubernetesAgentDeclaration
}

def kubernetes(@DelegatesTo(strategy = DELEGATE_ONLY, value = KubernetesAgentDeclaration) Closure closure) {
this.@kubernetes = createComponent(KubernetesAgentDeclaration, closure)
def kubernetes(@DelegatesTo(strategy = DELEGATE_FIRST, value = KubernetesAgentDeclaration) Closure closure) {
this.@kubernetes = createComponent(this, KubernetesAgentDeclaration, closure)
}

def dockerfile(boolean dockerfile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@ class DeclarativePipeline extends GenericPipelineDeclaration {
properties.put('scm', 'scm')
}

def getParams() {
return binding?.params
}

def propertyMissing(String name) {
if (properties.containsKey(name)) {
return properties.get(name)
} else {
throw new IllegalStateException("Missing $name")
throw new MissingPropertyException(name)
}
}

Expand All @@ -44,8 +40,8 @@ class DeclarativePipeline extends GenericPipelineDeclaration {
this.params = new ParametersDeclaration().with { it.label = o; it }
}

def parameters(@DelegatesTo(strategy=Closure.DELEGATE_ONLY, value=ParametersDeclaration) Closure closure) {
this.params = createComponent(ParametersDeclaration, closure)
def parameters(@DelegatesTo(strategy=DELEGATE_FIRST, value=ParametersDeclaration) Closure closure) {
this.params = createComponent(this, ParametersDeclaration, closure)
}

def execute(Object delegate) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.lesfurets.jenkins.unit.declarative

import static com.lesfurets.jenkins.unit.MethodSignature.method

import com.lesfurets.jenkins.unit.BasePipelineTest

import static com.lesfurets.jenkins.unit.MethodSignature.method

@groovy.transform.InheritConstructors
abstract class DeclarativePipelineTest extends BasePipelineTest {

def pipelineInterceptor = { Closure closure ->
GenericPipelineDeclaration.binding = delegate.binding
GenericPipelineDeclaration.createComponent(DeclarativePipeline, closure).execute(delegate)
GenericPipelineDeclaration.binding = binding
GenericPipelineDeclaration.createComponent(this, DeclarativePipeline, closure).execute(delegate)
}

def paramInterceptor = { Map desc ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.lesfurets.jenkins.unit.declarative


import static groovy.lang.Closure.DELEGATE_FIRST
import static groovy.lang.Closure.DELEGATE_ONLY

abstract class GenericPipelineDeclaration {

Expand All @@ -13,35 +11,23 @@ abstract class GenericPipelineDeclaration {
Map<String, StageDeclaration> stages = [:]
static def binding = null

static <T> T createComponent(Class<T> componentType,
@DelegatesTo(strategy = DELEGATE_ONLY) Closure<T> closure) {
static <T> T createComponent(Object owner, Class<T> componentType,
@DelegatesTo(strategy = DELEGATE_FIRST) Closure<T> closure) {
// declare componentInstance as final to prevent any multithreaded issues, since it is used inside closure
final def componentInstance = componentType.newInstance()
def rehydrate = closure.rehydrate(componentInstance, this, this)
rehydrate.resolveStrategy = DELEGATE_ONLY
def rehydrate = closure.rehydrate(componentInstance, owner ? owner : componentInstance, this)
if (binding && componentInstance.hasProperty('binding') && componentInstance.binding != binding) {
componentInstance.binding = binding
componentInstance.metaClass.getProperty = { String name ->
def retVal
def metaProperty = componentInstance.metaClass.getMetaProperty(closure)
if (metaProperty) {
retVal = metaProperty.getProperty(name)
}
else {
retVal = binding.getProperty(name)
}
return retVal
}
}
rehydrate.call()
return componentInstance
}

static <T> T executeOn(@DelegatesTo.Target Object delegate,
@DelegatesTo(strategy = DELEGATE_ONLY) Closure<T> closure) {
@DelegatesTo(strategy = DELEGATE_FIRST) Closure<T> closure) {
if (closure) {
def cl = closure.rehydrate(delegate, delegate, delegate)
cl.resolveStrategy = DELEGATE_ONLY
cl.resolveStrategy = DELEGATE_FIRST
return cl.call()
}
return null
Expand All @@ -61,8 +47,8 @@ abstract class GenericPipelineDeclaration {
this.agent = new AgentDeclaration().with { it.label = o; it }
}

def agent(@DelegatesTo(strategy = Closure.DELEGATE_ONLY, value = AgentDeclaration) Closure closure) {
this.agent = createComponent(AgentDeclaration, closure)
def agent(@DelegatesTo(strategy = DELEGATE_FIRST, value = AgentDeclaration) Closure closure) {
this.agent = createComponent(this, AgentDeclaration, closure)
}

def environment(Closure closure) {
Expand All @@ -73,29 +59,40 @@ abstract class GenericPipelineDeclaration {
this.tools = closure
}

def post(@DelegatesTo(strategy = DELEGATE_ONLY, value = PostDeclaration) Closure closure) {
this.post = createComponent(PostDeclaration, closure)
def post(@DelegatesTo(strategy = DELEGATE_FIRST, value = PostDeclaration) Closure closure) {
this.post = createComponent(this, PostDeclaration, closure)
}

def stages(@DelegatesTo(DeclarativePipeline) Closure closure) {
closure.call()
}

def stage(String name,
@DelegatesTo(strategy = DELEGATE_ONLY, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(StageDeclaration, closure).with { it.name = name; it })
}

def getCurrentBuild() {
return binding?.currentBuild
}

def getEnv() {
return binding?.env
@DelegatesTo(strategy = DELEGATE_FIRST, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(this, StageDeclaration, closure).with { it.name = name; it })
}

def getParams() {
return binding?.params
def getProperty(String propertyName) {
def metaProperty = this.metaClass.getMetaProperty(propertyName)
if (metaProperty) {
return metaProperty.getProperty(this)
} else {
if (binding?.hasProperty(propertyName) || binding?.hasVariable(propertyName)) {
return binding.getProperty(propertyName)
}
if (binding?.hasVariable("params") && (binding?.getProperty("params") as Map).containsKey(propertyName)) {
return (binding?.getProperty("params") as Map).get(propertyName)
}
if (binding?.hasVariable("env") && (binding?.getProperty("env") as Map).containsKey(propertyName)) {
return (binding?.getProperty("env") as Map).get(propertyName)
}
def metaMethod = this.metaClass.getMetaMethod("propertyMissing", propertyName)
if (metaMethod) {
metaMethod.invoke(this, propertyName)
} else {
throw new MissingPropertyException(name)
}
}
}

def execute(Object delegate) {
Expand All @@ -107,7 +104,7 @@ abstract class GenericPipelineDeclaration {
env.currentBuild = delegate.binding.currentBuild

def cl = this.environment.rehydrate(env, delegate, this)
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl.resolveStrategy = DELEGATE_FIRST
cl.call()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.lesfurets.jenkins.unit.declarative

import static groovy.lang.Closure.DELEGATE_ONLY
import static groovy.lang.Closure.DELEGATE_FIRST
//import static com.lesfurets.jenkins.unit.declarative.DeclarativePipeline.executeOn

class ParallelDeclaration extends GenericPipelineDeclaration {
Expand All @@ -16,8 +16,8 @@ class ParallelDeclaration extends GenericPipelineDeclaration {
}

def stage(String name,
@DelegatesTo(strategy = DELEGATE_ONLY, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(StageDeclaration, closure).with{it.name = name;it} )
@DelegatesTo(strategy = DELEGATE_FIRST, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(this, StageDeclaration, closure).with{it.name = name;it} )
}

def execute(Object delegate) {
Expand All @@ -27,4 +27,4 @@ class ParallelDeclaration extends GenericPipelineDeclaration {
}
}

}
}
Loading

0 comments on commit a658ae4

Please sign in to comment.