Search code examples
spring-bootgradlejacocogradle-pluginjacoco-plugin

Gradle Multi-Project Build with JaCoCo Code Coverage fails when using Spring Boot


Starting with the sample from https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html (i.e., the code here https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/java/jvm-multi-project-with-code-coverage ) and simply adding Spring Boot by changing application/build.gradle to

plugins {
    id 'myproject.java-conventions'
    id 'application'
}

dependencies {
    implementation project(':list')
    implementation project(':utilities')
    implementation 'org.springframework.boot:spring-boot-starter-web:2.3.8.RELEASE' // <-- this line is new
}

application {
    mainClass = 'org.gradle.sample.Main'
}

and changing application/src/main/java/org/gradle/sample/app/Main.java to

package org.gradle.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.gradle.sample.list.LinkedList;

import static org.gradle.sample.utilities.StringUtils.join;
import static org.gradle.sample.utilities.StringUtils.split;
import static org.gradle.sample.app.MessageUtils.getMessage;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        LinkedList tokens;
        tokens = split(getMessage());
        System.out.println(join(tokens));
        SpringApplication.run(Main.class, args);
    }
}

will break the example (full working, or rather non-working example here: https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage/tree/57e57b8bf24ef4208d9a03a361714c916701e599 ).

When calling

./gradlew clean build codeCoverage -stacktrace

it will fail with

> Task :code-coverage-report:codeCoverageReport FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':code-coverage-report:codeCoverageReport'.
> Error while creating report

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':code-coverage-report:codeCoverageReport'.
        [...]
Caused by: java.io.IOException: Error while analyzing log4j-api-2.13.3.jar@org/apache/logging/log4j/util/ProcessIdUtil.class.
        at org.jacoco.core.analysis.Analyzer.analyzerError(Analyzer.java:162)
        at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:134)
        at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:157)
        at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:193)
        at org.jacoco.core.analysis.Analyzer.analyzeZip(Analyzer.java:265)
        at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:196)
        at org.jacoco.ant.ReportTask.createBundle(ReportTask.java:573)
        at org.jacoco.ant.ReportTask.createReport(ReportTask.java:545)
        at org.jacoco.ant.ReportTask.execute(ReportTask.java:496)
        ... 251 more
Caused by: java.lang.IllegalStateException: Can't add different class with same name: org/apache/logging/log4j/util/ProcessIdUtil
        at org.jacoco.core.analysis.CoverageBuilder.visitCoverage(CoverageBuilder.java:106)
        at org.jacoco.core.analysis.Analyzer$1.visitEnd(Analyzer.java:99)
        at org.objectweb.asm.ClassVisitor.visitEnd(ClassVisitor.java:378)
        at org.jacoco.core.internal.flow.ClassProbesAdapter.visitEnd(ClassProbesAdapter.java:100)
        at org.objectweb.asm.ClassReader.accept(ClassReader.java:722)
        at org.objectweb.asm.ClassReader.accept(ClassReader.java:401)
        at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:116)
        at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:132)
        ... 258 more


* Get more help at https://help.gradle.org

BUILD FAILED in 8s
19 actionable tasks: 19 executed

I have a suspicion that this may be related to https://github.com/jacoco/jacoco/issues/407 . However, my question is whether there is any way to fix this problem. Unfortunately, the official documentation at https://docs.gradle.org/current/userguide/jacoco_plugin.html does not provide any guidance on this topic.

As far as I can tell, the problematic line is https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage/blob/57e57b8bf24ef4208d9a03a361714c916701e599/buildSrc/src/main/groovy/myproject.jacoco-aggregation.gradle#L38

// Task to gather code coverage from multiple subprojects
def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) {
    additionalClassDirs(configurations.runtimeClasspath) // <-- this line here
    additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
    executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })

    reports {
        // xml is usually used to integrate code coverage with
        // other tools like SonarQube, Coveralls or Codecov
        xml.enabled true

        // HTML reports can be used to see code coverage
        // without any external tools
        html.enabled true
    }
}

Removing it will make the build work again. However, then the code coverage report is empty. Is there any way to adjust it, so it will only include code from the project itself, but not external jars, etc.?


Solution

  • Just do that and you will be fine (all external classes will be excluded):

    additionalClassDirs(configurations.runtimeClasspath.get().filter{it.path.contains(rootProject.name)  })
    

    This works with a Kotlin DSL , with Groovy you might have to change the code a little bit maybe this way :

    additionalClassDirs(configurations.runtimeClasspath.filter{it.path.contains(rootProject.name)  })