From ef0acf9888fbc65e72b2e76f20d2cc9852322811 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 24 Oct 2020 13:10:36 +0200 Subject: [PATCH 1/4] Allow easier filtering of functional tests & update template file names --- .../gradle/plugins/junit5/FunctionalTests.kt | 122 +++++++++++------- .../projects/FunctionalTestProjectCreator.kt | 16 +-- ...dle.template => build.gradle.kts.template} | 0 ....template => settings.gradle.kts.template} | 0 4 files changed, 86 insertions(+), 52 deletions(-) rename plugin/android-junit5/src/test/resources/test-projects/{build.gradle.template => build.gradle.kts.template} (100%) rename plugin/android-junit5/src/test/resources/test-projects/{settings.gradle.template => settings.gradle.kts.template} (100%) diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt index e3bbd4a6..28172b51 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt @@ -18,67 +18,101 @@ import java.io.File class FunctionalTests { private val environment = TestEnvironment() - private lateinit var projectCreator: FunctionalTestProjectCreator + private lateinit var folder: File + + // Test permutations for AGP (default: empty set, which will exercise all) + private val testedAgpVersions: Set = setOf( + "4.1" + ) + + // Test permutations for projects (default: empty set, which will exercise all) + private val testedProjects: Set = setOf( + ) @BeforeAll fun beforeAll() { // The "project provider" is responsible for the construction // of all virtual Gradle projects, using a template file located in // the project's test resources. - val folder = File("build/tmp/virtualProjectsRoot") - folder.mkdirs() - projectCreator = FunctionalTestProjectCreator(folder, environment) + folder = File("build/tmp/virtualProjectsRoot").also { it.mkdirs() } } @TestFactory fun execute(): List = - environment.supportedAgpVersions.map { agp -> - dynamicContainer( - "AGP ${agp.shortVersion}", - projectCreator.allSpecs.map { spec -> - dynamicTest(spec.name) { - // Required for visibility inside IJ's logging console (display names are still bugged in the IDE) - println("AGP: ${agp.version}, Project: ${spec.name}, Forced Gradle: ${agp.requiresGradle ?: "no"}") - - // Create a virtual project with the given settings & AGP version - val project = projectCreator.createProject(spec, agp) - - // Execute the tests of the virtual project with Gradle - val result = runGradle(agp) - .withProjectDir(project) - .build() - - // Check that the task execution was successful in general - when (val outcome = result.task(":test")?.outcome) { - TaskOutcome.UP_TO_DATE -> { - // Nothing to do, a previous build already checked this - println("Test task up-to-date; skipping assertions.") - } + // Create a matrix of permutations between the AGP versions to test + // and the language of the project's build script + environment.supportedAgpVersions.filterAgpVersions() + .map { agp -> + val projectCreator = FunctionalTestProjectCreator(folder, environment) - TaskOutcome.SUCCESS -> { - // Based on the spec's configuration in the test project, - // assert that all test classes have been executed as expected - for (expectation in spec.expectedTests) { - result.assertAgpTests( - buildType = expectation.buildType, - productFlavor = expectation.productFlavor, - tests = expectation.testsList - ) - } - } + // Generate a container for all tests with this specific AGP/Language combination + dynamicContainer("AGP ${agp.shortVersion}", + + // Exercise each test project within the given environment + projectCreator.allSpecs.filterSpecs().map { spec -> + dynamicTest(spec.name) { + // Required for visibility inside IJ's logging console (display names are still bugged in the IDE) + println("AGP: ${agp.version}, Project: ${spec.name}, Forced Gradle: ${agp.requiresGradle ?: "no"}") + + // Create a virtual project with the given settings & AGP version + val project = projectCreator.createProject(spec, agp) + + // Execute the tests of the virtual project with Gradle + val result = runGradle(agp) + .withProjectDir(project) + .build() + + // Check that the task execution was successful in general + when (val outcome = result.task(":test")?.outcome) { + TaskOutcome.UP_TO_DATE -> { + // Nothing to do, a previous build already checked this + println("Test task up-to-date; skipping assertions.") + } + + TaskOutcome.SUCCESS -> { + // Based on the spec's configuration in the test project, + // assert that all test classes have been executed as expected + for (expectation in spec.expectedTests) { + result.assertAgpTests( + buildType = expectation.buildType, + productFlavor = expectation.productFlavor, + tests = expectation.testsList + ) + } + } - else -> { - // Unexpected result; fail - fail { "Unexpected task outcome: $outcome" } + else -> { + // Unexpected result; fail + fail { "Unexpected task outcome: $outcome" } + } + } } } - } - } - ) - } + ) + } /* Private */ + private fun List.filterAgpVersions(): List = + if (testedAgpVersions.isEmpty()) { + // Nothing to do, exercise functional tests on all AGP versions + this + } else { + filter { agp -> + testedAgpVersions.any { it == agp.shortVersion } + } + } + + private fun List.filterSpecs(): List = + if (testedProjects.isEmpty()) { + // Nothing to do, exercise all different projects + this + } else { + filter { spec -> + testedProjects.any { it == spec.name } + } + } + private fun runGradle(agpVersion: TestedAgp) = GradleRunner.create() .apply { diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt index 4ad449a0..97e65085 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt @@ -8,10 +8,10 @@ import de.mannodermaus.gradle.plugins.junit5.util.TestEnvironment import java.io.File private const val TEST_PROJECTS_RESOURCE = "/test-projects" -private const val BUILD_GRADLE_TEMPLATE_NAME = "build.gradle.template" -private const val SETTINGS_GRADLE_TEMPLATE_NAME = "settings.gradle.template" -private const val BUILD_GRADLE_NAME = "build.gradle.kts" -private const val SETTINGS_GRADLE_NAME = "settings.gradle.kts" +private const val BUILD_GRADLE_TEMPLATE_NAME = "build.gradle.kts.template" +private const val SETTINGS_GRADLE_TEMPLATE_NAME = "settings.gradle.kts.template" +private const val OUTPUT_BUILD_GRADLE_NAME = "build.gradle.kts" +private const val OUTPUT_SETTINGS_GRADLE_NAME = "settings.gradle.kts" private const val PROJECT_CONFIG_FILE_NAME = "config.toml" private const val SRC_FOLDER_NAME = "src" @@ -58,8 +58,8 @@ class FunctionalTestProjectCreator(private val rootFolder: File, val projectFolder = File(rootFolder, projectName) if (projectFolder.exists()) { File(projectFolder, SRC_FOLDER_NAME).deleteRecursively() - File(projectFolder, BUILD_GRADLE_NAME).delete() - File(projectFolder, SETTINGS_GRADLE_NAME).delete() + File(projectFolder, OUTPUT_BUILD_GRADLE_NAME).delete() + File(projectFolder, OUTPUT_SETTINGS_GRADLE_NAME).delete() } projectFolder.mkdirs() @@ -78,9 +78,9 @@ class FunctionalTestProjectCreator(private val rootFolder: File, val processor = BuildScriptTemplateProcessor(agp.requiresGradle, replacements) val processedBuildGradle = processor.process(rawBuildGradle) - File(projectFolder, BUILD_GRADLE_NAME).writeText(processedBuildGradle) + File(projectFolder, OUTPUT_BUILD_GRADLE_NAME).writeText(processedBuildGradle) val processedSettingsGradle = processor.process(rawSettingsGradle) - File(projectFolder, SETTINGS_GRADLE_NAME).writeText(processedSettingsGradle) + File(projectFolder, OUTPUT_SETTINGS_GRADLE_NAME).writeText(processedSettingsGradle) return projectFolder } diff --git a/plugin/android-junit5/src/test/resources/test-projects/build.gradle.template b/plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template similarity index 100% rename from plugin/android-junit5/src/test/resources/test-projects/build.gradle.template rename to plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template diff --git a/plugin/android-junit5/src/test/resources/test-projects/settings.gradle.template b/plugin/android-junit5/src/test/resources/test-projects/settings.gradle.kts.template similarity index 100% rename from plugin/android-junit5/src/test/resources/test-projects/settings.gradle.template rename to plugin/android-junit5/src/test/resources/test-projects/settings.gradle.kts.template From 97a4a6f415571756881d5472a027617492211fca Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 24 Oct 2020 13:44:45 +0200 Subject: [PATCH 2/4] Allow functional test projects to have a min AGP requirement --- .../gradle/plugins/junit5/FunctionalTests.kt | 4 +++- .../projects/FunctionalTestProjectCreator.kt | 22 +++++++++++++++-- .../test-projects/new-variant-api/config.toml | 24 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 1 + .../main/java/de/mannodermaus/app/Adder.java | 7 ++++++ .../java/de/mannodermaus/app/JavaTest.java | 13 ++++++++++ .../mannodermaus/app/JavaFreeReleaseTest.java | 13 ++++++++++ .../mannodermaus/app/KotlinPaidDebugTest.kt | 12 ++++++++++ .../de/mannodermaus/app/KotlinReleaseTest.kt | 12 ++++++++++ 9 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/AndroidManifest.xml create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/java/de/mannodermaus/app/Adder.java create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/test/java/de/mannodermaus/app/JavaTest.java create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testFreeRelease/java/de/mannodermaus/app/JavaFreeReleaseTest.java create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testPaidDebug/java/de/mannodermaus/app/KotlinPaidDebugTest.kt create mode 100644 plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testRelease/java/de/mannodermaus/app/KotlinReleaseTest.kt diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt index 28172b51..54fa81d5 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt @@ -54,7 +54,9 @@ class FunctionalTests { // Required for visibility inside IJ's logging console (display names are still bugged in the IDE) println("AGP: ${agp.version}, Project: ${spec.name}, Forced Gradle: ${agp.requiresGradle ?: "no"}") - // Create a virtual project with the given settings & AGP version + // Create a virtual project with the given settings & AGP version. + // This call will throw a TestAbortedException if the spec is not eligible for this version, + // marking the test as ignored in the process val project = projectCreator.createProject(spec, agp) // Execute the tests of the virtual project with Gradle diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt index 97e65085..aa0538f5 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt @@ -5,6 +5,9 @@ import com.uchuhimo.konf.ConfigSpec import com.uchuhimo.konf.source.toml import de.mannodermaus.gradle.plugins.junit5.util.TestedAgp import de.mannodermaus.gradle.plugins.junit5.util.TestEnvironment +import org.junit.jupiter.api.Assumptions +import org.junit.jupiter.api.Assumptions.assumeTrue +import org.opentest4j.TestAbortedException import java.io.File private const val TEST_PROJECTS_RESOURCE = "/test-projects" @@ -49,7 +52,11 @@ class FunctionalTestProjectCreator(private val rootFolder: File, ?: emptyList() } + @Throws(TestAbortedException::class) fun createProject(spec: Spec, agp: TestedAgp): File { + // Validate the spec requirement against the executing AGP version first + validateSpec(spec, agp) + // Construct the project folder, cleaning it if necessary. // If any Gradle or build caches already exist, we keep those around. // That's the reason for not doing "projectFolder.deleteRecursively()" @@ -85,12 +92,22 @@ class FunctionalTestProjectCreator(private val rootFolder: File, return projectFolder } + private fun validateSpec(spec: Spec, agp: TestedAgp) { + if (spec.minAgpVersion != null) { + // If the spec dictates a minimum version of the AGP, + // disable the test for plugin versions below that minimum requirement + assumeTrue( + SemanticVersion(agp.version) >= SemanticVersion(spec.minAgpVersion), + "This project requires AGP ${spec.minAgpVersion} and was disabled on ths version.") + } + } + /* Types */ class Spec private constructor(val name: String, val srcFolder: File, config: Config) { - + val minAgpVersion = config[TomlSpec.Settings.minAgpVersion] val useKotlin = config[TomlSpec.Settings.useKotlin] val useFlavors = config[TomlSpec.Settings.useFlavors] val useCustomBuildType = config[TomlSpec.Settings.useCustomBuildType] @@ -125,6 +142,7 @@ class FunctionalTestProjectCreator(private val rootFolder: File, val expectations by required>() object Settings : ConfigSpec() { + val minAgpVersion by optional(default = null) val useFlavors by optional(default = false) val useKotlin by optional(default = false) val useCustomBuildType by optional(default = null) @@ -139,7 +157,7 @@ class FunctionalTestProjectCreator(private val rootFolder: File, data class ExpectedTests( val buildType: String, val productFlavor: String?, - val tests: String + private val tests: String ) { val testsList = tests.split(",").map(String::trim) } diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml new file mode 100644 index 00000000..54ecc488 --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml @@ -0,0 +1,24 @@ +[settings] +minAgpVersion = "4.1" +useFlavors = true +useKotlin = true + +[[expectations]] +buildType = "debug" +productFlavor = "free" +tests = "JavaTest" + +[[expectations]] +buildType = "debug" +productFlavor = "paid" +tests = "JavaTest,KotlinPaidDebugTest" + +[[expectations]] +buildType = "release" +productFlavor = "free" +tests = "JavaTest,KotlinReleaseTest,JavaFreeReleaseTest" + +[[expectations]] +buildType = "release" +productFlavor = "paid" +tests = "JavaTest,KotlinReleaseTest" diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/AndroidManifest.xml b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..426c240f --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/java/de/mannodermaus/app/Adder.java b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/java/de/mannodermaus/app/Adder.java new file mode 100644 index 00000000..d21fca3d --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/main/java/de/mannodermaus/app/Adder.java @@ -0,0 +1,7 @@ +package de.mannodermaus.app; + +public class Adder { + public int add(int a, int b) { + return a + b; + } +} diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/test/java/de/mannodermaus/app/JavaTest.java b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/test/java/de/mannodermaus/app/JavaTest.java new file mode 100644 index 00000000..08c59a97 --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/test/java/de/mannodermaus/app/JavaTest.java @@ -0,0 +1,13 @@ +package de.mannodermaus.app; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class JavaTest { + @Test + void test() { + Adder adder = new Adder(); + assertEquals(4, adder.add(2, 2), "This should succeed!"); + } +} diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testFreeRelease/java/de/mannodermaus/app/JavaFreeReleaseTest.java b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testFreeRelease/java/de/mannodermaus/app/JavaFreeReleaseTest.java new file mode 100644 index 00000000..6631487c --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testFreeRelease/java/de/mannodermaus/app/JavaFreeReleaseTest.java @@ -0,0 +1,13 @@ +package de.mannodermaus.app; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class JavaFreeReleaseTest { + @Test + void test() { + Adder adder = new Adder(); + assertEquals(4, adder.add(2, 2), "This should succeed!"); + } +} diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testPaidDebug/java/de/mannodermaus/app/KotlinPaidDebugTest.kt b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testPaidDebug/java/de/mannodermaus/app/KotlinPaidDebugTest.kt new file mode 100644 index 00000000..8fb6cadd --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testPaidDebug/java/de/mannodermaus/app/KotlinPaidDebugTest.kt @@ -0,0 +1,12 @@ +package de.mannodermaus.app + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class KotlinPaidDebugTest { + @Test + fun test() { + val adder = Adder() + assertEquals(4, adder.add(2, 2), "This should succeed!") + } +} diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testRelease/java/de/mannodermaus/app/KotlinReleaseTest.kt b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testRelease/java/de/mannodermaus/app/KotlinReleaseTest.kt new file mode 100644 index 00000000..cd36ba26 --- /dev/null +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/src/testRelease/java/de/mannodermaus/app/KotlinReleaseTest.kt @@ -0,0 +1,12 @@ +package de.mannodermaus.app + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class KotlinReleaseTest { + @Test + fun test() { + val adder = Adder() + assertEquals(4, adder.add(2, 2), "This should succeed!") + } +} From 977f2ccdd3109b6acf9293ad4afc3c2c20eccb41 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 24 Oct 2020 14:08:36 +0200 Subject: [PATCH 3/4] Add support for loops in project templates & new variant API --- .../gradle/plugins/junit5/FunctionalTests.kt | 5 ++-- .../projects/BuildScriptTemplateProcessor.kt | 30 ++++++++++++++++++- .../projects/FunctionalTestProjectCreator.kt | 6 ++++ .../test-projects/build.gradle.kts.template | 13 ++++++++ .../test-projects/new-variant-api/config.toml | 13 ++------ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt index 54fa81d5..cbeac554 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt @@ -22,7 +22,6 @@ class FunctionalTests { // Test permutations for AGP (default: empty set, which will exercise all) private val testedAgpVersions: Set = setOf( - "4.1" ) // Test permutations for projects (default: empty set, which will exercise all) @@ -145,7 +144,9 @@ class FunctionalTests { .ofTask(taskName) .apply { tests.forEach { expectedClass -> - contains("$expectedClass > test() PASSED") + val line = "$expectedClass > test() PASSED" + contains(line) + println(line) } executedTestCount().isEqualTo(tests.size) } diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/BuildScriptTemplateProcessor.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/BuildScriptTemplateProcessor.kt index 86fa7d6d..ec049076 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/BuildScriptTemplateProcessor.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/BuildScriptTemplateProcessor.kt @@ -7,6 +7,7 @@ private val GET_MATCHER = Regex("//\\\$GET\\{(.*)}") private val IF_MATCHER = Regex("//\\\$IF\\{(.*)}") private val ELSE_MATCHER = Regex("//\\\$ELSE") private val IFGRADLE_MATCHER = Regex("//\\\$IFGRADLE\\{(.*)}") +private val FOREACH_MATCHER = Regex("//\\\$FOREACH\\{(.+)}") private val END_MATCHER = Regex("//\\\$END") /** @@ -19,6 +20,7 @@ class BuildScriptTemplateProcessor(private val targetGradleVersion: String?, fun process(rawText: String): String { var ignoredBlockCount = 0 + var loopBlockCount = 0 // Replace GET tokens first val text1 = rawText.replace(GET_MATCHER) { result -> @@ -37,6 +39,7 @@ class BuildScriptTemplateProcessor(private val targetGradleVersion: String?, val ifMatch = IF_MATCHER.find(line) val elseMatch = ELSE_MATCHER.find(line) val ifgradleMatch = IFGRADLE_MATCHER.find(line) + val foreachMatch = FOREACH_MATCHER.find(line) val endMatch = END_MATCHER.find(line) if (ignoredBlockCount == 0 && ifMatch != null) { @@ -62,6 +65,25 @@ class BuildScriptTemplateProcessor(private val targetGradleVersion: String?, } } + if (ignoredBlockCount == 0 && foreachMatch != null) { + val foreachKey = foreachMatch.groupValues.last() + + // Started a loop. Find the associated list of elements first + val value = (replacements[foreachKey] as? List) + ?.joinToString(separator = ",") { "\"$it\""} + ?: throw IllegalArgumentException("FOREACH replacement value should be a list, but got: ${replacements[foreachKey]}") + + // Emit the beginning of a foreach loop and continue onward. + // As soon as the next END marker is detected, the loop will be closed again + if (value.isNotEmpty()) { + text2.append("listOf($value).forEach { it ->").appendln() + loopBlockCount++ + } else { + // Empty list == ignore the entire block and don't emit it + ignoredBlockCount++ + } + } + if (elseMatch != null) { ignoredBlockCount = if (ignoredBlockCount == 0) { 1 @@ -78,7 +100,13 @@ class BuildScriptTemplateProcessor(private val targetGradleVersion: String?, } if (endMatch != null) { - ignoredBlockCount = max(0, ignoredBlockCount - 1) + if (loopBlockCount > 0) { + // Emit the end of a prior loop + text2.append("}").appendln() + loopBlockCount = max(0, loopBlockCount - 1) + } else { + ignoredBlockCount = max(0, ignoredBlockCount - 1) + } } } return text2.toString() diff --git a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt index aa0538f5..8fec2b3c 100644 --- a/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt +++ b/plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/util/projects/FunctionalTestProjectCreator.kt @@ -82,6 +82,7 @@ class FunctionalTestProjectCreator(private val rootFolder: File, replacements["USE_CUSTOM_BUILD_TYPE"] = spec.useCustomBuildType replacements["RETURN_DEFAULT_VALUES"] = spec.returnDefaultValues replacements["INCLUDE_ANDROID_RESOURCES"] = spec.includeAndroidResources + replacements["DISABLE_TESTS_FOR_BUILD_TYPES"] = spec.disableTestsForBuildTypes val processor = BuildScriptTemplateProcessor(agp.requiresGradle, replacements) val processedBuildGradle = processor.process(rawBuildGradle) @@ -115,6 +116,10 @@ class FunctionalTestProjectCreator(private val rootFolder: File, val includeAndroidResources = config[TomlSpec.Settings.includeAndroidResources] val expectedTests = config[TomlSpec.expectations] + val disableTestsForBuildTypes = config[TomlSpec.Settings.disableTestsForBuildTypes] + ?.split(",")?.map(String::trim) + ?: emptyList() + companion object { fun tryCreate(folder: File): Spec? { if (folder.isFile) { @@ -148,6 +153,7 @@ class FunctionalTestProjectCreator(private val rootFolder: File, val useCustomBuildType by optional(default = null) val returnDefaultValues by optional(default = false) val includeAndroidResources by optional(default = false) + val disableTestsForBuildTypes by optional(default = null) } } diff --git a/plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template b/plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template index ce88e451..d8597fb5 100644 --- a/plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template +++ b/plugin/android-junit5/src/test/resources/test-projects/build.gradle.kts.template @@ -16,6 +16,10 @@ // Evaluate the running Gradle version against the "versionmarker", excluding the block if it doesn't match. // The "versionmarker" is an expression in the form 'x.y', where x.y is a Gradle version. // An AGP version without a fixed Gradle requirement will always evaluate these blocks to false, skipping them +// $FOREACH{list} +// Emit the body of the enclosed block for each item in the provided "list", +// which is coerced into a list of strings by the template processor. +// Inside the enclosed block, the list element can be accessed with the `it` variable. // $ELSE // Alternative case for $IF or $GRADLE markers. // Inverts the status of excluding/including blocks based on the condition. @@ -115,6 +119,15 @@ android { }, unitTests)) //$END } + + // New Variant API, introduced with AGP 4.1 + //$FOREACH{DISABLE_TESTS_FOR_BUILD_TYPES} + onVariants.withBuildType(it) { + unitTest { + enabled = false + } + } + //$END } dependencies { diff --git a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml index 54ecc488..c2e0e632 100644 --- a/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml +++ b/plugin/android-junit5/src/test/resources/test-projects/new-variant-api/config.toml @@ -1,17 +1,8 @@ [settings] -minAgpVersion = "4.1" +minAgpVersion = "4.0" useFlavors = true useKotlin = true - -[[expectations]] -buildType = "debug" -productFlavor = "free" -tests = "JavaTest" - -[[expectations]] -buildType = "debug" -productFlavor = "paid" -tests = "JavaTest,KotlinPaidDebugTest" +disableTestsForBuildTypes = "debug" [[expectations]] buildType = "release" From 1fa26c29a0993f8548c54a9db94a0d6ad09d20d9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 24 Oct 2020 14:25:32 +0200 Subject: [PATCH 4/4] Be more lenient with absent test tasks The new Variant API, introduced in AGP 4+, allows test tasks to be disabled on a per-variant basis with an easier API. The plugin must not expect all test tasks to always be present, since that could result in UnknownTaskExceptions down the line. Now, testTaskOf() uses the less direct "findByName()" API rather than "getByName()" to obtain the AndroidTestTasks, bailing out if a task is absent. --- .../kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt | 7 ++++--- .../gradle/plugins/junit5/internal/Extensions.kt | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt index 0276c3cd..4c24199b 100644 --- a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt +++ b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt @@ -56,9 +56,10 @@ class AndroidJUnitPlatformPlugin : Plugin { } private fun Project.configureUnitTests() { - // Configure JUnit 5 for each variant-specific unit test task + // Configure JUnit 5 for each variant-specific unit test task, + // unless that variant has its tests disabled projectConfig.variants.all { variant -> - val testTask = tasks.testTaskOf(variant) + val testTask = tasks.testTaskOf(variant) ?: return@all val configuration = junit5ConfigurationOf(variant) testTask.useJUnitPlatform { options -> @@ -133,7 +134,7 @@ class AndroidJUnitPlatformPlugin : Plugin { if (isJacocoApplied && jacocoOptions.taskGenerationEnabled) { projectConfig.variants.all { variant -> val directoryProviders = collectDirectoryProviders(variant) - val testTask = tasks.testTaskOf(variant) + val testTask = tasks.testTaskOf(variant) ?: return@all // Create a Jacoco friend task val enabledVariants = jacocoOptions.onlyGenerateTasksForVariants diff --git a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/Extensions.kt b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/Extensions.kt index 92a4373e..c69a6be4 100644 --- a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/Extensions.kt +++ b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/Extensions.kt @@ -217,13 +217,13 @@ internal fun BaseVariant.getTaskName(prefix: String = "", suffix: String = ""): /** * Obtains the {AndroidUnitTest} for the provided variant. */ -internal fun TaskContainer.testTaskOf(variant: BaseVariant): AndroidUnitTest { +internal fun TaskContainer.testTaskOf(variant: BaseVariant): AndroidUnitTest? { // From AGP 4.1 onwards, there is no Scope API on VariantData anymore. // Task names must be constructed manually val taskName = variant.getTaskName( prefix = VariantTypeCompat.UNIT_TEST_PREFIX, suffix = VariantTypeCompat.UNIT_TEST_SUFFIX) - return getByName(taskName) as AndroidUnitTest + return findByName(taskName) as? AndroidUnitTest } /**