I’m assuming you got here because you are using Gradle with Jacoco and noticed that integrating it with Sonarqube does not work perfectly out of the box. Specifically, when your project has multiple modules, you might have seen that Sonarqube’s coverage report ignores code in module A covered by tests in module B.
In fact, this is a problem that you will find even if you are not using Sonarqube: Jacoco itself will not merge test reports by default, which makes it extra hard to find a solution online. Google will provide you with a plethora of hacks to workaround this, all of them giving you false hopes that this will work in Sonarqube too.
I’d like to say I fixed this myself, but no, I was simply not able to. Things got so bad that I stopped Googling a solution and started using Github’s search to find snippets of code where other devs, hopefully, had already figured it out.
That’s how I found this sample project: https://github.com/BolderTechnologies/SonarQube-TotalCoverage where someone named Brian made my day. Here’s how it worked (for me, anyway):
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
}
}
apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'org.sonarqube'
allprojects {
repositories {
jcenter()
}
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.0"
}
}
… the snippet above is pretty standard. We are just integrating Jacoco and Sonarqube into our Gradle build.
Here’s where stuff gets interesting:
def allTestCoverageFile = "$buildDir/jacoco/allTestCoverage.exec"
sonarqube {
properties {
property "sonar.projectKey", "your.org:YourProject"
property "sonar.projectName", "YourProject"
property "sonar.jacoco.reportPaths", allTestCoverageFile
}
}
task jacocoMergeTest(type: JacocoMerge) {
destinationFile = file(allTestCoverageFile)
executionData = project.fileTree(dir: '.', include:'**/build/jacoco/test.exec')
}
task jacocoMerge(dependsOn: ['jacocoMergeTest']) {
// used to run the other merge tasks
}
subprojects {
sonarqube {
properties {
property "sonar.jacoco.reportPaths", allTestCoverageFile
}
}
}
- First, we define a global coverage file output for our test
reports (
allTestCoverageFile
). - Then we need to tell Sonarqube to use that file (using
sonar.jacoco.reportPaths
). But notice we also have to do it in the subprojects closure. This is extremely important. Don’t miss it. - Finally, we create a custom task that extends from
JacocoMerge
(an incubating class from the Jacoco plugin), that merges all the test coverage reports from all projects (executionData
) into ourallTestCoverageFile
.
This is how I regained my sanity. I hope you find this useful if you are starting to lose yours.