From df3cd2b0d9d8c8b25198befcad9cb4c4c54c86ce Mon Sep 17 00:00:00 2001 From: Dmitry Kostyrev Date: Mon, 13 Apr 2020 21:45:14 +0300 Subject: [PATCH 1/3] Introduce com.android.test plugin support --- and-test/app/build.gradle | 31 ++++++++ and-test/app/src/main/AndroidManifest.xml | 21 ++++++ and-test/build.gradle | 19 +++++ and-test/gradle | 1 + and-test/gradle.properties | 1 + and-test/gradlew | 1 + and-test/gradlew.bat | 1 + and-test/settings.gradle | 35 +++++++++ and-test/test/build.gradle | 49 +++++++++++++ and-test/test/src/main/AndroidManifest.xml | 26 +++++++ .../java/com/trevjonez/andapp/AppTesting.java | 34 +++++++++ .../com/trevjonez/composer/ComposerPlugin.kt | 5 ++ .../composer/internal/ComposerBasePlugin.kt | 8 ++- .../composer/internal/ComposerTestPlugin.kt | 71 +++++++++++++++++++ 14 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 and-test/app/build.gradle create mode 100644 and-test/app/src/main/AndroidManifest.xml create mode 100644 and-test/build.gradle create mode 120000 and-test/gradle create mode 120000 and-test/gradle.properties create mode 120000 and-test/gradlew create mode 120000 and-test/gradlew.bat create mode 100644 and-test/settings.gradle create mode 100644 and-test/test/build.gradle create mode 100644 and-test/test/src/main/AndroidManifest.xml create mode 100644 and-test/test/src/main/java/com/trevjonez/andapp/AppTesting.java create mode 100644 plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerTestPlugin.kt diff --git a/and-test/app/build.gradle b/and-test/app/build.gradle new file mode 100644 index 0000000..fdb93d4 --- /dev/null +++ b/and-test/app/build.gradle @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Trevor Jones + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id "com.android.application" +} + +android { + compileSdkVersion Integer.parseInt(COMPILE_SDK) + defaultConfig { + applicationId "com.trevjonez.testapp" + minSdkVersion Integer.parseInt(MIN_SDK) + targetSdkVersion Integer.parseInt(COMPILE_SDK) + versionCode 1 + versionName "1.0" + resValue "string", "app_name", "TestApp" + } +} diff --git a/and-test/app/src/main/AndroidManifest.xml b/and-test/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..23fdd0b --- /dev/null +++ b/and-test/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/and-test/build.gradle b/and-test/build.gradle new file mode 100644 index 0000000..aeb8b59 --- /dev/null +++ b/and-test/build.gradle @@ -0,0 +1,19 @@ +buildscript { + repositories { + mavenLocal() + google() + jcenter() + } + + dependencies { + classpath "com.trevjonez.composer:plugin:$CGP_VERSION" + } +} + +allprojects { + repositories { + mavenLocal() + google() + jcenter() + } +} diff --git a/and-test/gradle b/and-test/gradle new file mode 120000 index 0000000..3337596 --- /dev/null +++ b/and-test/gradle @@ -0,0 +1 @@ +../gradle \ No newline at end of file diff --git a/and-test/gradle.properties b/and-test/gradle.properties new file mode 120000 index 0000000..7677fb7 --- /dev/null +++ b/and-test/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/and-test/gradlew b/and-test/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/and-test/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/and-test/gradlew.bat b/and-test/gradlew.bat new file mode 120000 index 0000000..2840132 --- /dev/null +++ b/and-test/gradlew.bat @@ -0,0 +1 @@ +../gradlew.bat \ No newline at end of file diff --git a/and-test/settings.gradle b/and-test/settings.gradle new file mode 100644 index 0000000..c7abd11 --- /dev/null +++ b/and-test/settings.gradle @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Trevor Jones + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +pluginManagement { + repositories { + mavenLocal() + google() + jcenter() + } + resolutionStrategy { + eachPlugin { + if (requested.id.id.startsWith("com.android")) + useModule("com.android.tools.build:gradle:$AGP_VERSION") + } + } +} + +rootProject.name = "and-test" + +include(":app") +include(":test") diff --git a/and-test/test/build.gradle b/and-test/test/build.gradle new file mode 100644 index 0000000..a7d49a6 --- /dev/null +++ b/and-test/test/build.gradle @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Trevor Jones + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id "com.android.test" + id "com.trevjonez.composer" +} + +android { + compileSdkVersion Integer.parseInt(COMPILE_SDK) + defaultConfig { + minSdkVersion Integer.parseInt(MIN_SDK) + targetSdkVersion Integer.parseInt(COMPILE_SDK) + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + targetProjectPath ":app" +} + +dependencies { + implementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'androidx.test:runner:1.2.0' + implementation 'androidx.test.ext:junit:1.1.1' +} + +composer { + verboseOutput true + instrumentationArgument('screenshotsDisabled', 'false') + apkInstallTimeout 10 + configs { + debug { + instrumentationArgument('screenshotsEngine', 'uiAutomator') + keepOutput true + } + } +} diff --git a/and-test/test/src/main/AndroidManifest.xml b/and-test/test/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ece9cc2 --- /dev/null +++ b/and-test/test/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/and-test/test/src/main/java/com/trevjonez/andapp/AppTesting.java b/and-test/test/src/main/java/com/trevjonez/andapp/AppTesting.java new file mode 100644 index 0000000..7e54889 --- /dev/null +++ b/and-test/test/src/main/java/com/trevjonez/andapp/AppTesting.java @@ -0,0 +1,34 @@ +package com.trevjonez.andapp; + +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +@RunWith(AndroidJUnit4.class) +public class AppTesting { + + @Test + public void basicMaths() { + Log.w("AppTesting", "Can do maths and log?"); + assertEquals(4, 2 + 2); + } + + @Test + public void assumeFailed() { + //noinspection ConstantConditions + assumeTrue("This assumption is clearly bad",false); + } + + @Test + public void assertFailed() { + //noinspection ConstantConditions,SimplifiableJUnitAssertion + assertTrue("This assert is clearly bad",false); + } +} diff --git a/plugin/src/main/kotlin/com/trevjonez/composer/ComposerPlugin.kt b/plugin/src/main/kotlin/com/trevjonez/composer/ComposerPlugin.kt index a64ec6f..473ce7b 100644 --- a/plugin/src/main/kotlin/com/trevjonez/composer/ComposerPlugin.kt +++ b/plugin/src/main/kotlin/com/trevjonez/composer/ComposerPlugin.kt @@ -20,6 +20,7 @@ import com.android.build.gradle.api.AndroidBasePlugin import com.trevjonez.composer.internal.ComposerApplicationPlugin import com.trevjonez.composer.internal.ComposerDynamicFeaturePlugin import com.trevjonez.composer.internal.ComposerLibraryPlugin +import com.trevjonez.composer.internal.ComposerTestPlugin import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project @@ -43,6 +44,10 @@ class ComposerPlugin : Plugin { project.pluginManager.apply(ComposerDynamicFeaturePlugin::class.java) } + project.pluginManager.withPlugin("com.android.test") { + project.pluginManager.apply(ComposerTestPlugin::class.java) + } + project.afterEvaluate { project.extensions.findByType(ConfigExtension::class.java) ?: project.tasks.find { it is ComposerTask } //check if manually created tasks exist before throwing diff --git a/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerBasePlugin.kt b/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerBasePlugin.kt index 98e4b6e..cf6be47 100644 --- a/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerBasePlugin.kt +++ b/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerBasePlugin.kt @@ -47,6 +47,10 @@ abstract class ComposerBasePlugin : Plugin return project.layout.buildDirectory.dir("reports/composer/$dirName") } + open fun T.isTestable(): Boolean { + return testVariant != null + } + lateinit var project: Project lateinit var globalConfig: ConfigExtension @@ -62,8 +66,8 @@ abstract class ComposerBasePlugin : Plugin private fun observeVariants() { testableVariants.all { testableVariant -> if (globalConfig.variants.isEmpty() || globalConfig.variants.contains(testableVariant.name)) { - if (testableVariant.testVariant == null) { - project.logger.info("variant: ${testableVariant.name}. has no test variant. skipping composer task registration.") + if (!testableVariant.isTestable()) { + project.logger.info("variant: ${testableVariant.name}. is not testable. skipping composer task registration.") return@all } diff --git a/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerTestPlugin.kt b/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerTestPlugin.kt new file mode 100644 index 0000000..a88e3e0 --- /dev/null +++ b/plugin/src/main/kotlin/com/trevjonez/composer/internal/ComposerTestPlugin.kt @@ -0,0 +1,71 @@ +package com.trevjonez.composer.internal + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.TestExtension +import com.android.build.gradle.api.ApplicationVariant +import com.trevjonez.composer.ComposerTask +import org.gradle.api.DomainObjectCollection +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import java.io.File + +class ComposerTestPlugin : ComposerBasePlugin() { + private val testExtension by lazy(LazyThreadSafetyMode.NONE) { + requireNotNull(project.findExtension("android")) { + "Failed to find android test extension" + } + } + + private val appExtension by lazy(LazyThreadSafetyMode.NONE) { + val project = requireNotNull(project.findProject(testExtension.targetProjectPath)) { + "Failed to find target project ${testExtension.targetProjectPath}" + } + requireNotNull(project.findExtension("android")) { + "Failed to find android app extension" + } + } + + private val androidTestUtil by lazy(LazyThreadSafetyMode.NONE) { + project.configurations.findByName("androidTestUtil") + } + + override val sdkDir: File + get() = testExtension.sdkDirectory + + override val testableVariants: DomainObjectCollection + get() = testExtension.applicationVariants + + override fun ApplicationVariant.getApk(task: ComposerTask): Provider { + val variant = appExtension.applicationVariants.find { it.name == name } + ?: error("Failed to find application variant") + + task.dependsOn(variant.assembleProvider) + return project.layout.file(project.provider { + variant.outputs.single().outputFile + }) + } + + override fun ApplicationVariant.getTestApk(task: ComposerTask): Provider { + task.dependsOn(assembleProvider) + return project.layout.file(project.provider { + outputs.single().outputFile + }) + } + + override fun ApplicationVariant.getExtraApks(task: ComposerTask): ConfigurableFileCollection { + return project.objects.fileCollection().also { + it.from(project.provider { + androidTestUtil?.resolvedConfiguration?.files?.toList().orEmpty() + }) + } + } + + override fun ApplicationVariant.getMultiApks(task: ComposerTask): ConfigurableFileCollection { + return project.objects.fileCollection() + } + + override fun ApplicationVariant.isTestable(): Boolean { + return true + } +} From 1d12855fd49c9cd490c11d31a13f3d9925e86448 Mon Sep 17 00:00:00 2001 From: trevjonez Date: Mon, 13 Apr 2020 14:45:00 -0600 Subject: [PATCH 2/3] Add ComposerTestPlugin to the ComposerPluginTest.kt smoke checks Remove the explicit inclusion of the plugin from the and-test root project classpath (GradleRunner.withPluginClasspath() handles it when running smoke tests.) --- and-test/build.gradle | 12 ---------- plugin/build.gradle | 10 +++++++++ .../trevjonez/composer/ComposerPluginTest.kt | 22 ++++++++++++++++++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/and-test/build.gradle b/and-test/build.gradle index aeb8b59..fb45b3f 100644 --- a/and-test/build.gradle +++ b/and-test/build.gradle @@ -1,15 +1,3 @@ -buildscript { - repositories { - mavenLocal() - google() - jcenter() - } - - dependencies { - classpath "com.trevjonez.composer:plugin:$CGP_VERSION" - } -} - allprojects { repositories { mavenLocal() diff --git a/plugin/build.gradle b/plugin/build.gradle index 6905f3c..0c86c9b 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -39,6 +39,7 @@ tasks.named("test").configure { systemProperty("andApp", new File(rootProject.projectDir, "and-app").absolutePath) systemProperty("andLib", new File(rootProject.projectDir, "and-lib").absolutePath) systemProperty("andDyn", new File(rootProject.projectDir, "and-dyn").absolutePath) + systemProperty("andTest", new File(rootProject.projectDir, "and-test").absolutePath) systemProperty("org.gradle.testkit.debug", false) outputs.dir("$buildDir/tests") @@ -68,6 +69,15 @@ tasks.named("test").configure { inputs.dir(new File(rootProject.projectDir, "and-dyn/atInstall/src")) inputs.files(new File(rootProject.projectDir, "and-dyn/atInstall/build.gradle")) + inputs.dir(new File(rootProject.projectDir, "and-test/app/src")) + inputs.files(new File(rootProject.projectDir, "and-test/app/build.gradle")) + + inputs.dir(new File(rootProject.projectDir, "and-test/test/src")) + inputs.files(new File(rootProject.projectDir, "and-test/test/build.gradle")) + + inputs.files(new File(rootProject.projectDir, "and-test/build.gradle")) + inputs.files(new File(rootProject.projectDir, "and-test/settings.gradle")) + inputs.dir(new File(rootProject.projectDir, "commander/os/src/main")) inputs.files(new File(rootProject.projectDir, "commander/os/build.gradle")) diff --git a/plugin/src/test/kotlin/com/trevjonez/composer/ComposerPluginTest.kt b/plugin/src/test/kotlin/com/trevjonez/composer/ComposerPluginTest.kt index f3804d7..ca5ea7c 100644 --- a/plugin/src/test/kotlin/com/trevjonez/composer/ComposerPluginTest.kt +++ b/plugin/src/test/kotlin/com/trevjonez/composer/ComposerPluginTest.kt @@ -36,6 +36,7 @@ class ComposerPluginTest { private val andApp by systemProperty private val andLib by systemProperty private val andDyn by systemProperty + private val andTest by systemProperty @get:Rule val testProjectDir = BuildDir(buildDir) @@ -91,7 +92,7 @@ class ComposerPluginTest { * Run with at least one device/emulator connected */ @Test - fun `library plugin integration`() { + fun `test plugin integration`() { val projectDir = testProjectDir.newFolder("basicLib").apply { andLib.copyRecursively(this, true) writeLocalProps() @@ -106,6 +107,25 @@ class ComposerPluginTest { "Error: There were failed tests.") } + /** + * Run with at least one device/emulator connected + */ + @Test + fun `library plugin integration`() { + val projectDir = testProjectDir.newFolder("basicTest").apply { + andTest.copyRecursively(this, true) + writeLocalProps() + } + + val result = gradleRunner(projectDir, "testDebugComposer") + .buildAndFail() + + assertThat(result.output).contains("Successfully installed apk", + "Starting tests", + "Test run finished, total passed = 1, total failed = 1, total ignored = 1", + "Error: There were failed tests.") + } + /** * Run with at least one device/emulator connected */ From e11f67899b76d909a9d2f611239aba73658678c1 Mon Sep 17 00:00:00 2001 From: trevjonez Date: Mon, 13 Apr 2020 14:50:31 -0600 Subject: [PATCH 3/3] Update CHANGELOG.md with new feature notice. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 348c974..5b09549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Items listed here may not be exhaustive, if you are seeing issues, check the git - Update gradle and AGP to latest stable. `6.2.1` & `3.6.1` respectively. - Process: Github actions for verification. #36 again, thank you [@plastiv](https://github.com/plastiv). - Feature: Dynamic-Feature module support +- Feature: Test module support "com.android.test". #71 thanks [@dkostyrev](https://github.com/dkostyrev). ## 1.0.0-rc05 - Catch a specific subset of errors allowing clean disposal of underlying processes. (logcat and instrumentation)