If you use JaCoCo on a Groovy project, you get a lot of implicitly created closure classes in your coverage report. The source linked to those, are the enclosing classes. The coverage report of the enclosing classes also contains the closure coverage, so it does not add any value to the coverage report. Even worse, it falsifies the coverage report, as the statements to cover in closures are counted twice in the overall statistic.
So how do you exclude those closures from the generated JaCoCo coverage report?
You can exclude the implicit closure classes from JaCoCo recording with the following snippet, this does not prevent the statements to be recorded in the enclosing class
test {
jacoco.excludes = ['**/*$*_closure*']
}
With that snippet the coverage is not recorded anymore, but now all closures are shown with 0% coverage in the report and thus also reduce the overall coverage statistic.
Adding the following snippet will remove the closure classes from the JaCoCo report and thus will also give a more meaningful overall statistic. The above snippet is not really necessary, it just brings a little performance improvement as the coverage does not need to be recorded and transformed for the report, so I suggest using both snippets.
jacocoTestReport {
doFirst {
classDirectories = classDirectories.collect { fileTree(it) { exclude '**/*$*_closure*' } }.sum()
}
}
If you have multiple Test
and JacocoReport
tasks that you want to modify this way, you can of course also do it generically like
tasks.withType(Test) {
jacoco.excludes = ['**/*$*_closure*']
}
tasks.withType(JacocoReport) {
doFirst {
classDirectories = classDirectories.collect { fileTree(it) { exclude '**/*$*_closure*' } }.sum()
}
}
Update:
Here a reworked version of the last example working with Gradle 8.0.2 for Kotlin DSL:
tasks.withType<Test>().configureEach {
the<JacocoTaskExtension>().excludes = listOf("**/*$*_closure*")
}
afterEvaluate {
tasks.withType<JacocoReport>().configureEach {
classDirectories.setFrom(
classDirectories
.asFileTree
.matching { exclude("**/*$*_closure*") }
.toList()
)
}
}
The afterEvaluate
is of course evil, but in this case necessary. Changing the configuration in doFirst
and thus during execution phase was even more evil, but in this case more reliable, but does not work anymore as the property is read-only during the execution phase.