11

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
Michael Zöller
  • 125
  • 1
  • 1
  • 8

3 Answers3

12

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

Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 1
    Hi Cristian, what's the difference between the two properties sonar.jacoco.reportPaths and sonar.jacoco.reportPath (without trailing 's')? You write it's important to set this property globally and for each subproject... but those are two different properties! What's going on...?! – dokaspar Mar 29 '18 at 15:13
  • https://docs.sonarqube.org/display/PLUG/Code+Coverage+by+Unit+Tests+for+Java+Project _if you are using a version of SonarQube prior to 6.2 please use sonar.jacoco.reportPath property_ – Cristian Mar 30 '18 at 07:19
  • I see what you mean. I have a typo. I will fix it. – Cristian Mar 30 '18 at 07:23
  • @Cristian Can i have your email? – Pie Dec 08 '18 at 05:27
  • So sonarqube.com is currently down, but I clearly remember reading that the latest Sonarqube plugin "org.sonarqube:2.7" doesn't support binary .exec files. I also recall reading the sonarqube plugin must be run from the root project only (so the emphasized instruction of running it from the subprojects closure appears outdated). This means the above solution is no longer applicable. The org.sonarqube:2.7 plugin does seem to support a comma-separated list of XML reports for the sonar.coverage.jacoco.xmlReportPaths property, however, so I'm looking at some Groovy script to produce this. – Frans Feb 04 '20 at 12:59
  • https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ says: "To analyze a project hierarchy, apply the SonarQube plugin to the root project of the hierarchy." – Frans Feb 04 '20 at 13:43
  • Do you have a complete example? I guess there is the part of jacocoTestReport or codeCoverageReport missing, isn't it? In addition I am asking myself how to get this running via gitlab-ci. – Bobbelinio Aug 19 '20 at 14:13
  • `sonar.jacoco.reportPaths` no longer supported for me, had to use `sonar.coverage.jacoco.xmlReportPaths` (and provide aggregate xml file location) note you can also use the `allprojects` closure to refer to root project + subprojects: ``allprojects { sonar { properties { property "sonar.coverage.jacoco.xmlReportPaths", "$rootProject.buildDir/reports/jacoco/jacocoFullReport.xml" } } }`` – hmac Feb 23 '23 at 13:34
5

Speaking about SonarQube: you can get aggregated report by using same location for jacoco.exec across all modules. Make sure that file is removed before build and appended in all modules.

Speaking solely about Gradle: have a look on

Godin
  • 9,801
  • 2
  • 39
  • 76
  • 1
    Thank you for the input, I'll try the jacocoMerge task. For SonarQube: it's collecting the individual data just fine but not the cross-project data. – Michael Zöller Jan 04 '17 at 16:11
  • 1
    @MichaelZöller about SonarQube once again: collect data into single file "jacoco.exec" e.g. in "../jacoco.exec" that relative to each module or any other file that is the same across modules, instruct SonarQube to use this location during analysis by setting "sonar.jacoco.reportPath" into "../jacoco.exec". If this doesn't help, then please make an effort to describe your issue more precisely and provide example (see http://stackoverflow.com/help/mcve). – Godin Jan 04 '17 at 16:56
  • I finally took the time to use the single jacoco.exec approach. This is working. Additionally one has to copy junit-test results to one location, too zo get the correct test count in SonarQube. I have adapted the project on github with the needed tasks. The new SQ 6.2 property for a collection of jacoco paths seems not to work with the sonar-gradle-plugin version 2.2.1. – Michael Zöller Mar 08 '17 at 15:12
1
subprojects {
    apply(plugin: 'org.jetbrains.kotlin.jvm')

    repositories {
        jcenter()
        mavenCentral()
   }
}

task codeCoverageReport(type: JacocoReport) {

    // Gather execution data from all subprojects
    executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")

    // Add all relevant sourcesets from the subprojects
    subprojects.each {
        sourceSets it.sourceSets.main
    }

    reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
    }
}

// always run the tests before generating the report
codeCoverageReport.dependsOn {
    subprojects*.test
}

sonarqube {
    properties {
        property "sonar.projectKey", "your_project_key"
        property "sonar.verbose", true
        property "sonar.projectName", "Your project name"
        property "sonar.coverage.jacoco.xmlReportPaths", "${rootDir}/build/reports/jacoco/codeCoverageReport/codeCoverageReport.xml"
    }
}

Command to run test with coverage:

./gradlew codeCoverageReport
./gradlew sonarqube -x test (test is excluded since already run and sonarqube by default executes test)

Two things to be noted that made it work:

  1. To make available sourcesets of all modules, looping over subprojects and accumulating sourcesets worked. subprojects.sourceSets.main.allSource.srcDirs did not work.
  2. sonar.jacoco.reportPaths is deprecated. We need to use sonar.coverage.jacoco.xmlReportPaths. Check the documentation here

  • I got "Could not get unknown property 'test' for project ':app' of type org.gradle.api.Project." I extracted this logic to a jacoco.gradle file but that shouldn't matter for me. – Bobbelinio Aug 19 '20 at 14:05