diff --git a/README.md b/README.md index 1f07ccd..af7b3dd 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ now promoted to dedicated Gradle plugins: [io.github.gmazzo.test.aggregation.coverage](https://plugins.gradle.org/plugin/io.github.gmazzo.test.aggregation.coverage) and [io.github.gmazzo.test.aggregation.results](https://plugins.gradle.org/plugin/io.github.gmazzo.test.aggregation.results) + +## `UnitTest` and `AndroidTest` code coverage support +By default, the plugin will do `buildTypes["debug]".enableUnitTestCoverage = true` when applied, allowing coverage to be collected. + +Coverage on `AndroidTest` variant is also support and will be automatically collected, if `buildType["debug"].enableAndroidTestCoverage = true` is also set (it applies for any `BuildType`) + ## Filtering content The plugins will automatically aggregate `android` modules and `java` modules that also apply `jacoco` plugin on the `jacocoAggregation` and the `testReportAggregation` configurations. diff --git a/demo-project/app/build.gradle.kts b/demo-project/app/build.gradle.kts index 8329d41..05b41a6 100644 --- a/demo-project/app/build.gradle.kts +++ b/demo-project/app/build.gradle.kts @@ -21,7 +21,8 @@ android { buildTypes { debug { - enableUnitTestCoverage = true + // FIXME UI tests requires an emulator on CI + enableAndroidTestCoverage = true } release { aggregateTestCoverage = false @@ -39,7 +40,7 @@ android { } create("prod") { dimension = "environment" - //aggregateTestCoverage.set(false) + aggregateTestCoverage.set(false) } } diff --git a/demo-project/app/src/main/java/com/example/myapplication/SomeSingleton.kt b/demo-project/app/src/main/java/com/example/myapplication/SomeSingleton.kt new file mode 100644 index 0000000..8c4adc7 --- /dev/null +++ b/demo-project/app/src/main/java/com/example/myapplication/SomeSingleton.kt @@ -0,0 +1,9 @@ +package com.example.myapplication + +object SomeSingleton { + + fun doStuff() { + println("SomeSingleton") + } + +} diff --git a/demo-project/ui-tests/src/main/kotlin/com/example/myapplication/ExampleInstrumentedTest.kt b/demo-project/ui-tests/src/main/kotlin/com/example/myapplication/ExampleInstrumentedTest.kt index 0d17fa1..62d910e 100644 --- a/demo-project/ui-tests/src/main/kotlin/com/example/myapplication/ExampleInstrumentedTest.kt +++ b/demo-project/ui-tests/src/main/kotlin/com/example/myapplication/ExampleInstrumentedTest.kt @@ -18,5 +18,8 @@ class ExampleInstrumentedTest { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.example.app.test", appContext.packageName) + + SomeSingleton.doStuff() // to mark coverage of UI tests } } + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96364fa..e6f6a52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "1.9.20" -agp = "8.1.2" +agp = "8.3.0-alpha12" android-minSDK = "21" android-compileSDK = "34" androidx-lifecycle = "2.6.2" diff --git a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestCoverageAggregationPlugin.kt b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestCoverageAggregationPlugin.kt index 3d2dcab..aae6152 100644 --- a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestCoverageAggregationPlugin.kt +++ b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestCoverageAggregationPlugin.kt @@ -3,6 +3,7 @@ package io.github.gmazzo.android.test.aggregation import com.android.build.api.artifact.ScopedArtifact +import com.android.build.api.variant.HasAndroidTest import com.android.build.api.variant.HasUnitTest import com.android.build.api.variant.ScopedArtifacts import com.android.build.api.variant.Variant @@ -18,10 +19,13 @@ import org.gradle.api.file.Directory import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.file.RegularFile import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Sync +import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.kotlin.dsl.USAGE_TEST_AGGREGATION import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.domainObjectSet import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getAt import org.gradle.kotlin.dsl.getValue @@ -43,16 +47,21 @@ abstract class AndroidTestCoverageAggregationPlugin : Plugin { // enables jacoco test coverage on `debug` build type by default android.buildTypes["debug"].enableUnitTestCoverage = true - val jacocoVariants = objects.namedDomainObjectSet(Variant::class) + val codeCoverageExecData = objects.domainObjectSet(Provider::class) + val aggregatedVariants = objects.namedDomainObjectSet(Variant::class) - androidComponents.onVariants { variant -> + androidComponents.onVariants(androidComponents.selector().all()) { variant -> val buildType = android.buildTypes[variant.buildType!!] - if ((variant as? HasUnitTest)?.unitTest != null && - buildType.enableUnitTestCoverage && - android.shouldAggregate(variant) - ) { - jacocoVariants.add(variant) + if (android.shouldAggregate(variant)) { + if (buildType.enableUnitTestCoverage && (variant as? HasUnitTest)?.unitTest != null) { + aggregatedVariants.add(variant) + codeCoverageExecData.add(unitTestTaskOf(variant).execData) + } + if (buildType.enableAndroidTestCoverage && (variant as? HasAndroidTest)?.androidTest != null) { + aggregatedVariants.add(variant) + codeCoverageExecData.add(androidTestTaskOf(variant).execData) + } } } @@ -73,11 +82,8 @@ abstract class AndroidTestCoverageAggregationPlugin : Plugin { ) } afterEvaluate { - jacocoVariants.all variant@{ - val execData = unitTestTaskOf(this@variant)!! - .map { it.the().destinationFile!! } - - outgoing.artifact(execData) { + codeCoverageExecData.all data@{ + outgoing.artifact(this@data) { type = ArtifactTypeDefinition.BINARY_DATA_TYPE } } @@ -87,7 +93,7 @@ abstract class AndroidTestCoverageAggregationPlugin : Plugin { val allVariantsSourcesForCoverageReport by tasks.registering(Sync::class) { destinationDir = temporaryDir duplicatesStrategy = DuplicatesStrategy.INCLUDE // in case of duplicated classes - jacocoVariants.all { + aggregatedVariants.all { from(sources.java?.all, sources.kotlin?.all) } } @@ -132,7 +138,7 @@ abstract class AndroidTestCoverageAggregationPlugin : Plugin { ) } - jacocoVariants.all task@{ + aggregatedVariants.all task@{ artifacts .forScope(ScopedArtifacts.Scope.PROJECT) .use(allVariantsClassesForCoverageReport) @@ -180,4 +186,7 @@ abstract class AndroidTestCoverageAggregationPlugin : Plugin { } } + private val Provider.execData + get() = map { it.the().destinationFile!! } + } diff --git a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestResultsAggregationPlugin.kt b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestResultsAggregationPlugin.kt index 60401bd..9e569f9 100644 --- a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestResultsAggregationPlugin.kt +++ b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/AndroidTestResultsAggregationPlugin.kt @@ -1,5 +1,6 @@ package io.github.gmazzo.android.test.aggregation +import com.android.build.api.variant.HasAndroidTest import com.android.build.api.variant.HasUnitTest import org.gradle.api.Plugin import org.gradle.api.Project @@ -7,6 +8,8 @@ import org.gradle.api.attributes.Category import org.gradle.api.attributes.TestSuiteType import org.gradle.api.attributes.Usage import org.gradle.api.attributes.VerificationType +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.kotlin.dsl.USAGE_TEST_AGGREGATION import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.named @@ -34,13 +37,16 @@ abstract class AndroidTestResultsAggregationPlugin : Plugin { } } + fun aggregate(task: Provider) = afterEvaluate { + testResultsElements.outgoing.artifact(task.flatMap { it.binaryResultsDirectory }) + } + androidComponents.onVariants { variant -> if ((variant as? HasUnitTest)?.unitTest != null && android.shouldAggregate(variant)) { - afterEvaluate { - val testTask = unitTestTaskOf(variant)!! - - testResultsElements.outgoing.artifact(testTask.flatMap { it.binaryResultsDirectory }) - } + //aggregate(unitTestTaskOf(variant)) + } + if ((variant as? HasAndroidTest)?.androidTest != null && android.shouldAggregate(variant)) { + //aggregate(androidTestTaskOf(variant)) } } } diff --git a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/InternalDSL.kt b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/InternalDSL.kt index bd30d71..445e6c9 100644 --- a/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/InternalDSL.kt +++ b/plugin/src/main/kotlin/io/github/gmazzo/android/test/aggregation/InternalDSL.kt @@ -1,6 +1,7 @@ package io.github.gmazzo.android.test.aggregation import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.HasAndroidTest import com.android.build.api.variant.HasUnitTest import com.android.build.api.variant.Variant import com.android.build.gradle.BaseExtension @@ -13,7 +14,6 @@ import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getByName -import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.testAggregation import org.gradle.kotlin.dsl.the @@ -60,6 +60,10 @@ internal fun TestAggregationExtension.aggregateProject(project: Project, config: private fun TestAggregationExtension.Modules.includes(project: Project) = (includes.get().isEmpty() || project in includes.get()) && project !in excludes.get() -internal fun Project.unitTestTaskOf(variant: Variant) = (variant as? HasUnitTest) - ?.unitTest - ?.let { tasks.named("test${it.name.capitalized()}") } +internal fun Project.unitTestTaskOf(variant: Type) where Type : Variant, Type : HasUnitTest = provider { + tasks.getByName("test${(variant as HasUnitTest).unitTest!!.name.capitalized()}") +} + +internal fun Project.androidTestTaskOf(variant: Type) where Type : Variant, Type : HasAndroidTest = provider { + tasks.getByName("test${variant.androidTest!!.name.capitalized()}") +}