Repository: runningcode/fladle
Pull request #124 (2020-05-17 12:18:39)

Configure entire project APKs for additionalTestApks.
This adds a new plugin called `com.osacky.fulladle` which is to be applied at the root of the project.
It scans the project's submodules and adds all the apks and test apks to additionalTestApks.

#96 


Patch:
diff --git a/README.md b/README.md
index 47c6df9..c9370ec 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 # Fladle 
 
-The Gradle Plugin for Firebase Test Lab and Flank
+Easily scale your Android Instrumentation tests on Firebase Test Lab with Flank.
 
 ### Documentation is at [runningcode.github.io/fladle](https://runningcode.github.io/fladle)
diff --git a/build.gradle b/build.gradle
index 12d694c..3c9b2c3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,15 +14,22 @@ buildscript {
 
 plugins {
     id "com.github.ben-manes.versions" version "0.28.0"
+    id "com.osacky.fulladle"
 }
 
 allprojects {
     repositories {
         google()
         mavenCentral()
+        // jcenter is required for "org.jetbrains.trove4j:trove4j:20160824."
+        jcenter()
     }
 }
 
+fladle {
+    serviceAccountCredentials = project.layout.projectDirectory.file("sample/flank-gradle-5cf02dc90531.json")
+}
+
 task clean(type: Delete) {
     delete rootProject.buildDir
 }
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 2caed09..f0a94c3 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,5 +1,6 @@
 group = "com.osacky.flank.gradle"
-version = "0.9.5-SNAPSHOT"
+version = "0.10.0-SNAPSHOT"
+description = "Easily Scale your Android Instrumentation Tests with Firebase Test Lab with Flank"
 
 repositories {
   google()
@@ -52,9 +53,15 @@ gradlePlugin {
     create("fladle") {
       id = "com.osacky.fladle"
       displayName = "Fladle"
-      description = "The Gradle Plugin for Flank"
+      description = project.description
       implementationClass = "com.osacky.flank.gradle.FlankGradlePlugin"
     }
+    create("fulladle") {
+      id = "com.osacky.fulladle"
+      displayName = "Fulladle"
+      description = project.description
+      implementationClass = "com.osacky.flank.gradle.FulladlePlugin"
+    }
   }
 }
 
@@ -92,19 +99,23 @@ publishing {
     }
   }
   publications {
-      afterEvaluate {
-          named<MavenPublication>("fladlePluginMarkerMaven") {
-              signing.sign(this)
-              pom.configureForFladle()
-          }
-
-          named<MavenPublication>("pluginMaven") {
-              artifact(tasks["sourcesJar"])
-              artifact(tasks["javadocJar"])
-              signing.sign(this)
-              pom.configureForFladle()
-          }
+    afterEvaluate {
+      named<MavenPublication>("fladlePluginMarkerMaven") {
+        signing.sign(this)
+        pom.configureForFladle("Fladle")
+      }
+
+      named<MavenPublication>("pluginMaven") {
+        artifact(tasks["sourcesJar"])
+        artifact(tasks["javadocJar"])
+        signing.sign(this)
+        pom.configureForFladle("Fladle")
+      }
+      named<MavenPublication>("fulladlePluginMarkerMaven") {
+        signing.sign(this)
+        pom.configureForFladle("Fulladle")
       }
+    }
   }
 }
 
@@ -112,25 +123,25 @@ signing {
   isRequired = isReleaseBuild
 }
 
-fun org.gradle.api.publish.maven.MavenPom.configureForFladle() {
-    name.set("Fladle")
-    description.set("The Gradle Plugin for Flank")
-    url.set("https://github.com/runningcode/fladle")
-    licenses {
-        license {
-            name.set("The Apache License, Version 2.0")
-            url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
-        }
+fun org.gradle.api.publish.maven.MavenPom.configureForFladle(pluginName: String) {
+  name.set(pluginName)
+  description.set(project.description)
+  url.set("https://github.com/runningcode/fladle")
+  licenses {
+    license {
+      name.set("The Apache License, Version 2.0")
+      url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
     }
-    developers {
-        developer {
-            id.set("runningcode")
-            name.set("Nelson Osacky")
-        }
-    }
-    scm {
-        connection.set("scm:git:git://github.com/runningcode/fladle.git")
-        developerConnection.set("scm:git:ssh://github.com/runningcode/fladle.git")
-        url.set("https://github.com/runningcode/fladle")
+  }
+  developers {
+    developer {
+      id.set("runningcode")
+      name.set("Nelson Osacky")
     }
+  }
+  scm {
+    connection.set("scm:git:git://github.com/runningcode/fladle.git")
+    developerConnection.set("scm:git:ssh://github.com/runningcode/fladle.git")
+    url.set("https://github.com/runningcode/fladle")
+  }
 }
diff --git a/buildSrc/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt b/buildSrc/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt
new file mode 100644
index 0000000..6af8462
--- /dev/null
+++ b/buildSrc/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt
@@ -0,0 +1,78 @@
+package com.osacky.flank.gradle
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.LibraryExtension
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.getByType
+
+/**
+ * Like the Fladle plugin, but it configures additionalTestApks for the _full_ project. Hence fulladle.
+ */
+class FulladlePlugin : Plugin<Project> {
+  override fun apply(root: Project) {
+    println("""
+              Warning: Fulladle is still in development. It is very likely not to work. 
+               * Report bugs to the Firebase Community slack in #flank or as an issue in the Fladle project.
+               * Include the output from the printYml task.
+            """.trimIndent())
+
+    check(root.parent == null) { "Fulladle must be applied in the root project in order to configure subprojects." }
+    FladlePluginDelegate().apply(root)
+
+    val flankGradleExtension = root.extensions.getByType(FlankGradleExtension::class)
+
+    root.subprojects {
+      pluginManager.withPlugin("com.android.application") {
+        val appExtension = extensions.getByType<AppExtension>()
+        // Only configure the first test variant per module.
+        // Does anyone test more than one variant per module?
+        var addedTestsForModule = false
+
+        // TODO deal with ignored/filtered variants
+        appExtension.testVariants.configureEach testVariant@{
+          if (addedTestsForModule) {
+            return@testVariant
+          }
+          val appVariant = testedVariant
+          appVariant.outputs.configureEach app@{
+
+            this@testVariant.outputs.configureEach test@{
+              // TODO is this racy?
+              // If the debugApk isn't yet set, let's use this one.
+              if (!flankGradleExtension.debugApk.isPresent) {
+                flankGradleExtension.debugApk.set(root.provider { this@app.outputFile.absolutePath })
+              } else {
+                // Otherwise, let's just add it to the list.
+                flankGradleExtension.additionalTestApks.add(root.provider {
+                  "- app: ${this@app.outputFile}"
+                })
+              }
+              // If the instrumentation apk isn't yet set, let's use this one.
+              if (!flankGradleExtension.instrumentationApk.isPresent) {
+                flankGradleExtension.instrumentationApk.set(root.provider { this@test.outputFile.absolutePath })
+              } else {
+                // Otherwise, let's just add it to the list.
+                flankGradleExtension.additionalTestApks.add(root.provider {
+                  "  test: ${this@test.outputFile}"
+                })
+              }
+              addedTestsForModule = true
+              return@test
+            }
+          }
+        }
+      }
+      pluginManager.withPlugin("com.android.library") {
+        val library = extensions.getByType<LibraryExtension>()
+        library.testVariants.configureEach {
+          outputs.configureEach {
+            flankGradleExtension.additionalTestApks.add(root.provider {
+              "- test: $outputFile"
+            })
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/com.osacky.fulladle.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/com.osacky.fulladle.properties
new file mode 100644
index 0000000..5b4d2a1
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/com.osacky.fulladle.properties
@@ -0,0 +1,1 @@
+implementation-class=com.osacky.flank.gradle.FulladlePlugin
diff --git a/docs/changelog.md b/docs/changelog.md
index 81616c5..8eebabb 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,10 +1,11 @@
 # Changelog
 
-## 0.9.5
+## 0.10.0
 
 * Allow for debugging using [--dump-shards](/fladle/faq/#debugging)
 * Fix naming for variant discovery of apk and instrumentation apk. Instead of chocolate-debug, variant must now be set as chocolateDebug.
 * Update [Flank to 20.05.2](https://github.com/Flank/flank/releases/tag/v20.05.2).
+* [Fulladle Preview](/fladle/multi-module-testing)
 
 !!! Warning "Breaking API Change"
     [additionalTestApks](/fladle/configuration/#additionaltestapks) now uses ListProperty instead of the previous Map. This is to allow for lazy configuration of the provided files.
diff --git a/docs/configuration.md b/docs/configuration.md
index e94a069..0053248 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -62,7 +62,7 @@ fladle {
         [ "model": "Nexus5", "version": "23" ]
     ]
     projectId("flank-gradle")
-    flankVersion = "20.05.2"
+    flankVersion = "{{ fladle.flank_version }}"
     debugApk = "$buildDir/outputs/apk/debug/sample-debug.apk"
     instrumentationApk = "$buildDir/outputs/apk/androidTest/debug/sample-debug-androidTest.apk"
     additionalTestApks = [
@@ -128,9 +128,29 @@ The projectId is a unique identifier which can be found in the project's URL: `h
 This is automatically discovered based on the service credential by default.
 
 ### flankVersion
-`flankVersion = "flank_snapshot"` to specify a Flank snapshot.
+Need a different Flank version? Specify it with `flankVersion`.
 
-`flankVersion = "20.05.2"` to specify a specific Flank version.
+To use a snapshot:
+=== "Groovy"
+    ``` groovy 
+    flankVersion = "flank_snapshot"`
+    ```
+=== "Kotlin"
+    ``` kotlin
+    flankVersion.set("flank_snapshot")
+    ```
+
+Need more than 50 shards? Use Flank `8.1.0`.
+
+To use a different version:
+=== "Groovy"
+    ``` groovy 
+    flankVersion = "{{ fladle.flank_version }}"
+    ```
+=== "Kotlin"
+    ``` kotlin
+    flankVersion.set("{{ fladle.flank_version }}")
+    ```
 
 ### flankCoordinates
 `flankCoordinates = "com.github.flank:flank"` to specify custom flank coordinates.
diff --git a/docs/faq.md b/docs/faq.md
index b965bc8..39c2e6d 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -22,7 +22,7 @@ No signature of method: flank_4vvjv7w3oopge32w1tl9cs6e4.fladle() is applicable f
 			Possible solutions: file(java.lang.Object), find(), findAll(), file(java.lang.Object, org.gradle.api.PathValidation), files([Ljava.lang.Object;), findAll(groovy.lang.Closure)
 ```
 
-If you receive a similar error, please check [configuration](/configuration#sample-configuration) for a sample configuration.
+If you receive a similar error, please check [configuration](configuration/#sample-configuration) for a sample configuration.
 
 ## Debugging
 `./gradlew runFlank -PdumpShards` Will dump shards and exit the process without running the tests.
diff --git a/docs/quick-start.md b/docs/quick-start.md
index 2e8081a..06e41d3 100644
--- a/docs/quick-start.md
+++ b/docs/quick-start.md
@@ -57,7 +57,19 @@ Using Fladle takes 3 steps:
     !!! Warning
         If using buildFlavors or testing against a non default variant, [variant must also configured](/fladle/configuration#variant)
 
-3. Run the Flank Gradle task.
+3. Run your tests!
+
+    First assemble your debug apk and test apk.
+    ``` bash
+    ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest
+    ```
+   
+    !!! note
+        When using flavors, make sure to assemble your buildVariants.
+        
+        `./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest`
+
+    Run Flank!
     ``` bash
     ./gradlew runFlank
     ```
diff --git a/mkdocs.yml b/mkdocs.yml
index 330e4d3..4aa9be7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -6,6 +6,7 @@ extra:
   fladle:
     current_release: '0.9.4'
     next_release: '0.9.5'
+    flank_version: '20.05.2'
 
 site_name: Fladle
 site_url: https://runningcode.github.io/fladle/
@@ -20,6 +21,7 @@ nav:
   - Configuration: configuration.md
   - Changelog: changelog.md
   - Results: results.md
+  - Multi Module Testing: multi-module-testing.md
   - FAQ: faq.md
   - Testing Snapshots: snapshots.md
   - Releasing: releasing.md
diff --git a/sample-android-library/build.gradle.kts b/sample-android-library/build.gradle.kts
new file mode 100644
index 0000000..6cb3e50
--- /dev/null
+++ b/sample-android-library/build.gradle.kts
@@ -0,0 +1,28 @@
+plugins {
+  id("com.android.library")
+  kotlin("android")
+}
+
+android {
+  compileSdkVersion(28)
+  defaultConfig {
+    minSdkVersion(23)
+    targetSdkVersion(28)
+    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+  }
+  testOptions {
+    execution = "ANDROIDX_TEST_ORCHESTRATOR"
+  }
+}
+
+dependencies {
+  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
+  implementation("androidx.appcompat:appcompat:1.1.0")
+  implementation("android.arch.navigation:navigation-fragment-ktx:1.0.0")
+  implementation("androidx.constraintlayout:constraintlayout:1.1.3")
+  testImplementation("junit:junit:4.13")
+  androidTestImplementation("androidx.test:runner:1.2.0")
+  androidTestImplementation("androidx.test:rules:1.2.0")
+  androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
+}
+
diff --git a/sample-android-library/src/main/AndroidManifest.xml b/sample-android-library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8487ce0
--- /dev/null
+++ b/sample-android-library/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="com.osacky.flank.gradle.sample.library"/>
diff --git a/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt
new file mode 100644
index 0000000..c3c528b
--- /dev/null
+++ b/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt
@@ -0,0 +1,11 @@
+package com.osacky.flank.gradle.sample
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+
+class MainActivity : AppCompatActivity() {
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index 9020fed..3991f99 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,3 +5,4 @@ plugins {
 include ':sample'
 include ':sample-kotlin'
 include ':sample-flavors-kotlin'
+include ':sample-android-library'


Test patch:
diff --git a/buildSrc/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt b/buildSrc/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt
new file mode 100644
index 0000000..3fca571
--- /dev/null
+++ b/buildSrc/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt
@@ -0,0 +1,32 @@
+package com.osacky.flank.gradle.integration
+
+import com.google.common.truth.Truth.assertThat
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class FulladlePluginIntegrationTest {
+  @get:Rule
+  var testProjectRoot = TemporaryFolder()
+
+  fun writeBuildGradle(build: String) {
+    val file = testProjectRoot.newFile("build.gradle")
+    file.writeText(build)
+  }
+
+  @Test
+  fun fladleSmokeTest() {
+    writeBuildGradle(
+        """plugins {
+             |  id "com.osacky.fulladle"
+             |}""".trimMargin()
+    )
+    val result = GradleRunner.create()
+        .withProjectDir(testProjectRoot.root)
+        .withPluginClasspath()
+        .withGradleVersion("6.0")
+        .build()
+    assertThat(result.output).contains("SUCCESS")
+  }
+}
diff --git a/docs/multi-module-testing.md b/docs/multi-module-testing.md
new file mode 100644
index 0000000..29f0dca
--- /dev/null
+++ b/docs/multi-module-testing.md
@@ -0,0 +1,70 @@
+# Multi-module testing
+
+Multi module testing can be done by manually specifying [additionalTestApks](/fladle/configuration/#additionaltestapks) or applying the Fulladle plugin to automacally gather all the additional test apks.
+
+## Fulladle Plugin
+
+!!! Warning
+    Fulladle is still under development and is not guaranteed to work and may change at any moment.
+
+1. Apply the Fulladle plugin at the root of the project.
+
+    === "Groovy"
+        ``` groovy
+        plugins {
+            id 'com.osacky.fulladle' version '{{ fladle.current_release }}'
+        }
+        ```
+    === "Kotlin"
+        ``` kotlin
+        plugins {
+            id 'com.osacky.fulladle' version '{{ fladle.current_release }}'
+        }
+        ```
+
+2. Configure the Fladle extension.
+
+    ===! "Groovy"
+        ``` groovy
+        fladle {
+            serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json")
+        }
+        ```
+    === "Kotlin"
+        ``` kotlin
+        fladle {
+            serviceAccountCredentials.set(project.layout.projectDirectory.file("flank-gradle-service-account.json"))
+        }
+        ```
+        
+    !!! Warning
+        If using buildFlavors or testing against a non default variant, Fulladle might not test the variant you are expecting.
+
+3. Run the tests.   
+    First assemble all your debug apks and test apks.
+    ``` bash
+    ./gradlew assembleDebug assembleDebugAndroidTest
+    ```
+   
+    !!! note
+        When using flavors, make sure to assemble your buildVariants as well.
+        
+        `./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest` 
+
+    Run Flank!
+    ``` bash
+    ./gradlew runFlank
+    ```
+
+
+## Troubleshooting
+Fulladle isn't ready yet, but we'd love feedback. Please join us in the [Firebase Community Slack](https://firebase.community/) with any feedback you may have.
+You can also file [Fladle Github issues](https://github.com/runningcode/fladle/issues).
+
+When filing a bug report, please include the Flank version number, the Fladle version number and the output of the following:
+
+`./gradlew printYml`
+
+`./gradlew runFlank -PdumpShards`
+
+
diff --git a/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..0cdcf28
--- /dev/null
+++ b/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt
@@ -0,0 +1,20 @@
+package com.osacky.flank.gradle.sample
+
+
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+
+  @Test
+  fun seeView() {
+      assert(true)
+  }
+
+  @Test
+  fun runAndFail() {
+    throw RuntimeException("Test failed")
+  }
+}