JaCoCo Gradle Kotlin DSL exclude file from violation rules

Viewed 1435

I am trying to set my project up to run JaCoCo and to fail the build if it has less that 80% test coverage. The caveat is that I want to exclude a particular file and I can't seem to make that work. I have scoured the web and read oodles of Stack Overflow answers, blog posts, and the documentation for the plugin, but I cannot make anything work. It always still includes the file that I am trying to exclude.

I started with:

tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}

which obviously will include everything and it does. I have tried the following variations based on what I have read, but none of them have worked:

tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            classDirectories.setFrom(sourceSets.main.get().output.asFileTree.matching {
                exclude("path.to.my.class.HelloWorld")
            })
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}
tasks.jacocoTestCoverageVerification {
    classDirectories.setFrom(
            sourceSets.main.get().output.asFileTree.matching {
                exclude("path.to.my.class.HelloWorld")
            }
    )
    violationRules {
        rule {
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}
tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}
tasks {
    getByName<JacocoReport>("jacocoTestReport") {
        afterEvaluate {
            classDirectories.setFrom(files(classDirectories.files.map {
                fileTree(it) {
                    exclude("path.to.my.class.HelloWorld")
                }
            }))
        }
    }
}
tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}
tasks.withType<JacocoReport> {
    classDirectories.setFrom(
        sourceSets.main.get().output.asFileTree.matching {
            exclude("path.to.my.class.HelloWorld")
        }
    )
}

The output of all of these is exactly the same:

Execution failed for task ':jacocoTestCoverageVerification'.
> Rule violated for bundle OneUIBackend: instructions covered ratio is 0.6, but expected minimum is 0.8

Does anyone have any insight in to how to do this properly?

3 Answers

An addition to the accepted solution (that also worked for me; for kotlin project using kotlin gradle dsl; gradle 7.2; jacoco plugin 0.8.7)

  • In case you are trying to figure out how to exclude classes from coverage report using afterEvaluate {...} the following worked for me:

    tasks.withType<JacocoReport> {
        afterEvaluate {
            classDirectories.setFrom(classDirectories.files.map {
                fileTree(it).matching {
                    exclude("com/**")
                }
            })
        }
    }
    

    Putting the afterEvaluate block inside tasks.jacocoTestReport {...} result in an error:

    Project#afterEvaluate(Action) on root project 'demo' cannot be executed in the current context

  • You can reuse classDirectories from jacocoTestReport task in jacocoTestCoverageVerification:

    tasks.jacocoTestReport {
        dependsOn(tasks.test)
        finalizedBy(tasks.jacocoTestCoverageVerification)
    
        classDirectories.setFrom(classDirectories.files.map {
            fileTree(it).matching {
                exclude("com/**")
            }
        })
    }
    
    tasks.jacocoTestCoverageVerification {
        violationRules {
            rule {
                classDirectories.setFrom(tasks.jacocoTestReport.get().classDirectories)
                limit {
                    minimum = "0.78".toBigDecimal()
                }
            }
        }
    }
    

So it turns out that I was declaring the excluded file as a classpath rather than a filepath. The final solution is:

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    finalizedBy(tasks.jacocoTestCoverageVerification)
    classDirectories.setFrom(
            sourceSets.main.get().output.asFileTree.matching {
                exclude("path/to/my/file/HelloWorld*")
            }
    )
}
tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            classDirectories.setFrom(sourceSets.main.get().output.asFileTree.matching {
                exclude("path/to/my/file/HelloWorld*")
            })
            limit {
                minimum = "0.8".toBigDecimal()
            }
        }
    }
}

It is possible to exclude a bit clearer:

tasks.jacocoTestReport {
    classDirectories.setFrom(
        files(classDirectories.files.map {
            fileTree(it) {
                exclude(
                    "com/example/integration/**",
                    "com/example/application/*Ext*"
                )
            }
        })
    )
}

Related