From 00d3964b87d35fac8d40a5476429468e17f295df Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Wed, 24 Sep 2025 22:42:46 +0200 Subject: [PATCH 1/3] Support multiple android variants --- affectedmoduledetector/build.gradle | 1 + .../AffectedModuleDetectorPlugin.kt | 62 ++++++++++++++++--- .../AffectedTestConfiguration.kt | 3 + 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/affectedmoduledetector/build.gradle b/affectedmoduledetector/build.gradle index c3657d1..562de84 100644 --- a/affectedmoduledetector/build.gradle +++ b/affectedmoduledetector/build.gradle @@ -32,6 +32,7 @@ gradlePlugin { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation("com.android.tools.build:gradle:8.13.0") testImplementation("junit:junit:4.13.2") testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0") testImplementation("com.google.truth:truth:1.4.5") diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt index 9f4edc1..e55927a 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt @@ -4,6 +4,7 @@ package com.dropbox.affectedmoduledetector +import com.android.build.gradle.TestedAndroidConfig import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -167,7 +168,7 @@ class AffectedModuleDetectorPlugin : Plugin { } project.pluginManager.withPlugin(pluginId) { - getAffectedPath(testType, project)?.let { path -> + getAffectedPaths(testType, project).takeIf { it.isNotEmpty() }?.forEach { path -> val pathOrNull = project.tasks.findByPath(path) val onlyIf = when { pathOrNull == null -> false @@ -183,10 +184,10 @@ class AffectedModuleDetectorPlugin : Plugin { } } - private fun getAffectedPath( + private fun getAffectedPaths( taskType: AffectedModuleTaskType, project: Project - ): String? { + ): List { val tasks = requireNotNull( value = project.extensions.findByName(AffectedTestConfiguration.name), lazyMessage = { "Unable to find ${AffectedTestConfiguration.name} in $project" } @@ -194,15 +195,27 @@ class AffectedModuleDetectorPlugin : Plugin { return when (taskType) { InternalTaskType.ANDROID_TEST -> { - getPathAndTask(project, tasks.runAndroidTestTask) + if (tasks.runTestsForEveryVariant) { + getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ANDROID_TEST) + } else { + getPathAndTask(project, tasks.runAndroidTestTask) + } } InternalTaskType.ASSEMBLE_ANDROID_TEST -> { - getPathAndTask(project, tasks.assembleAndroidTestTask) + if (tasks.runTestsForEveryVariant) { + getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ASSEMBLE_ANDROID_TEST) + } else { + getPathAndTask(project, tasks.assembleAndroidTestTask) + } } InternalTaskType.ANDROID_JVM_TEST -> { - getPathAndTask(project, tasks.jvmTestTask) + if (tasks.runTestsForEveryVariant) { + getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ANDROID_JVM_TEST) + } else { + getPathAndTask(project, tasks.jvmTestTask) + } } InternalTaskType.JVM_TEST -> { @@ -219,8 +232,41 @@ class AffectedModuleDetectorPlugin : Plugin { } } - private fun getPathAndTask(project: Project, task: String?): String? { - return if (task.isNullOrBlank()) null else "${project.path}:$task" + private fun getAndroidPathsAndTasks(project: Project, buildType: String, taskType: InternalTaskType): List { + val androidTestExtension = requireNotNull( + value = project.extensions.findByType(TestedAndroidConfig::class.java), + lazyMessage = { "Unable to find ${TestedAndroidConfig::class.simpleName} in $project" } + ) + + val taskSuffix = when (taskType) { + InternalTaskType.ANDROID_JVM_TEST -> "UnitTest" + InternalTaskType.ANDROID_TEST -> "AndroidTest" + InternalTaskType.ASSEMBLE_ANDROID_TEST -> "AndroidTest" + else -> throw IllegalArgumentException("Unknown task type: $taskType") + } + + val taskPrefix = when (taskType) { + InternalTaskType.ANDROID_JVM_TEST -> "test" + InternalTaskType.ANDROID_TEST -> "test" + InternalTaskType.ASSEMBLE_ANDROID_TEST -> "assemble" + else -> throw IllegalArgumentException("Unknown task type: $taskType") + } + + val variantSet = when (taskType) { + InternalTaskType.ANDROID_JVM_TEST -> androidTestExtension.unitTestVariants + InternalTaskType.ANDROID_TEST -> androidTestExtension.unitTestVariants + InternalTaskType.ASSEMBLE_ANDROID_TEST -> androidTestExtension.testVariants + else -> throw IllegalArgumentException("Unknown task type: $taskType") + } + + return variantSet.matching { it.buildType.name == buildType } + .map { variant -> + "${taskPrefix}${variant.name.replaceFirstChar { it.uppercase() }}${taskSuffix}" + } + } + + private fun getPathAndTask(project: Project, task: String?): List { + return if (task.isNullOrBlank()) emptyList() else listOf("${project.path}:$task") } private fun filterAndroidTests(project: Project) { diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt index 2746e16..1822989 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt @@ -12,6 +12,9 @@ open class AffectedTestConfiguration { var runAndroidTestTask: String? = DEFAULT_ANDROID_TEST_TASK var jvmTestTask: String? = DEFAULT_JVM_TEST_TASK + var runTestsForEveryVariant: Boolean = false + var defaultTestBuildType: String = "debug" + companion object { const val name = "affectedTestConfiguration" From 7ce2df9698d41630d699d0021b3a8e3d2b429e9f Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Thu, 25 Sep 2025 00:07:22 +0200 Subject: [PATCH 2/3] Update variants provider --- affectedmoduledetector/build.gradle | 1 - .../AffectedModuleDetectorPlugin.kt | 54 ++----------------- .../AffectedTestConfiguration.kt | 12 ++++- 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/affectedmoduledetector/build.gradle b/affectedmoduledetector/build.gradle index 562de84..c3657d1 100644 --- a/affectedmoduledetector/build.gradle +++ b/affectedmoduledetector/build.gradle @@ -32,7 +32,6 @@ gradlePlugin { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation("com.android.tools.build:gradle:8.13.0") testImplementation("junit:junit:4.13.2") testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0") testImplementation("com.google.truth:truth:1.4.5") diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt index e55927a..543321a 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt @@ -4,7 +4,6 @@ package com.dropbox.affectedmoduledetector -import com.android.build.gradle.TestedAndroidConfig import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -193,29 +192,19 @@ class AffectedModuleDetectorPlugin : Plugin { lazyMessage = { "Unable to find ${AffectedTestConfiguration.name} in $project" } ) as AffectedTestConfiguration + val androidTaskNames = tasks.testTasksProvider?.orNull + return when (taskType) { InternalTaskType.ANDROID_TEST -> { - if (tasks.runTestsForEveryVariant) { - getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ANDROID_TEST) - } else { - getPathAndTask(project, tasks.runAndroidTestTask) - } + androidTaskNames?.androidTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.runAndroidTestTask) } InternalTaskType.ASSEMBLE_ANDROID_TEST -> { - if (tasks.runTestsForEveryVariant) { - getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ASSEMBLE_ANDROID_TEST) - } else { - getPathAndTask(project, tasks.assembleAndroidTestTask) - } + androidTaskNames?.assembleAndroidTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.assembleAndroidTestTask) } InternalTaskType.ANDROID_JVM_TEST -> { - if (tasks.runTestsForEveryVariant) { - getAndroidPathsAndTasks(project, tasks.defaultTestBuildType, InternalTaskType.ANDROID_JVM_TEST) - } else { - getPathAndTask(project, tasks.jvmTestTask) - } + androidTaskNames?.unitTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.jvmTestTask) } InternalTaskType.JVM_TEST -> { @@ -232,39 +221,6 @@ class AffectedModuleDetectorPlugin : Plugin { } } - private fun getAndroidPathsAndTasks(project: Project, buildType: String, taskType: InternalTaskType): List { - val androidTestExtension = requireNotNull( - value = project.extensions.findByType(TestedAndroidConfig::class.java), - lazyMessage = { "Unable to find ${TestedAndroidConfig::class.simpleName} in $project" } - ) - - val taskSuffix = when (taskType) { - InternalTaskType.ANDROID_JVM_TEST -> "UnitTest" - InternalTaskType.ANDROID_TEST -> "AndroidTest" - InternalTaskType.ASSEMBLE_ANDROID_TEST -> "AndroidTest" - else -> throw IllegalArgumentException("Unknown task type: $taskType") - } - - val taskPrefix = when (taskType) { - InternalTaskType.ANDROID_JVM_TEST -> "test" - InternalTaskType.ANDROID_TEST -> "test" - InternalTaskType.ASSEMBLE_ANDROID_TEST -> "assemble" - else -> throw IllegalArgumentException("Unknown task type: $taskType") - } - - val variantSet = when (taskType) { - InternalTaskType.ANDROID_JVM_TEST -> androidTestExtension.unitTestVariants - InternalTaskType.ANDROID_TEST -> androidTestExtension.unitTestVariants - InternalTaskType.ASSEMBLE_ANDROID_TEST -> androidTestExtension.testVariants - else -> throw IllegalArgumentException("Unknown task type: $taskType") - } - - return variantSet.matching { it.buildType.name == buildType } - .map { variant -> - "${taskPrefix}${variant.name.replaceFirstChar { it.uppercase() }}${taskSuffix}" - } - } - private fun getPathAndTask(project: Project, task: String?): List { return if (task.isNullOrBlank()) emptyList() else listOf("${project.path}:$task") } diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt index 1822989..e328ca7 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt @@ -1,5 +1,8 @@ package com.dropbox.affectedmoduledetector +import org.gradle.api.provider.Provider +import java.io.Serializable + /** * Used to configure which variant to run for affected tasks by adding following block to modules * affectedTestConfiguration{ @@ -12,8 +15,13 @@ open class AffectedTestConfiguration { var runAndroidTestTask: String? = DEFAULT_ANDROID_TEST_TASK var jvmTestTask: String? = DEFAULT_JVM_TEST_TASK - var runTestsForEveryVariant: Boolean = false - var defaultTestBuildType: String = "debug" + var testTasksProvider: Provider? = null + + data class TasksNames( + val unitTestTasks: List, + val androidTestTasks: List, + val assembleAndroidTestTasks: List, + ): Serializable companion object { const val name = "affectedTestConfiguration" From 28b45cacf957dab6cd7b982c282ae5e2ecb774cf Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Thu, 25 Sep 2025 10:28:29 +0200 Subject: [PATCH 3/3] Refactor code --- .../AffectedModuleDetectorPlugin.kt | 22 +++++++-------- .../AffectedTestConfiguration.kt | 17 ++++++----- .../AffectedTestConfigurationTest.kt | 28 +++++-------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt index 543321a..299abf7 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt @@ -192,27 +192,25 @@ class AffectedModuleDetectorPlugin : Plugin { lazyMessage = { "Unable to find ${AffectedTestConfiguration.name} in $project" } ) as AffectedTestConfiguration - val androidTaskNames = tasks.testTasksProvider?.orNull + val androidTaskNames = tasks.getTestTaskNames() return when (taskType) { InternalTaskType.ANDROID_TEST -> { - androidTaskNames?.androidTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.runAndroidTestTask) + androidTaskNames.androidTestTasks.takeIf { + it.isNotEmpty() + } ?: getPathAndTask(project, AffectedTestConfiguration.DEFAULT_ANDROID_TEST_TASK) } InternalTaskType.ASSEMBLE_ANDROID_TEST -> { - androidTaskNames?.assembleAndroidTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.assembleAndroidTestTask) + androidTaskNames.assembleAndroidTestTasks.takeIf { + it.isNotEmpty() + } ?: getPathAndTask(project, AffectedTestConfiguration.DEFAULT_ASSEMBLE_ANDROID_TEST_TASK) } InternalTaskType.ANDROID_JVM_TEST -> { - androidTaskNames?.unitTestTasks?.takeIf { it.isNotEmpty() } ?: getPathAndTask(project, tasks.jvmTestTask) - } - - InternalTaskType.JVM_TEST -> { - if (tasks.jvmTestTask != AffectedTestConfiguration.DEFAULT_JVM_TEST_TASK) { - getPathAndTask(project, tasks.jvmTestTask) - } else { - getPathAndTask(project, taskType.originalGradleCommand) - } + androidTaskNames.unitTestTasks.takeIf { + it.isNotEmpty() + } ?: getPathAndTask(project, AffectedTestConfiguration.DEFAULT_JVM_TEST_TASK) } else -> { diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt index e328ca7..2f10027 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfiguration.kt @@ -10,17 +10,16 @@ import java.io.Serializable * } */ open class AffectedTestConfiguration { + var testTasksProvider: Provider? = null - var assembleAndroidTestTask: String? = DEFAULT_ASSEMBLE_ANDROID_TEST_TASK - var runAndroidTestTask: String? = DEFAULT_ANDROID_TEST_TASK - var jvmTestTask: String? = DEFAULT_JVM_TEST_TASK - - var testTasksProvider: Provider? = null + fun getTestTaskNames(): TaskNames { + return testTasksProvider?.orNull ?: TaskNames() + } - data class TasksNames( - val unitTestTasks: List, - val androidTestTasks: List, - val assembleAndroidTestTasks: List, + data class TaskNames( + val unitTestTasks: List = listOf(DEFAULT_JVM_TEST_TASK), + val androidTestTasks: List = listOf(DEFAULT_ANDROID_TEST_TASK), + val assembleAndroidTestTasks: List = listOf(DEFAULT_ASSEMBLE_ANDROID_TEST_TASK), ): Serializable companion object { diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfigurationTest.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfigurationTest.kt index 90f21e7..9c8acd1 100644 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfigurationTest.kt +++ b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedTestConfigurationTest.kt @@ -15,27 +15,13 @@ class AffectedTestConfigurationTest { @Test fun `GIVEN AffectedTestConfiguration WHEN default values THEN default values returned`() { - assertThat(config.assembleAndroidTestTask).isEqualTo("assembleDebugAndroidTest") - assertThat(config.runAndroidTestTask).isEqualTo("connectedDebugAndroidTest") - assertThat(config.jvmTestTask).isEqualTo("testDebugUnitTest") - } - - @Test - fun `GIVEN AffectedTestConfiguration WHEN values are updated THEN new values are returned`() { - // GIVEN - val assembleAndroidTestTask = "assembleAndroidTestTask" - val runAndroidTestTask = "runAndroidTestTask" - val jvmTest = "jvmTest" - - // WHEN - config.assembleAndroidTestTask = assembleAndroidTestTask - config.runAndroidTestTask = runAndroidTestTask - config.jvmTestTask = jvmTest - - // THEN - assertThat(config.assembleAndroidTestTask).isEqualTo(assembleAndroidTestTask) - assertThat(config.runAndroidTestTask).isEqualTo(runAndroidTestTask) - assertThat(config.jvmTestTask).isEqualTo(jvmTest) + val taskNames = config.getTestTaskNames() + assertThat(taskNames.assembleAndroidTestTasks.size).isEqualTo(1) + assertThat(taskNames.assembleAndroidTestTasks.first()).isEqualTo("assembleDebugAndroidTest") + assertThat(taskNames.androidTestTasks.size).isEqualTo(1) + assertThat(taskNames.androidTestTasks.first()).isEqualTo("connectedDebugAndroidTest") + assertThat(taskNames.unitTestTasks.size).isEqualTo(1) + assertThat(taskNames.unitTestTasks.first()).isEqualTo("testDebugUnitTest") } @Test