Custom task to execute all unit tests in all subprojects (aka. modules)

I would like to write a custom task for an Android application project with multiple Gradle modules which executes the unit tests in the following modules so I can run all of them with one Gradle task invocation:

  • Android application module, e.g. testOrangeDebugUnitTest
  • Android library modules, e.g. testDebug
  • Java/Kotlin library modules, e.g. test

Requirements

  1. It would be convenient to being able to configure one product flavor (e.g. “orange”) to being tested (running all would take too long).
  2. Also, running Android unit tests with the debug product type is sufficient.
  3. Modules names should not be hardcoded so that the project is extensible and tests in new modules are picked up automatically.

Draft

Here is my work in progress approach:

// buildSrc/src/main/kotlin/com/example/TestAllModulesTask.kt
abstract class TestAllModulesTask : DefaultTask() {

    private companion object {
        val DISALLOWED_BUILD_TYPES = setOf("release")
        val DISALLOWED_PRODUCT_FLAVORS = setOf("blue", "green")
    }

    private val allowedTestTasks = mutableListOf<Pair<Project, Test>>()

    init {
        project.subprojects
            .forEach { subproject ->
                subproject.tasks
                    .filterIsInstance<Test>()
                    .filter { !it.namedWithAny(DISALLOWED_BUILD_TYPES) }
                    .filter { !it.namedWithAny(DISALLOWED_PRODUCT_FLAVORS) }
                    .forEach { task ->
                        println(":${subproject.name}:${task.name}")
                        allowedTestTasks.add(subproject to task)
                    }
            }
    }

    override fun getDescription(): String {
        return "Runs all tests for all modules except for disallowed build types and product flavors."
    }

    override fun getGroup(): String {
        return "verification"
    }

    @TaskAction
    fun action() {
        println(allowedTestTasks)
        // TODO Execute all the allowedTestTasks ./gradlew
    }

    private fun Test.namedWithAny(disallowedTerms: Set<String>): Boolean {
        return disallowedTerms.any { name.contains(it, ignoreCase = true) }
    }

}

Then register the custom task:

// /build.gradle
import com.example.TestAllModulesTask

tasks.register("testAllModules", TestAllModulesTask)

Questions

  1. What would be a modern way to collect all the test tasks and wire them with the custom task? I don’t really like the string comparison here. Filtering by build type and product flavor would be appreciated.
  2. How do I tell the custom task to actually execute all the test tasks which were collected?

Related

What would be a modern way to collect all the test tasks and wire them with the custom task?

You shouldn’t.
Especially reaching into the subproject models and operating on their tasks is extremely problematic.
This would for example require that all subprojects are configured already.
And even then it would be very bad practice and unsafe.
And also add project coupling which works against various optimizations and more sophisticated Gradle features.

What you for example would do is to have a convention plugin that apply to all projects and in that you for example register a testAllModules task that depends on the tasks you want to depend on in the current project according to whatever logic you want.
Then you also do not need and root project testAllModules task if there are no relevant tasks, because if you request ./gradlew testAllModules then this task is executed in all project that have such a task.

How do I tell the custom task to actually execute all the test tasks which were collected?

You cannot.
A task cannot “call” or “execute” other tasks.
It can only depend on other tasks or have them as finalizer, …