Measuring cross-module coverage with gradle sonarqube plugin

764 views
Skip to first unread message

tfn...@gmail.com

unread,
Oct 21, 2016, 9:11:14 AM10/21/16
to SonarQube
Hi all,

Here is my environment:

* Gradle 3.0 in a larger multi-module project
* gradle org.sonarqube plugin version 2.2
* Configured extra integrationTest sourceSet
* Running on a Mac with Java 8
* SonarQube version 5.6.3
* Java plugin version 4.2.1.6971
* Generic coverage plugin version 1.2

We are unable to measure code coverage across module, meaning that if module A depends on module B (a
compile project dependency, in Gradle terms), we want to see coverage within classes in B when the tests in
A exercise code there.

First question is: Is this possible out of the box? Should('nt) it be?

As we couldn't get it working, we stepped back a bit and tried with the Jacoco Gradle plugin itself.

We are able to get cross-module coverage by configuring the jacocoTestReport to take additional sourceSets like this:

List<Project> getAllDependentProjects(Project project) {
  def projectDependencies = project.configurations.compile.getAllDependencies().withType(ProjectDependency)
  List<Project> dependentProjects = projectDependencies*.dependencyProject
  if (dependentProjects.size > 0) {
    dependentProjects.each { dependentProjects += getAllDependentProjects(it) }
  }
  return dependentProjects.unique()
}

jacocoTestReport {
    getAllDependentProjects(project).each { Project depProject ->
        sourceSets depProject.sourceSets.main
    }
}


Running the jacocoTestReport in module A now gives cross-module coverage (only for unit tests, but that's another problem). 

So after getting things half-way working with the jacocoTestReport, I tried getting the same configuration back into SonarQube.

Here is the top level sonar config:

sonarqube {
    properties {
        property
"sonar.projectName", "Our project"
       
property "sonar.jacoco.itReportPath", 'build/jacoco/integrationTest.exec'
       
property "sonar.exclusions", "**/node_modules/**/*.js,**/bower_components/**/*.js,**/bootstrap/**/*.js,**/coverage/**/*.js"
       
property "sonar.jacoco.reportMissing.force.zero", "true"
   
}
}


By adding the project dependencies' java sources to the sonar.sources, similar to how we did it for jacoco, we get very close to the goal:

def getSrcDirs(Project project) {
    def srcDirs = project.sourceSets.main.java.srcDirs // + project(':workflow:workflow-core').sourceSets.main.java
    getAllDependentProjects(project).each { Project depProject ->
        SourceDirectorySet javaSourceSet = depProject.sourceSets.main.java
        srcDirs += javaSourceSet.srcDirs
    }
    return srcDirs;
}

sonarqube{
    properties{
        property "sonar.sources", getSrcDirs(project)
    }
}

but right at the finish line, SonarQube decides to exclude all results that are not part of the module currently being built:
File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/B'.
(the above is repeated for each java file in the B sources).

I've tried setting the sonar.projectBaseDir to the top level project directory, but it doesn't make any difference. Sonar seems pretty much hardcoded to only count coverage within the module A.

Is there any by-the-book way of getting this working? Is it supposed to work out of the box? I've been googling around quite a bit, and I haven't seen many people running into this exact problem (coverage in a gradle multi-module project).

Thanks in advance for all pointers!

tfn...@gmail.com

unread,
Oct 21, 2016, 9:13:51 AM10/21/16
to SonarQube, tfn...@gmail.com


On Friday, October 21, 2016 at 3:11:14 PM UTC+2, tfn...@gmail.com wrote:
File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/B'.


Correction, the above warning should be like this (replaced B with A):

File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/A'.
 

Nicolas Peru

unread,
Oct 21, 2016, 11:32:36 AM10/21/16
to tfn...@gmail.com, SonarQube
Hi, 

This is indeed how the import of JaCoCo code coverage reports is working in the java analyzer. 
The correct way to go around that limitation is to merge the JaCoCo reports between your modules so the coverage information you are looking for will be in the report imported by that module. 

Cheers,

--
You received this message because you are subscribed to the Google Groups "SonarQube" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sonarqube+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/sonarqube/be249995-615b-4f1e-b008-4e5dcb233435%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
Nicolas PERU | SonarSource
Senior Developer
http://sonarsource.com

Thomas Ferris Nicolaisen

unread,
Oct 22, 2016, 4:53:53 PM10/22/16
to Nicolas Peru, SonarQube
Hi Nicolas, thanks for your reply.

Conceptually, would that mean that I merge the destination-file of module A (A/build/jacoco/test.exec) into the destination file of module B (B/build/jacoco/test.exec) before sonarqube starts analyzing module B? 

This sounds tricky since A depends on B having been built first (A depends on B). I could run two builds: one for running tests generating the initial exec files, and a second one for merging them and running sonarqube. But I have a feeling things could get complicated quickly as the number of modules increases...

Do you have any pointers on how to do this properly in a Gradle build?


On Fri, Oct 21, 2016 at 5:32 PM, Nicolas Peru <nicola...@sonarsource.com> wrote:
Hi, 

This is indeed how the import of JaCoCo code coverage reports is working in the java analyzer. 
The correct way to go around that limitation is to merge the JaCoCo reports between your modules so the coverage information you are looking for will be in the report imported by that module. 

Cheers,

Le ven. 21 oct. 2016 à 15:13, <tfn...@gmail.com> a écrit :


On Friday, October 21, 2016 at 3:11:14 PM UTC+2, tfn...@gmail.com wrote:
File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/B'.


Correction, the above warning should be like this (replaced B with A):

File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/A'.
 

--
You received this message because you are subscribed to the Google Groups "SonarQube" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sonarqube+unsubscribe@googlegroups.com.

Nicolas Peru

unread,
Nov 22, 2016, 3:04:02 AM11/22/16
to Thomas Ferris Nicolaisen, SonarQube
Hi, 
Sorry for the delay of answer.
This is indeed what you should do  : generate the reports, then merge them then import them into sonarqube through the java analyzer. 

As for gradle build pointer, sorry can't really help you there I am no gradle expert (at all ;) )

Cheers, 

Le sam. 22 oct. 2016 à 22:53, Thomas Ferris Nicolaisen <tfn...@gmail.com> a écrit :
Hi Nicolas, thanks for your reply.

Conceptually, would that mean that I merge the destination-file of module A (A/build/jacoco/test.exec) into the destination file of module B (B/build/jacoco/test.exec) before sonarqube starts analyzing module B? 

This sounds tricky since A depends on B having been built first (A depends on B). I could run two builds: one for running tests generating the initial exec files, and a second one for merging them and running sonarqube. But I have a feeling things could get complicated quickly as the number of modules increases...

Do you have any pointers on how to do this properly in a Gradle build?

On Fri, Oct 21, 2016 at 5:32 PM, Nicolas Peru <nicola...@sonarsource.com> wrote:
Hi, 

This is indeed how the import of JaCoCo code coverage reports is working in the java analyzer. 
The correct way to go around that limitation is to merge the JaCoCo reports between your modules so the coverage information you are looking for will be in the report imported by that module. 

Cheers,

Le ven. 21 oct. 2016 à 15:13, <tfn...@gmail.com> a écrit :


On Friday, October 21, 2016 at 3:11:14 PM UTC+2, tfn...@gmail.com wrote:
File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/B'.


Correction, the above warning should be like this (replaced B with A):

File '/Users/tfnico/projects/our-project/B/src/main/java/com/foo/dao/filter/EntitySpecification.java' is ignored. It is not located in module basedir '/Users/tfnico/projects/our-project/A'.
 

--
You received this message because you are subscribed to the Google Groups "SonarQube" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sonarqube+...@googlegroups.com.
--
Nicolas PERU | SonarSource
Senior Developer
http://sonarsource.com
Reply all
Reply to author
Forward
0 new messages