Parallel Builds with Jenkins and gradle
JUnit 5↗ and TestNG↗ have good support for parallelizing tests. And usually you should stick to that and not do anything else. But sometimes you have some reasons why you cannot use that. In my case, the legacy software had tests that did some system wide stubbing which was a show stopper for parallel tests.
When the tests took too much time, we thought that we should run them in parallel. Because of the bad tests, we had to start different java processes.
First of all, you should use JUnit Tags or TestNG groups to group your tests into different suites that can run independently. Then you structure your build, that you first compile the classes and then run the test suites in parallel:
build.gradle:
test {
useJUnitPlatform {
includeTags 'suite1'
excludeTags 'suite2'
}
}
task testSuite2(type: Test) {
useJUnitPlatform {
includeTags 'suite2'
excludeTags 'suite1'
}
dependsOn compileTestJava, processTestResources
}
check.dependsOn testSuite2
Then you setup parallel execution in your Jenkinsfile↗:
pipeline {
stages {
stage("compile") {
steps {
sh "./gradlew build -x test -x testSuite2"
}
}
stage ("test"){
parallel {
stage("testSuite1") {
steps {
sh "./gradlew test"
}
}
stage("testSuite1") {
steps {
sh "./gradlew testSuite2"
}
}
}
}
}
}
If you run this, you will find that the steps “testSuite1” and “testSuite2” will compile the classes, too.
How to copy the compiled classes from step compile to the test steps?
I can combine gradle’s build cache↗ and Jenkins’ stash feature↗. But attention: I can not unstash the same stash twice, so I have to copy directories into different stashes:
settings.gradle:
buildCache {
local(DirectoryBuildCache) {
directory = new File(rootDir, 'build-cache')
removeUnusedEntriesAfterDays = 1
}
}
Jenkinsfile:
pipeline {
stages {
stage("compile") {
steps {
// clean
sh "if [ -d build-cache ]; then rm -r build-cache; fi"
// build
sh "./gradlew --build-cache clean build -x test -x testSuite1"
// stash 1
sh "mkdir build/stash1"
sh "cp -r build-cache build/stash1"
stash includes: 'build/stash1/**', name: 'stash1'
// stash 2
sh "mkdir build/stash2"
sh "cp -r build-cache build/stash2"
stash includes: 'build/stash2/**', name: 'stash2'
}
}
stage ("test"){
parallel {
stage("testSuite1") {
steps {
// clean
sh "if [ -d build-cache ]; then rm -r build-cache; fi"
sh "./gradlew clean"
// unstash
unstash 'stash1'
sh "mv build/stash1/build-cache ."
sh "rm -Rf build/stash1"
// test
sh "./gradlew --build-cache test"
}
}
stage("testSuite2") {
steps {
// clean
sh "if [ -d build-cache ]; then rm -r build-cache; fi"
sh "./gradlew clean"
// unstash
unstash 'stash2'
sh "mv build/stash2/build-cache ."
sh "rm -Rf build/stash2"
// test
sh "./gradlew --build-cache testSuite2"
}
}
}
}
}
}
If you start parallelizing your build you might soonly see memory problems on your build server. Think about configuring memory settings in gradle↗.
Any comments or suggestions? Leave an issue or a pull request!