SonarQube code coverage does not match Jacoco coverage for Java Gradle multi-module project

  • which versions are you using (SonarQube, Scanner, Plugin, and any relevant extension)
    • SonarQube Enterprise Edition Version 9.9 (build 65466)
    • Gradle Plugin version 3.4.0.2513
    • jacoco toolVersion = “0.8.10”
    • Gradle 7.4.2
  • how is SonarQube deployed: Docker
  • what are you trying to achieve
    • SonarQube coverage should match Jacoco coverage

I have Gradle Java multi-module project.
Problem: SonarQube says that MyAwesomeClassModule1.callFromDifferentModule is not covered by tests but it is covered by test in other module.

Module1:

package org.example;

public class MyAwesomeClassModule1 {
   public int callFromTheSameModule(int a, int b) {
       return a + b;
   }

   public int callFromDifferentModule(int a, int b) {
       return a - b;
   }
}

test in Module1:

package org.example;

import org.junit.Assert;
import org.junit.Test;

public class MyAwesomeClassModule1Test {

    @Test
    public void callFromTheSameModule() {
        Assert.assertEquals(3, new MyAwesomeClassModule1().callFromTheSameModule(1, 2));
    }
}

test in Module2 that covers class in Module1:

package org.example;

import org.junit.Assert;
import org.junit.Test;

public class MyAwesomeClassModule2Test {
    @Test
    public void callFromDifferentModule() {
        Assert.assertEquals(-1, new MyAwesomeClassModule1().callFromDifferentModule(1, 2));
    }
}

Root build.gradle:

plugins {
    id 'jacoco'
    id 'java'
    id 'org.sonarqube' version '3.4.0.2513'
}

apply plugin: 'org.sonarqube'

sonarqube {
    properties {
        property "sonar.projectKey", "multi"
        property "sonar.host.url", "http://localhost:9000"
        property "sonar.login", "sqp_432705d91e78439505e76b60745102d5a3aadfc7"
        property "sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
        property "sonar.verbose", "true"
    }
}

group = 'org.example'
version = '1.0-SNAPSHOT'

allprojects {
    repositories {
        mavenCentral()
    }

    apply plugin: 'jacoco'

    jacoco {
        toolVersion = "0.8.10"
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'java-library'
    sourceCompatibility = 11

    jacocoTestReport {
        reports {
            xml.required = true
            html.required = true
        }
        dependsOn test
        mustRunAfter test
    }

    test {
        dependsOn cleanTest
        testLogging.showStandardStreams = true

        workingDir(projectDir.getParent())

        testLogging {
            exceptionFormat "full"
        }
        beforeTest { descriptor ->
            logger.lifecycle("Running test: " + descriptor)
        }

        useJUnitPlatform {
            includeEngines("junit-jupiter", "junit-vintage")
        }
        finalizedBy jacocoTestReport
    }

    apply plugin: 'idea'
    idea {
        module {
            sourceDirs += file("src/main/generated/main/java")
            generatedSourceDirs += file("src/main/generated/main/java")
            downloadSources = true
            downloadJavadoc = false
            inheritOutputDirs = false
            outputDir = file('.out/classes/main')
            testOutputDir = file('.out/classes/test')
            testSourceDirs += file('src/test/java')
            testSourceDirs += file('src/test/cassandra')
        }
    }

    dependencies {
        testImplementation 'junit:junit:4.13.2'
        testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.3"
        testImplementation "org.junit.jupiter:junit-jupiter-engine:5.9.3"
        testImplementation "org.junit.jupiter:junit-jupiter-params:5.9.3"
        testRuntimeOnly('org.junit.vintage:junit-vintage-engine:5.9.3')
    }
}

task jacocoRootReport(type: JacocoReport) {
    description = 'Generates an aggregate report from all subprojects'

    dependsOn(subprojects.jacocoTestReport)

    additionalSourceDirs.from = files(subprojects.sourceSets.main.allSource.srcDirs).filter { f -> f.exists() }
    sourceDirectories.from = files(subprojects.sourceSets.main.allSource.srcDirs).filter { f -> f.exists() }
    classDirectories.from = files(subprojects.sourceSets.main.output).filter { f -> f.exists() }

    getExecutionData().setFrom(fileTree(projectDir).include("**/jacoco/*.exec"))
    reports {
        xml.required = true
        html.required = true
    }
}

check.finalizedBy tasks.jacocoRootReport

tasks.jacocoRootReport.finalizedBy("sonarqube")
tasks.sonarqube.mustRunAfter("jacocoRootReport")

Jacoco shows that both methods are covered build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!DOCTYPE report PUBLIC "-//JACOCO//DTD Report 1.1//EN"
        "report.dtd">
<report name="multi">
    <sessioninfo id="MacBook-Pro-10.local-16e8272c" start="1687892217117" dump="1687892217680"/>
    <sessioninfo id="MacBook-Pro-10.local-11d44b8e" start="1687892218237" dump="1687892218799"/>
    <package name="org/example">
        <class name="org/example/MyAwesomeClassModule1" sourcefilename="MyAwesomeClassModule1.java">
            <method name="&lt;init&gt;" desc="()V" line="3">
                <counter type="INSTRUCTION" missed="0" covered="3"/>
                <counter type="LINE" missed="0" covered="1"/>
                <counter type="COMPLEXITY" missed="0" covered="1"/>
                <counter type="METHOD" missed="0" covered="1"/>
            </method>
            <method name="callFromTheSameModule" desc="(II)I" line="5">
                <counter type="INSTRUCTION" missed="0" covered="4"/>
                <counter type="LINE" missed="0" covered="1"/>
                <counter type="COMPLEXITY" missed="0" covered="1"/>
                <counter type="METHOD" missed="0" covered="1"/>
            </method>
            <method name="callFromDifferentModule" desc="(II)I" line="9">
                <counter type="INSTRUCTION" missed="0" covered="4"/>
                <counter type="LINE" missed="0" covered="1"/>
                <counter type="COMPLEXITY" missed="0" covered="1"/>
                <counter type="METHOD" missed="0" covered="1"/>
            </method>
            <counter type="INSTRUCTION" missed="0" covered="11"/>
            <counter type="LINE" missed="0" covered="3"/>
            <counter type="COMPLEXITY" missed="0" covered="3"/>
            <counter type="METHOD" missed="0" covered="3"/>
            <counter type="CLASS" missed="0" covered="1"/>
        </class>
        <sourcefile name="MyAwesomeClassModule1.java">
            <line nr="3" mi="0" ci="3" mb="0" cb="0"/>
            <line nr="5" mi="0" ci="4" mb="0" cb="0"/>
            <line nr="9" mi="0" ci="4" mb="0" cb="0"/>
            <counter type="INSTRUCTION" missed="0" covered="11"/>
            <counter type="LINE" missed="0" covered="3"/>
            <counter type="COMPLEXITY" missed="0" covered="3"/>
            <counter type="METHOD" missed="0" covered="3"/>
            <counter type="CLASS" missed="0" covered="1"/>
        </sourcefile>
        <counter type="INSTRUCTION" missed="0" covered="11"/>
        <counter type="LINE" missed="0" covered="3"/>
        <counter type="COMPLEXITY" missed="0" covered="3"/>
        <counter type="METHOD" missed="0" covered="3"/>
        <counter type="CLASS" missed="0" covered="1"/>
    </package>
    <counter type="INSTRUCTION" missed="0" covered="11"/>
    <counter type="LINE" missed="0" covered="3"/>
    <counter type="COMPLEXITY" missed="0" covered="3"/>
    <counter type="METHOD" missed="0" covered="3"/>
    <counter type="CLASS" missed="0" covered="1"/>
</report>

However SonarQube shows that callFromDifferentModule method is not covered.

Could you please suggest what I can be missing in configuration that leads to this behavior?


build/sonar/scanner-report/analysis.log:

Plugins:
Bundled analyzers:
  - Python Code Quality and Security 3.24.0.10784 (python)
  - Go Code Quality and Security 1.11.0.3905 (go)
  - JaCoCo 1.3.0.1538 (jacoco)
  - Kotlin Code Quality and Security 2.12.0.1956 (kotlin)
  - IaC Code Quality and Security 1.11.0.2847 (iac)
  - JavaScript/TypeScript/CSS Code Quality and Security 9.13.0.20537 (javascript)
  - Ruby Code Quality and Security 1.11.0.3905 (ruby)
  - Scala Code Quality and Security 1.11.0.3905 (sonarscala)
  - C# Code Quality and Security 8.51.0.59060 (csharp)
  - Java Code Quality and Security 7.16.0.30901 (java)
  - HTML Code Quality and Security 3.7.1.3306 (web)
  - Flex Code Quality and Security 2.8.0.3166 (flex)
  - XML Code Quality and Security 2.7.0.3820 (xml)
  - PHP Code Quality and Security 3.27.1.9352 (php)
  - Text Code Quality and Security 2.0.2.1090 (text)
  - VB.NET Code Quality and Security 8.51.0.59060 (vbnet)
  - Configuration detection fot Code Quality and Security 1.2.0.267 (config)
Global server settings:
  - sonar.core.id=147B411E-AYj7Iy2JeWCOTM7vJ1Wa
  - sonar.core.startTime=2023-06-27T18:48:02+0000
  - sonar.forceAuthentication=true
Project server settings:
Project scanner properties:
  - sonar.coverage.jacoco.xmlReportPaths=/Users/username/Repos/multi/build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml
  - sonar.host.url=http://localhost:9000
  - sonar.java.jdkHome=/Library/Java/JavaVirtualMachines/applejdk-11.0.15.10.2.jdk/Contents/Home
  - sonar.java.source=11
  - sonar.java.target=11
  - sonar.login=******
  - sonar.modules=:module1,:module2
  - sonar.projectBaseDir=/Users/username/Repos/multi
  - sonar.projectKey=multi
  - sonar.projectName=multi
  - sonar.projectVersion=1.0-SNAPSHOT
  - sonar.scanner.app=ScannerGradle
  - sonar.scanner.appVersion=3.4.0.2513/Gradle 7.4.2
  - sonar.sourceEncoding=UTF-8
  - sonar.sources=
  - sonar.verbose=true
  - sonar.working.directory=/Users/username/Repos/multi/build/sonar
Scanner properties of module: multi:module2
  - sonar.coverage.jacoco.xmlReportPaths=/Users/username/Repos/multi/module2/build/reports/jacoco/test/jacocoTestReport.xml
  - sonar.jacoco.reportPath=/Users/username/Repos/multi/module2/build/jacoco/test.exec
  - sonar.jacoco.reportPaths=/Users/username/Repos/multi/module2/build/jacoco/test.exec
  - sonar.java.libraries=/Users/username/Repos/multi/module1/build/classes/java/main
  - sonar.java.test.binaries=/Users/username/Repos/multi/module2/build/classes/java/test
  - sonar.java.test.libraries=/Users/username/Repos/multi/module1/build/classes/java/main,/Users/username/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12/junit-4.13.2.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-params/5.9.3/9e2a4bf6016a1975f408a73523392875cff7c26f/junit-jupiter-params-5.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.9.3/8616734a190f8d307376aeb7353dba0a2c037a09/junit-platform-engine-1.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.9.3/36b2e26a90c41603be7f0094bee80e3f8a2cd4d4/junit-platform-commons-1.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.9.3/355322b03bf39306a183162cd06626c206f0286b/junit-jupiter-engine-5.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.9.3/8...
  - sonar.junit.reportPaths=/Users/username/Repos/multi/module2/build/test-results/test
  - sonar.junit.reportsPath=/Users/username/Repos/multi/module2/build/test-results/test
  - sonar.libraries=/Users/username/Repos/multi/module1/build/classes/java/main
  - sonar.moduleKey=multi:module2
  - sonar.projectBaseDir=/Users/username/Repos/multi/module2
  - sonar.projectKey=multi:module2
  - sonar.projectName=module2
  - sonar.projectVersion=unspecified
  - sonar.sources=/Users/username/Repos/multi/module2/src/main/java
  - sonar.surefire.reportsPath=/Users/username/Repos/multi/module2/build/test-results/test
  - sonar.tests=/Users/username/Repos/multi/module2/src/test/java
Scanner properties of module: multi:module1
  - sonar.binaries=/Users/username/Repos/multi/module1/build/classes/java/main
  - sonar.coverage.jacoco.xmlReportPaths=/Users/username/Repos/multi/module1/build/reports/jacoco/test/jacocoTestReport.xml
  - sonar.jacoco.reportPath=/Users/username/Repos/multi/module1/build/jacoco/test.exec
  - sonar.jacoco.reportPaths=/Users/username/Repos/multi/module1/build/jacoco/test.exec
  - sonar.java.binaries=/Users/username/Repos/multi/module1/build/classes/java/main
  - sonar.java.test.binaries=/Users/username/Repos/multi/module1/build/classes/java/test
  - sonar.java.test.libraries=/Users/username/Repos/multi/module1/build/classes/java/main,/Users/username/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12/junit-4.13.2.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-params/5.9.3/9e2a4bf6016a1975f408a73523392875cff7c26f/junit-jupiter-params-5.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.9.3/8616734a190f8d307376aeb7353dba0a2c037a09/junit-platform-engine-1.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.9.3/36b2e26a90c41603be7f0094bee80e3f8a2cd4d4/junit-platform-commons-1.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.9.3/355322b03bf39306a183162cd06626c206f0286b/junit-jupiter-engine-5.9.3.jar,/Users/username/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.9.3/8...
  - sonar.junit.reportPaths=/Users/username/Repos/multi/module1/build/test-results/test
  - sonar.junit.reportsPath=/Users/username/Repos/multi/module1/build/test-results/test
  - sonar.moduleKey=multi:module1
  - sonar.projectBaseDir=/Users/username/Repos/multi/module1
  - sonar.projectKey=multi:module1
  - sonar.projectName=module1
  - sonar.projectVersion=unspecified
  - sonar.sources=/Users/username/Repos/multi/module1/src/main/java
  - sonar.surefire.reportsPath=/Users/username/Repos/multi/module1/build/test-results/test
  - sonar.tests=/Users/username/Repos/multi/module1/src/test/java
1 Like

moving sonarqube task from root to allprojects block helped to solve the issue.

allprojects { // <-- this is new
    sonarqube {
        properties {
            property "sonar.projectKey", "multi"
            property "sonar.host.url", "http://localhost:9000"
            property "sonar.login", "sqp_432705d91e78439505e76b60745102d5a3aadfc7"
            property "sonar.coverage.jacoco.xmlReportPaths", "$rootDir/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" // <--`$rootDir` is new
            property "sonar.verbose", "true"
        }
    }
}
3 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.