9
votes

I am building a SonarQube 6.2 server which is already analyzing my Java 8/Gradle 3.3 projects. When adding JaCoCo to a multimodule gradle project, I realized that SonarQube is measuring code coverage on a "per-module" basis:

If a class is located in module A and a test for this class is located in module B, SonarQube figures the class is not covered.

I want to measure code coverage across all modules, not on a per module basis. How do I achieve this?

There are lots of similar questions but no helpful answers, although the situation seems quite common to me. Jenkins for example does that per default.

I decided to build a blueprint on github to clarify the issue.

The main build.gradle consists of

plugins { id "org.sonarqube" version "2.2.1" }

subprojects {
    apply plugin: 'java'
    apply plugin: 'jacoco'

    repositories { mavenCentral() }
    dependencies { testCompile "junit:junit:4.12" }
}

modA/build.gradleis empty.

It contains 3 classes: TestedInModA, TestedInModATest and TestedViaModB.

modB/build.gradlejust declares a dependency to modA:

dependencies { compile project(':modA') }

It contains just one class: TestedViaModBTest, testing the class TestedViaModB located in modA.

My (private) Jenkins instance shows 100% coverage for the two classes included while SonarQube says only the class TestedInModA (which is tested in its own module) is covered.

How can I modify my build process to see "cross-module coverage" in SonarQube?

I would love to update my project so future visitors to this question can find a working example.

My working solution (thanks @Godin)

  1. add the following to the subprojects closure

    tasks.withType(Test) {
        // redirect all coverage data to one file
        // ... needs cleaning the data prior to the build to avoid accumulating coverage data of different runs.
        // see `task cleanJacoco`
        jacoco {
            destinationFile = file("$rootProject.buildDir/jacoco/test.exec")
        }
    }
    
  2. add

    task cleanJacoco(dependsOn: 'clean') {  delete "$buildDir/jacoco" }
    

outside the subprojects closure.

2
unfortunately the blueprint link is dead. I am still searching for a working solution.Bobbelinio
sorry @Bobbelinio, it seems I've removed the blueprint from my github account and I did not find another copy. However, the aforementioned solution does not work any more (according to a comment I've found it stopped working when updating to Gradle 5.2.1). Since we've stopped using sonarqube, we did not bother fixing the problem.sk_dev

2 Answers

7
votes

When you perform build JaCoCo Gradle Plugin will produce modA/build/jacoco/test.exec and modB/build/jacoco/test.exe that contain information about execution of tests in modA and modB respectively. SonarQube performs analysis of modules separately, so during analysis of modA for the file TestedViaModB it sees only modA/build/jacoco/test.exec.

Most common trick to cross boundaries - is to collect all coverage information into single location. This can be done with JaCoCo Gralde Plugin

  • either by changing location - see destinationFile and destPath ( since information is appended to exec file, don't forget to remove this single location prior to build, otherwise it will be accumulating information from different builds and not just from different modules ),

  • either by merging all files into single one - see JacocoMerge task. And then specify this single location to SonarQube as sonar.jacoco.reportPath.

Another trick: SonarQube 6.2 with Java Plugin 4.4 supports property sonar.jacoco.reportPaths allowing to specify multiple locations.

0
votes

If you are interested in the solution with sonar.jacoco.reportPaths (see answer of Godin), have looke at this gradle code:

tasks.getByName('sonarqube') {
    doFirst {
        // lazy initialize the property to collect all coverage files
        def jacocoFilesFromSubprojects = subprojects.findAll {it.plugins.hasPlugin('jacoco')}
                .collect {it.tasks.withType(Test)}.flatten().collect {it.jacoco.destinationFile}

        sonarqube.properties {
            property "sonar.jacoco.reportPaths", jacocoFilesFromSubprojects.join(',')
        }
    }
}

This will collect all coverage binary files and set them as comma-separated list to the sonar property. Considers all test tasks of sub projects where jacoco is applied and their jacoco destination file configured.