0

I have into the same problem quite a lot of people have here, which is getting proper code coverage information when using Jacoco/Gradle and Powermock.

I have read all the various threads here and in other places and I have successfully managed to create a task (for Gradle 6.4) that does offline instrumentation of my project's classes. For reference the code that does this is the following:

task instrumentClasses(dependsOn: [ classes, project.configurations.jacocoAnt ]) {
    inputs.files classes.outputs.files
    File outputDir = new File(project.buildDir, 'instrumented')
    outputs.dir outputDir
    doFirst {
        project.delete(outputDir)
        ant.taskdef(
                resource: 'org/jacoco/ant/antlib.xml',
                classpath: project.configurations.jacocoAnt.asPath,
                uri: 'jacoco'
        )
        def instrumented = false
        jacocoOfflineSourceSets.each { sourceSetName ->
            if (file(sourceSets[sourceSetName as String].output.classesDirs.singleFile.absolutePath).exists()) {
                def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
                ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
                    fileset(dir: sourceSets[sourceSetName as String].output.classesDirs.singleFile, includes: '**/*.class')
                }
                //Replace the classes dir in the test classpath with the instrumented one
                sourceSets.test.runtimeClasspath -= sourceSets[sourceSetName as String].output.classesDirs
                sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
                instrumented = true
            }
        }
        if (instrumented) {
            //Disable class verification based on https://github.com/jayway/powermock/issues/375
            test.jvmArgs += '-noverify'
        }
    }
}

Now, for the most part this seems to work alright. I have successfully verified that my classes are now properly instrumented and I'm seeing a Jacoco produced report which has the correct information. Problem is though that my SonarQube server still lists the classes in question as non covered. Regarding this I have no idea as to what I need to do to resolve it.

For reference I am using the following version of the sonarqube plugin:

"org.sonarqube" version "2.7"

And my CI runs the Gradle task in the following manner:

 - ./gradlew jacocoTestReport sonarqube ${SONAR_GRADLE_EXTRA_PARAMS} -Dsonar.projectKey=${CI_PROJECT_ID} -Dsonar.host.url=${SONAR_URL} -Dsonar.login=${SONAR_LOGIN} -Dsonar.branch.name=${CI_COMMIT_REF_NAME};

I do get that it must be some configuration issue with either SonarQube or the way I run the Gradle task but I am not really sure as to what is the culprit.

akortex
  • 5,067
  • 2
  • 25
  • 57

1 Answers1

1

If you are able to generate the aggregated jacoco report (aggregated from all source sets), then you can simply specify that in your sonarqube task while running (and sonar will just pick the exact coverage info that jacoco calculated)

./gradlew sonarqube -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=XXXX -Dsonar.organization=XXXXX -Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco-report.xml

FYI I am creating the aggregated report at build/jacoco-report.xml

Below is my gradle configuration (might be useful for you)

plugins {
  id 'org.springframework.boot' version '2.3.1.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
  id 'jacoco'
  id "org.sonarqube" version "2.8"
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11

repositories {
  mavenCentral()
}

sourceSets {
  intTest {
    compileClasspath += sourceSets.main.output + sourceSets.test.output
    runtimeClasspath += sourceSets.main.output + sourceSets.test.output
  }
}

configurations {
  intTestImplementation.extendsFrom testImplementation
  intTestRuntimeOnly.extendsFrom testRuntimeOnly
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'

  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
  useJUnitPlatform()
  testLogging.showStandardStreams = true //To print logs
}


task integrationTest(type: Test) {
  testClassesDirs = sourceSets.intTest.output.classesDirs
  classpath = sourceSets.intTest.runtimeClasspath
  shouldRunAfter test
  testLogging.showStandardStreams = true //To print logs
}

jacocoTestReport {
  executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
  reports {
    xml.enabled true
    csv.enabled false
    xml.destination file("${buildDir}/jacoco-report.xml")
    html.destination file("${buildDir}/jacocoHtml")
  }
  mustRunAfter(test, integrationTest) // integration tests are required to run before generating the report
}

jacocoTestCoverageVerification {
  executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
  violationRules {
    rule {
      limit {
        counter = 'INSTRUCTION'
        minimum = 0.94
      }
      limit {
        counter = 'BRANCH'
        minimum = 1.0
      }
    }
  }
}

check.dependsOn(integrationTest, jacocoTestCoverageVerification)

tasks.withType(Test) {
  finalizedBy jacocoTestReport
}
Abhinaba Chakraborty
  • 3,488
  • 2
  • 16
  • 37
  • 1
    Thanks for the reply but I have had the problem resolved. Turns out I wasn't instructing the Sonar plugin to upload the aggregated XML produced report to our server. Correcting this instantly resolved my issue. Both my instrumentation and report creation where OK and it all boiled down to some missing Sonar configuration. – akortex Jun 28 '20 at 13:08