Cross-module code coverage with jacoco and gradle multi-module project

Michael Zöller picture Michael Zöller · Jan 2, 2017 · Viewed 11.9k times · Source

We use gradle 3.3 and jacoco tool verson 0.7.6.201602180812. We have a gradle multi-project like this:

  • parent
    • prod1
    • prod2
    • prod3
    • int-test

We use unit-tests testing the project sources and jacoco on all child-projects producing test.exec files. We have additional integration-tests in the int-test project adding jacoco results to the test-exec in the int-test project. We use sonarqube gradle plugin (2.2.1) on the parent project to collect everything for a SonarQube server v6.2.

Everything runs fine with tests that test sources in their own project: The code coverage is measured in the jacoco reports as well as on SonarQube. Only the integration test (int-test project) coverage for the sources in the prod-projects (single process) is not measured neither in the coverage report in the project with the test nor in the project with the class. Probably one needs to combine the coverage data on the top level project somehow - does anyone know how to do that? At best with SonarQube still showing the coverage on single module level as well.

EDIT Here is a small test project: https://github.com/MichaelZett/coveragetest Running
'build smokeTest sonarqube' leads to:

  • Run of all tests
  • producing jacoco/test.exec and test-results/test/... files in all child projects
  • parsing of these in sonarqube
  • correct measurement of coverage for tests that test sources in their own projects
  • missing coverage for tests that test sources in another project

Answer

Cristian picture Cristian · Mar 8, 2018

As noted in the comments, you must first merge the Jacoco execution data and then tell sonarqube to use that instead of the individual exec files generated by each submodule.

I'm adding an example here since the links provided in the accepted answer are a little bit misleading. Most of them provide you with different workarounds to merge Jacoco reports, not to merge the execution data, which is what you want.

Here's how it would look like:

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
        }
    }
}

In a nutshell:

  • 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 our allTestCoverageFile.

If you are using a version of SonarQube prior to 6.2 please use sonar.jacoco.reportPath property