-
Notifications
You must be signed in to change notification settings - Fork 396
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from lesfurets/develop
Pre release 0.12
- Loading branch information
Showing
51 changed files
with
1,251 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -221,6 +221,91 @@ This will work fine for such a project structure: | |
└── TestExampleJob.groovy | ||
``` | ||
|
||
## Testing Shared Libraries | ||
|
||
With [Shared Libraries](https://jenkins.io/doc/book/pipeline/shared-libraries/) Jenkins lets you share common code | ||
on pipelines across different repositories of your organization. | ||
Shared libraries are configured via a settings interface in Jenkins and imported | ||
with `@Library` annotation in your scripts. | ||
|
||
Testing pipeline scripts using external libraries is not trivial because the shared library code | ||
is checked in another repository. | ||
JenkinsPipelineUnit lets you test shared libraries and pipelines depending on these libraries. | ||
|
||
Here is an example pipeline using a shared library: | ||
|
||
```groovy | ||
@Library('commons') | ||
import net.courtanet.jenkins.Utils | ||
sayHello 'World' | ||
node() { | ||
stage ('Checkout') { | ||
def utils = new Utils() | ||
checkout "${utils.gitTools()}" | ||
} | ||
stage ('Build') { | ||
sh './gradlew build' | ||
} | ||
stage ('Post Build') { | ||
String json = libraryResource 'net/courtanet/jenkins/request.json' | ||
sh "curl -H 'Content-Type: application/json' -X POST -d '$json' ${acme.url}" | ||
} | ||
} | ||
``` | ||
|
||
This pipeline is using a shared library called `commons`. | ||
Now lets test it: | ||
|
||
```groovy | ||
String clonePath = 'path/to/clone' | ||
def library = library() | ||
.name('commons') | ||
.retriever(gitSource('[email protected]:devteam/lesfurets-jenkins-shared.git')) | ||
.targetPath(clonePath) | ||
.defaultVersion("master") | ||
.allowOverride(true) | ||
.implicit(false) | ||
.build() | ||
helper.registerSharedLibrary(library) | ||
loadScript("job/library/exampleJob.jenkins") | ||
printCallStack() | ||
``` | ||
|
||
Notice how we defined the shared library and registered it to the helper. | ||
Library definition is done via a fluent API which lets you set the same configurations as in | ||
[Jenkins Global Pipeline Libraries](https://jenkins.io/doc/book/pipeline/shared-libraries/#using-libraries). | ||
|
||
The `retriever` and `targetPath` fields tell the framework how to fetch the sources of the library, in which local path. | ||
The framework comes with two naive but useful retrievers, `gitSource` and `localSource`. | ||
You can write your own retriever by implementing the `SourceRetriever` interface. | ||
|
||
Note that properties `defaultVersion`, `allowOverride` and `implicit` are optional with | ||
default values `master`, `true` and `false`. | ||
|
||
Now if we execute this test, the framework will fetch the sources from the Git repository and | ||
load classes, scripts, global variables and resources found in the library. | ||
The callstack of this execution will look like the following: | ||
|
||
```text | ||
Loading shared library commons with version master | ||
libraryJob.run() | ||
libraryJob.sayHello(World) | ||
sayHello.echo(Hello, World.) | ||
libraryJob.node(groovy.lang.Closure) | ||
libraryJob.stage(Checkout, groovy.lang.Closure) | ||
Utils.gitTools() | ||
libraryJob.checkout({branch=master}) | ||
libraryJob.stage(Build, groovy.lang.Closure) | ||
libraryJob.sh(./gradlew build) | ||
libraryJob.stage(Post Build, groovy.lang.Closure) | ||
libraryJob.libraryResource(net/courtanet/jenkins/request.json) | ||
libraryJob.sh(curl -H 'Content-Type: application/json' -X POST -d '{"name" : "Ben"}' http://acme.com) | ||
``` | ||
|
||
## Note on CPS | ||
|
||
If you already fiddled with Jenkins pipeline DSL, you experienced strange errors during execution on Jenkins. | ||
|
@@ -239,4 +324,10 @@ To simulate this aspect, CPS versions of the helpers transform your scripts into | |
To use this _*experimental*_ feature, you can use the abstract class `BasePipelineTestCPS` instead of `BasePipelineTest`. | ||
You may see some changes in the call stacks that the helper registers. | ||
Note also that the serialization used to test is not the same as what Jenkins uses. | ||
You may find some incoherence in that level. | ||
You may find some incoherence in that level. | ||
|
||
## Contributing | ||
|
||
JenkinsPipelineUnit aims to help devops code and test Jenkins pipelines with a shorter development cycle. | ||
It addresses some of the requirements traced in [JENKINS-33925](https://issues.jenkins-ci.org/browse/JENKINS-33925). | ||
If you are willing to contribute please don't hesitate to discuss in issues and open a pull-request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/main/groovy/com/lesfurets/jenkins/unit/InterceptingGCL.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.lesfurets.jenkins.unit | ||
|
||
import org.codehaus.groovy.control.CompilationFailedException | ||
import org.codehaus.groovy.control.CompilerConfiguration | ||
|
||
class InterceptingGCL extends GroovyClassLoader { | ||
|
||
PipelineTestHelper helper | ||
|
||
InterceptingGCL(PipelineTestHelper helper, | ||
ClassLoader loader, | ||
CompilerConfiguration config) { | ||
super(loader, config) | ||
this.helper = helper | ||
} | ||
|
||
@Override | ||
Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) | ||
throws CompilationFailedException { | ||
Class clazz = super.parseClass(codeSource, shouldCacheSource) | ||
clazz.metaClass.invokeMethod = helper.getMethodInterceptor() | ||
clazz.metaClass.static.invokeMethod = helper.getMethodInterceptor() | ||
clazz.metaClass.methodMissing = helper.getMethodMissingInterceptor() | ||
return clazz | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 3 additions & 27 deletions
30
src/main/groovy/com/lesfurets/jenkins/unit/MockPipelineScript.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,8 @@ | ||
package com.lesfurets.jenkins.unit | ||
|
||
/** | ||
* This class seems useless now | ||
*/ | ||
abstract class MockPipelineScript extends Script { | ||
|
||
def methodMissing(String name, args) { | ||
if (this._TEST_HELPER.isMethodAllowed(name, args)) { | ||
def result = null | ||
if (args != null) { | ||
for (argument in args) { | ||
result = callIfClosure(argument, result) | ||
if (argument instanceof Map) { | ||
argument.each { k, v -> | ||
result = callIfClosure(k, result) | ||
result = callIfClosure(v, result) | ||
} | ||
} | ||
} | ||
} | ||
return result | ||
} else { | ||
throw new MissingMethodException(name, this.class, args) | ||
} | ||
} | ||
|
||
def callIfClosure(Object closure, Object currentResult) { | ||
if (closure instanceof Closure) { | ||
currentResult = closure.call() | ||
} | ||
return currentResult | ||
} | ||
|
||
} |
Oops, something went wrong.