android-junit5
android-junit5 copied to clipboard
Unwanted tasks added as dependencies to JaCoCo task
In our project (using JUnit 4) we define a jacocoTestReport
Gradle task:
task jacocoTestReport(type: JacocoReport, dependsOn: ["testInternalDebugUnitTest"]) {
group = "Reporting"
description = "Generate Jacoco coverage reports"
reports {
xml.enabled = true
html.enabled = true
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
}
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTree], [kotlinClasses])
executionData.from = fileTree(dir: "$buildDir", includes: ["**/**/*.exec", "**/**/*.ec"])
}
And when using JUnit 4 and checking the dependency graph we see it is indeed dependent on testInternalDebugUnitTest
:
:app:jacocoTestReport
\--- :app:testInternalDebugUnitTest ...
BTW, we have two flavors, Internal
and Production
.
When I add the JUnit 5 plugin, however, the task is now dependent on all flavors of jacocoTestReport
, which are dependent on their respective flavor of unit test task:
:app:jacocoTestReport
+--- :app:jacocoTestReportInternalDebug
| \--- :app:testInternalDebugUnitTest ...
+--- :app:jacocoTestReportInternalRelease
| \--- :app:testInternalReleaseUnitTest ...
+--- :app:jacocoTestReportProductionDebug
| \--- :app:testProductionDebugUnitTest ...
+--- :app:jacocoTestReportProductionRelease
| \--- :app:testProductionReleaseUnitTest ...
\--- :app:testInternalDebugUnitTest *
Which results in our unit tests running once for each flavor combination, when we only want to run them for InternalDebug
.
I tried adding the following configuration to my build file but it didn't help.
junitPlatform {
jacoco {
onlyGenerateTasksForVariants("internalDebug")
}
}
Mind the name of the nested object for configuring the integration between JUnit 5 and Jacoco - it's called jacocoOptions
, not jacoco
:
junitPlatform {
jacocoOptions {
onlyGenerateTasksForVariants("internalDebug")
}
}
Alternatively, you can turn off Jacoco integration entirely using the taskGenerationEnabled
property.
junitPlatform {
jacocoOptions {
taskGenerationEnabled = false
}
}
Will any of these work for you?
Neither worked. jacocoTestReport
is still depending on all the flavors.
I'll look into options to further restrict the involvement of the JUnit 5 plugin with existing custom Jacoco tasks. It's supposed to not interfere when taskGenerationEnabled = false
, but there might be a bug in there. In the meantime, there are two workarounds I can see for your setup:
- Rename your custom task to something other than
jacocoTestReport
so that the plugin for JUnit 5 doesn't interfere with it - If you only want to run the unit tests and Jacoco for the
internalDebug
variant, invokejacocoTestReportInternalDebug
in your CI or build pipeline, instead of using the catch-all task
Regardless of the choice, I'll check out the hiccup with the DSL.
Hmm, I can't seem to reproduce the problem with a reference project of my own: Both onlyGenerateTasksForVariants
and taskGenerationEnabled
are working as expected. Note that I'm using the Gradle Kotlin DSL, but the following setup works for me:
// app/build.gradle.kts
plugins {
id("com.android.application")
id("jacoco")
id("de.mannodermaus.android-junit5")
}
// ...
tasks.register<JacocoReport>("jacocoTestReport") {
group = "Reporting"
// ...
}
junitPlatform {
// After syncing, there are no tasks from the JUnit 5 plugin and the custom Jacoco task's dependency chain is clean, too
jacocoOptions.taskGenerationEnabled = false
}
I wonder if it has to do with the way the custom task is defined in your build script. Could you share a little more about where this task is configured? Also, could you try defining it as a lazy TaskProvider
instead of eagerly, as per Gradle's newer recommendation?
// My Groovy is a little rusty, but this should be working
tasks.register("jacocoTestReport", JacocoReport) {
dependsOn("testInternalDebugUnitTest")
group = "Reporting"
description = "Generate Jacoco coverage reports"
reports {
xml.enabled = true
html.enabled = true
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
}
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTree], [kotlinClasses])
executionData.from = fileTree(dir: "$buildDir", includes: ["**/**/*.exec", "**/**/*.ec"])
}
junitPlatform {
// After syncing, there are no tasks from the JUnit 5 plugin and the custom Jacoco task's dependency chain is clean, too
jacocoOptions.taskGenerationEnabled = false
}
Ohh damn, I have been just struggling with the same problem for weeks and found the culprit fortunately! BTW I have a common Gradle
script that I apply in every submodule and I added the aforementioned configuration block in there and it did the trick!
android {
if (project.plugins.hasPlugin(libs.plugins.junit5.get().pluginId)) {
junitPlatform {
jacocoOptions.taskGenerationEnabled = false
}
}
}
- Rename your custom task to something other than
jacocoTestReport
so that the plugin for JUnit 5 doesn't interfere with it
It is not a good idea to rename the task for Android
modules as pure Kotlin/Java
modules are shipped with built-in jacocoTestReport
task when the jacoco
plugin is applied. Therefore, it is better to have the namesake task for Android modules, too.
Is there any side effects of the configuration below?
junitPlatform {
jacocoOptions.taskGenerationEnabled = false
}
I'm glad it turned out well for you in the end! Closing this ticket. 🙏
Is there any side effects of the configuration below?
The only side effect is that the variant-specific Jacoco tasks aren't generated automatically - other than that, nothing else comes to mind. That being said, this integration with Jacoco stems from a very old requirement, before there was native support for JUnit 5 in Gradle. Now that the first-party tasks run it well, it might be time to deprecate the integration slowly...
OP here.
I tried defining the task lazily, but that didn't work.
I tried @nuhkoca 's suggestion:
Ohh damn, I have been just struggling with the same problem for weeks and found the culprit fortunately! BTW I have a common
Gradle
script that I apply in every submodule and I added the aforementioned configuration block in there and it did the trick!android { if (project.plugins.hasPlugin(libs.plugins.junit5.get().pluginId)) { junitPlatform { jacocoOptions.taskGenerationEnabled = false } } }
but I get the following error:
groovy.lang.MissingPropertyException: No such property: junit5 for class: org.gradle.accessors.dm.LibrariesForLibs$PluginAccessors
We define the task in a file called code-coverage.gradle
. Before the task definition we have the following configration:
jacoco {
toolVersion = '0.8.7'
reportsDir = file("${buildDir}/jacocoReports")
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
In the top-level build script we have:
buildscript {
ext.gradlePlugins = [
...
codeCoverage : rootProject.file('gradle/plugins/code-coverage.gradle'),
...
]
}
And then in the module build file we have:
apply from: gradlePlugins.codeCoverage
Gradle version is 7.4.2.
@ebrowne72 Sorry I use the Version Catalogs. If you don't use it replace
libs.plugins.junit5.get().pluginId
with
de.mannodermaus.android-junit5
So
if (project.plugins.hasPlugin("de.mannodermaus.android-junit5")) {
junitPlatform {
jacocoOptions.taskGenerationEnabled = false
}
}
You can create a deferred task like
tasks.register('jacocoTestReport', JacocoReport) {
dependsOn testDebugUnitTest
group = "Reporting"
description = "Generates code coverage report for the test task."
reports {
html.required.set(true)
xml.required.set(true)
}
...
}
Still not working for me.
Sorry to hear that these suggestions aren't working for your setup. As I'm assuming that your project is not publicly available, would it be possible for you to strip it down to just the relevant pieces of the build logic and share it here so that I have a reproducer? No Android source code needed, just the parts of each Gradle file involved in the setup would do.
Command to show task tree:
./gradlew jacocoTestReport taskTree --depth 2
Command output:
:app:jacocoTestReport
+--- :app:jacocoTestReportInternalDebug
| \--- :app:testInternalDebugUnitTest ...
+--- :app:jacocoTestReportInternalRelease
| \--- :app:testInternalReleaseUnitTest ...
+--- :app:jacocoTestReportProductionDebug
| \--- :app:testProductionDebugUnitTest ...
+--- :app:jacocoTestReportProductionRelease
| \--- :app:testProductionReleaseUnitTest ...
\--- :app:testInternalDebugUnitTest *
@ebrowne72 I added this in your app/build.gradle
if (project.plugins.hasPlugin("de.mannodermaus.android-junit5")) {
junitPlatform {
jacocoOptions.taskGenerationEnabled = false
}
}
and output
:app:jacocoTestReport
\--- :app:testInternalDebugUnitTest
+--- :app:bundleInternalDebugClassesToRuntimeJar ...
+--- :app:compileInternalDebugUnitTestJavaWithJavac ...
+--- :app:compileInternalDebugUnitTestKotlin ...
+--- :app:preInternalDebugUnitTestBuild ...
+--- :app:processInternalDebugJavaRes ...
+--- :app:processInternalDebugResources ...
\--- :app:processInternalDebugUnitTestJavaRes ...
seems to be working. But as I mentioned, if you have a multi-module app, you can move this to a centralized place in order to not apply in each Gradle
file individually and manually.
I swear I tried that before and it didn't work, but now it does. Turning off task generation works for my case.
It's fine, that's what happens to all of us 🙂 Glad it worked for you, too! But I am still experiencing longer CI times after JUnit, is there any further improvements?
Glad to hear that it worked out eventually, and thanks @nuhkoca for chiming in, much appreciated! As for questions related to JUnit 5 execution performance on CI, please raise potential questions on their repo directly 🙏