Skip to content

Commit 81280fd

Browse files
authored
Use new Variant API for generated instrumentation filter file (#336)
* Add unit tests for write-filters task & fix a bug with generating the filters file for instrumentation tests * Replace resource folder generation for instrumentation filters with new Variant API Since the minimum AGP version bump, we can finally safely access the source directories across all supported versions * Update API definition to reflect changes to the filters task * Changelog
1 parent 5fedf9a commit 81280fd

File tree

12 files changed

+186
-83
lines changed

12 files changed

+186
-83
lines changed

plugin/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Change Log
1212
- Decouple discovery of instrumentation tests from Jupiter, allowing non-Jupiter test engines to be discovered as well
1313
- Update lifecycle of instrumentation runner params to only be set once, instead of once per test
1414
- Properly reported disabled dynamic tests to Android instrumentation
15+
- Use new Variant API to register generated resource folder for instrumentation filters file
1516

1617
## 1.10.0.0 (2023-11-05)
1718
- JUnit 5.10.0

plugin/android-junit5/api/android-junit5.api

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,8 @@ public abstract class de/mannodermaus/gradle/plugins/junit5/tasks/AndroidJUnit5J
6161
public abstract class de/mannodermaus/gradle/plugins/junit5/tasks/AndroidJUnit5WriteFilters : org/gradle/api/DefaultTask {
6262
public fun <init> ()V
6363
public final fun execute ()V
64-
public final fun getExcludeTags ()Ljava/util/List;
65-
public final fun getIncludeTags ()Ljava/util/List;
66-
public final fun getOutputFolder ()Ljava/io/File;
67-
public final fun setExcludeTags (Ljava/util/List;)V
68-
public final fun setIncludeTags (Ljava/util/List;)V
69-
public final fun setOutputFolder (Ljava/io/File;)V
64+
public abstract fun getExcludeTags ()Lorg/gradle/api/provider/ListProperty;
65+
public abstract fun getIncludeTags ()Lorg/gradle/api/provider/ListProperty;
66+
public abstract fun getOutputFolder ()Lorg/gradle/api/file/DirectoryProperty;
7067
}
7168

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/config/PluginConfig.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import com.android.build.gradle.DynamicFeaturePlugin
1313
import com.android.build.gradle.LibraryExtension
1414
import com.android.build.gradle.LibraryPlugin
1515
import com.android.build.gradle.api.BaseVariant
16-
import com.android.build.gradle.api.TestVariant
17-
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.instrumentationTestVariant
1816
import de.mannodermaus.gradle.plugins.junit5.internal.providers.DirectoryProvider
1917
import de.mannodermaus.gradle.plugins.junit5.internal.providers.JavaDirectoryProvider
2018
import de.mannodermaus.gradle.plugins.junit5.internal.providers.KotlinDirectoryProvider
@@ -68,11 +66,6 @@ private constructor(
6866
?: emptySet()
6967
}
7068

71-
fun instrumentationTestVariantOf(variant: Variant): TestVariant? {
72-
return legacyVariants.firstOrNull { it.name == variant.name }
73-
?.run { this.instrumentationTestVariant }
74-
}
75-
7669
/* Private */
7770

7871
private fun directoryProvidersOf(legacyVariant: BaseVariant): Set<DirectoryProvider> {

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import de.mannodermaus.gradle.plugins.junit5.internal.config.PluginConfig
1414
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
1515
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getAsList
1616
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getTaskName
17-
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.hasDependency
17+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.instrumentationTestVariant
1818
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junit5Warn
1919
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.namedOrNull
2020
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.usesComposeIn
@@ -23,7 +23,6 @@ import de.mannodermaus.gradle.plugins.junit5.internal.utils.excludedPackagingOpt
2323
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5JacocoReport
2424
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5WriteFilters
2525
import org.gradle.api.Project
26-
import org.gradle.api.artifacts.Dependency
2726
import org.gradle.api.tasks.testing.Test
2827

2928
internal fun configureJUnit5(
@@ -51,7 +50,7 @@ internal fun configureJUnit5(
5150
variants.forEach { variant ->
5251
configureUnitTests(it, variant)
5352
configureJacoco(it, config, variant)
54-
configureInstrumentationTests(it, config, variant)
53+
configureInstrumentationTests(it, variant)
5554
}
5655
}
5756
}
@@ -197,12 +196,11 @@ private fun AndroidJUnitPlatformExtension.configureJacoco(
197196

198197
private fun AndroidJUnitPlatformExtension.configureInstrumentationTests(
199198
project: Project,
200-
config: PluginConfig,
201199
variant: Variant,
202200
) {
203201
if (!instrumentationTests.enabled.get()) return
204202

205-
config.instrumentationTestVariantOf(variant)?.let { instrumentationTestVariant ->
206-
AndroidJUnit5WriteFilters.register(project, variant, instrumentationTestVariant)
203+
variant.instrumentationTestVariant?.sources?.res?.let { sourceDirs ->
204+
AndroidJUnit5WriteFilters.register(project, variant, sourceDirs)
207205
}
208206
}

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/extensions/BaseVariantExt.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package de.mannodermaus.gradle.plugins.junit5.internal.extensions
44

55
import com.android.build.gradle.api.BaseVariant
6-
import com.android.build.gradle.api.TestVariant
76
import com.android.build.gradle.api.UnitTestVariant
87
import com.android.build.gradle.internal.api.TestedVariant
98

@@ -15,12 +14,3 @@ internal val BaseVariant.unitTestVariant: UnitTestVariant
1514

1615
return requireNotNull(this.unitTestVariant)
1716
}
18-
19-
internal val BaseVariant.instrumentationTestVariant: TestVariant?
20-
get() {
21-
if (this !is TestedVariant) {
22-
throw IllegalArgumentException("Argument is not TestedVariant: $this")
23-
}
24-
25-
return this.testVariant
26-
}

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/extensions/VariantExt.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package de.mannodermaus.gradle.plugins.junit5.internal.extensions
22

3+
import com.android.build.api.variant.AndroidTest
4+
import com.android.build.api.variant.HasAndroidTest
35
import com.android.build.api.variant.Variant
46

57
internal fun Variant.getTaskName(prefix: String = "", suffix: String = ""): String {
@@ -18,3 +20,6 @@ internal fun Variant.getTaskName(prefix: String = "", suffix: String = ""): Stri
1820
append(suffix.capitalized())
1921
}.toString()
2022
}
23+
24+
internal val Variant.instrumentationTestVariant: AndroidTest?
25+
get() = (this as? HasAndroidTest)?.androidTest
Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
@file:Suppress("DEPRECATION")
2-
31
package de.mannodermaus.gradle.plugins.junit5.tasks
42

3+
import com.android.build.api.variant.SourceDirectories
54
import com.android.build.api.variant.Variant
6-
import com.android.build.gradle.api.TestVariant
75
import de.mannodermaus.gradle.plugins.junit5.internal.config.INSTRUMENTATION_FILTER_RES_FILE_NAME
86
import de.mannodermaus.gradle.plugins.junit5.internal.config.JUnit5TaskConfig
97
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getTaskName
108
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junitPlatform
119
import org.gradle.api.DefaultTask
1210
import org.gradle.api.Project
11+
import org.gradle.api.file.DirectoryProperty
12+
import org.gradle.api.provider.ListProperty
1313
import org.gradle.api.tasks.CacheableTask
1414
import org.gradle.api.tasks.Input
1515
import org.gradle.api.tasks.OutputDirectory
@@ -25,20 +25,20 @@ import java.io.File
2525
* This only allows tests to be filtered with @Tag annotations even in the instrumentation test realm.
2626
* Other plugin DSL settings, like includeEngines/excludeEngines or includePattern/excludePattern
2727
* are not copied out to file. This has to do with limitations of the backport implementation
28-
* of the JUnit Platform Runner, as well as some incompatibilities between Gradle and Java with regards to
29-
* how class name patterns are formatted.
28+
* of the JUnit Platform Runner, as well as some incompatibilities between Gradle and Java
29+
* regarding how class name patterns are formatted.
3030
*/
3131
@CacheableTask
3232
public abstract class AndroidJUnit5WriteFilters : DefaultTask() {
3333

3434
internal companion object {
35+
@Suppress("UnstableApiUsage")
3536
fun register(
3637
project: Project,
3738
variant: Variant,
38-
instrumentationTestVariant: TestVariant
39+
sourceDirs: SourceDirectories.Layered,
3940
): Boolean {
40-
val outputFolder = File("${project.buildDir}/generated/res/android-junit5/${instrumentationTestVariant.name}")
41-
val configAction = ConfigAction(project, variant, outputFolder)
41+
val configAction = ConfigAction(project, variant)
4242

4343
val provider = project.tasks.register(
4444
configAction.name,
@@ -48,67 +48,66 @@ public abstract class AndroidJUnit5WriteFilters : DefaultTask() {
4848

4949
// Connect the output folder of the task to the instrumentation tests
5050
// so that they are bundled into the built test application
51-
instrumentationTestVariant.registerGeneratedResFolders(
52-
project.files(outputFolder).builtBy(provider)
51+
sourceDirs.addGeneratedSourceDirectory(
52+
taskProvider = provider,
53+
wiredWith = AndroidJUnit5WriteFilters::outputFolder,
5354
)
54-
instrumentationTestVariant.mergeResourcesProvider.configure { it.dependsOn(provider) }
5555

5656
return true
5757
}
5858
}
5959

60-
@Input
61-
public var includeTags: List<String> = emptyList()
60+
@get:Input
61+
public abstract val includeTags: ListProperty<String>
6262

63-
@Input
64-
public var excludeTags: List<String> = emptyList()
63+
@get:Input
64+
public abstract val excludeTags: ListProperty<String>
6565

66-
@OutputDirectory
67-
public var outputFolder: File? = null
66+
@get:OutputDirectory
67+
public abstract val outputFolder: DirectoryProperty
6868

6969
@TaskAction
7070
public fun execute() {
71-
this.outputFolder?.let { folder ->
72-
// Clear out current contents of the generated folder
73-
folder.deleteRecursively()
74-
75-
if (this.hasFilters()) {
76-
folder.mkdirs()
77-
78-
// Re-write the new file structure into it;
79-
// the generated file will have a fixed name & is located
80-
// as a "raw" resource inside the output folder
81-
val rawFolder = File(folder, "raw").apply { mkdirs() }
82-
File(rawFolder, INSTRUMENTATION_FILTER_RES_FILE_NAME)
83-
.bufferedWriter()
84-
.use { writer ->
85-
// This format is a nod towards the real JUnit 5 ConsoleLauncher's arguments
86-
includeTags.forEach { tag -> writer.write("-t $tag") }
87-
excludeTags.forEach { tag -> writer.write("-T $tag") }
88-
}
89-
}
71+
// Clear out current contents of the generated folder
72+
val folder = outputFolder.get().asFile
73+
folder.deleteRecursively()
74+
75+
val includeTags = includeTags.get()
76+
val excludeTags = excludeTags.get()
77+
78+
if (includeTags.isNotEmpty() || excludeTags.isNotEmpty()) {
79+
folder.mkdirs()
80+
81+
// Re-write the new file structure into it;
82+
// the generated file will have a fixed name & is located
83+
// as a "raw" resource inside the output folder
84+
val rawFolder = File(folder, "raw").apply { mkdirs() }
85+
File(rawFolder, INSTRUMENTATION_FILTER_RES_FILE_NAME)
86+
.bufferedWriter()
87+
.use { writer ->
88+
// This format is a nod towards the real JUnit 5 ConsoleLauncher's arguments
89+
includeTags.forEach { tag -> writer.appendLine("-t $tag") }
90+
excludeTags.forEach { tag -> writer.appendLine("-T $tag") }
91+
}
9092
}
9193
}
9294

93-
private fun hasFilters() = includeTags.isNotEmpty() || excludeTags.isNotEmpty()
94-
9595
private class ConfigAction(
9696
private val project: Project,
9797
private val variant: Variant,
98-
private val outputFolder: File
9998
) {
10099

101100
val name: String = variant.getTaskName(prefix = "writeFilters", suffix = "androidTest")
102101

103102
val type = AndroidJUnit5WriteFilters::class.java
104103

105104
fun execute(task: AndroidJUnit5WriteFilters) {
106-
task.outputFolder = outputFolder
107-
108-
// Access filters for this particular variant & provide them to the task, too
105+
// Access filters for this particular variant & provide them to the task
109106
val configuration = JUnit5TaskConfig(variant, project.junitPlatform)
110-
task.includeTags = configuration.combinedIncludeTags.toList()
111-
task.excludeTags = configuration.combinedExcludeTags.toList()
107+
task.includeTags.set(configuration.combinedIncludeTags.toList())
108+
task.excludeTags.set(configuration.combinedExcludeTags.toList())
109+
110+
// Output folder is applied by Android Gradle Plugin, so there is no reason to provide a value ourselves
112111
}
113112
}
114113
}

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/plugin/AgpInstrumentationSupportTests.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ interface AgpInstrumentationSupportTests : AgpVariantAwareTests {
4545
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersDebugAndroidTest")
4646
assertAll(
4747
{ assertThat(task).isNotNull() },
48-
{ assertThat(task.includeTags).containsExactly("global-include-tag") },
49-
{ assertThat(task.excludeTags).containsExactly("debug-exclude-tag") }
48+
{ assertThat(task.includeTags.get()).containsExactly("global-include-tag") },
49+
{ assertThat(task.excludeTags.get()).containsExactly("debug-exclude-tag") }
5050
)
5151
},
5252

@@ -99,15 +99,15 @@ interface AgpInstrumentationSupportTests : AgpVariantAwareTests {
9999
dynamicTest("has a task for writing the freeDebug filters DSL to a resource file") {
100100
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersFreeDebugAndroidTest")
101101
assertThat(task).isNotNull()
102-
assertThat(task.includeTags).containsExactly("global-include-tag", "freeDebug-include-tag")
103-
assertThat(task.excludeTags).containsExactly("global-exclude-tag")
102+
assertThat(task.includeTags.get()).containsExactly("global-include-tag", "freeDebug-include-tag")
103+
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
104104
},
105105

106106
dynamicTest("has a task for writing the paidDebug filters DSL to a resource file") {
107107
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersPaidDebugAndroidTest")
108108
assertThat(task).isNotNull()
109-
assertThat(task.includeTags).containsExactly("global-include-tag")
110-
assertThat(task.excludeTags).containsExactly("global-exclude-tag")
109+
assertThat(task.includeTags.get()).containsExactly("global-include-tag")
110+
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
111111
},
112112

113113
dynamicTest("doesn't have tasks for writing the release filters DSL to a resource file") {

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/plugin/InstrumentationSupportTests.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ class InstrumentationSupportTests {
165165
assertThat(project).configuration("androidTestRuntimeOnly").hasDependency(runnerLibrary())
166166
}
167167

168+
@Test
169+
fun `register the filter-write tasks`() {
170+
project.addJUnitJupiterApi()
171+
project.evaluate()
172+
173+
// AGP only registers androidTest tasks for the debug build type
174+
assertThat(project).task("writeFiltersDebugAndroidTest").exists()
175+
assertThat(project).task("writeFiltersReleaseAndroidTest").doesNotExist()
176+
}
177+
168178
/* Private */
169179

170180
private fun Project.addJUnitJupiterApi() {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package de.mannodermaus.gradle.plugins.junit5.tasks
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import de.mannodermaus.gradle.plugins.junit5.internal.config.INSTRUMENTATION_FILTER_RES_FILE_NAME
5+
import de.mannodermaus.gradle.plugins.junit5.plugin.TestProjectProviderExtension
6+
import de.mannodermaus.gradle.plugins.junit5.util.assertAll
7+
import de.mannodermaus.gradle.plugins.junit5.util.evaluate
8+
import org.gradle.api.Project
9+
import org.junit.jupiter.api.BeforeEach
10+
import org.junit.jupiter.api.Test
11+
import org.junit.jupiter.api.extension.RegisterExtension
12+
import java.io.File
13+
import java.nio.file.Paths
14+
import kotlin.io.path.readLines
15+
16+
class AndroidJUnit5WriteFiltersTests {
17+
@RegisterExtension
18+
@JvmField
19+
val projectExtension = TestProjectProviderExtension()
20+
21+
private lateinit var project: Project
22+
23+
@BeforeEach
24+
fun beforeEach() {
25+
project = projectExtension.newProject()
26+
.asAndroidApplication()
27+
.applyJUnit5Plugin(true) { junitPlatform ->
28+
junitPlatform.filters().includeTags("included")
29+
junitPlatform.filters().excludeTags("excluded", "another-group")
30+
}
31+
.build()
32+
project.evaluate()
33+
}
34+
35+
@Test
36+
fun `generates file structure correctly`() {
37+
// Expect a 'raw' folder inside the output, then the actual filters file in that sub-folder
38+
val output = project.runTaskAndGetOutputFolder()
39+
40+
File(output, "raw").apply {
41+
assertAll(
42+
"output contains 'raw' folder",
43+
{ assertThat(exists()).isTrue() },
44+
{ assertThat(isDirectory).isTrue() },
45+
)
46+
47+
File(this, INSTRUMENTATION_FILTER_RES_FILE_NAME).apply {
48+
assertAll(
49+
"'raw' folder contains filters file'",
50+
{ assertThat(exists()).isTrue() },
51+
{ assertThat(isFile).isTrue() },
52+
)
53+
}
54+
}
55+
}
56+
57+
@Test
58+
fun `file contains expected content`() {
59+
val output = project.runTaskAndGetOutputFolder()
60+
val file = Paths.get(output.absolutePath, "raw", INSTRUMENTATION_FILTER_RES_FILE_NAME)
61+
62+
val content = file.readLines()
63+
assertThat(content).containsExactly(
64+
"-t included",
65+
"-T excluded",
66+
"-T another-group",
67+
)
68+
}
69+
70+
/* Private */
71+
72+
private fun Project.runTaskAndGetOutputFolder(): File {
73+
val task = project.tasks.getByName("writeFiltersDebugAndroidTest") as AndroidJUnit5WriteFilters
74+
task.execute()
75+
return requireNotNull(task.outputFolder.get().asFile)
76+
}
77+
}

0 commit comments

Comments
 (0)