diff --git a/test_app/bash/generate_duplicated_names_apks.sh b/test_app/bash/generate_duplicated_names_apks.sh
new file mode 100755
index 0000000000..f3136e7846
--- /dev/null
+++ b/test_app/bash/generate_duplicated_names_apks.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+DIR=`dirname "$BASH_SOURCE"`
+TEST_APP_DIR="$DIR/.."
+
+"$TEST_APP_DIR/gradlew" -p "$TEST_APP_DIR" \
+ app:assemble \
+ dir0:testModule:assembleAndroidTest \
+ dir1:testModule:assembleAndroidTest \
+ dir2:testModule:assembleAndroidTest \
+ dir3:testModule:assembleAndroidTest \
+
+APKS_DIR="$TEST_APP_DIR/../test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names"
+for INDEX in 0 1 2 3
+do
+ DIR_NAME="dir$INDEX"
+ APK_DIR="$APKS_DIR/$DIR_NAME/"
+ mkdir -p "$APK_DIR"
+ cp $TEST_APP_DIR/$DIR_NAME/testModule/build/outputs/apk/**/debug/*.apk "$APK_DIR"
+done
+
+cp "$TEST_APP_DIR/app/build/outputs/apk/singleSuccess/debug/app-single-success-debug.apk" "$APKS_DIR/app-debug.apk"
+
diff --git a/test_app/dir0/testModule/.gitignore b/test_app/dir0/testModule/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/test_app/dir0/testModule/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/test_app/dir0/testModule/build.gradle b/test_app/dir0/testModule/build.gradle
new file mode 100644
index 0000000000..b69eb8f565
--- /dev/null
+++ b/test_app/dir0/testModule/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
+ implementation 'androidx.core:core-ktx:1.5.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+ // Espresso.
+ // https://developer.android.com/jetpack/androidx/releases/test
+ androidTestUtil 'androidx.test:orchestrator:1.2.0'
+ androidTestImplementation 'pl.pragmatists:JUnitParams:1.1.1'
+ androidTestImplementation("androidx.test:runner:1.3.0-rc01")
+ androidTestImplementation("androidx.test.ext:junit:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:truth:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-concurrent:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-net:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-accessibility:3.3.0-rc01")
+ androidTestImplementation("androidx.test:rules:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-idling-resource:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-intents:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-web:3.3.0-rc01")
+}
diff --git a/test_app/dir0/testModule/src/main/AndroidManifest.xml b/test_app/dir0/testModule/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..df2ab4e63e
--- /dev/null
+++ b/test_app/dir0/testModule/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/test_app/dir1/testModule/.gitignore b/test_app/dir1/testModule/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/test_app/dir1/testModule/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/test_app/dir1/testModule/build.gradle b/test_app/dir1/testModule/build.gradle
new file mode 100644
index 0000000000..b69eb8f565
--- /dev/null
+++ b/test_app/dir1/testModule/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
+ implementation 'androidx.core:core-ktx:1.5.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+ // Espresso.
+ // https://developer.android.com/jetpack/androidx/releases/test
+ androidTestUtil 'androidx.test:orchestrator:1.2.0'
+ androidTestImplementation 'pl.pragmatists:JUnitParams:1.1.1'
+ androidTestImplementation("androidx.test:runner:1.3.0-rc01")
+ androidTestImplementation("androidx.test.ext:junit:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:truth:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-concurrent:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-net:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-accessibility:3.3.0-rc01")
+ androidTestImplementation("androidx.test:rules:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-idling-resource:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-intents:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-web:3.3.0-rc01")
+}
diff --git a/test_app/dir1/testModule/src/androidTest/java/com/example/test1/DuplicatedApkNameTests.kt b/test_app/dir1/testModule/src/androidTest/java/com/example/test1/DuplicatedApkNameTests.kt
new file mode 100644
index 0000000000..d92460850f
--- /dev/null
+++ b/test_app/dir1/testModule/src/androidTest/java/com/example/test1/DuplicatedApkNameTests.kt
@@ -0,0 +1,18 @@
+package com.example.test1
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DuplicatedApkNameTests {
+
+ @Test
+ fun test0() = Unit
+
+ @Test
+ fun test1() = Unit
+
+ @Test
+ fun test2() = Unit
+}
diff --git a/test_app/dir1/testModule/src/main/AndroidManifest.xml b/test_app/dir1/testModule/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..df2ab4e63e
--- /dev/null
+++ b/test_app/dir1/testModule/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/test_app/dir2/testModule/.gitignore b/test_app/dir2/testModule/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/test_app/dir2/testModule/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/test_app/dir2/testModule/build.gradle b/test_app/dir2/testModule/build.gradle
new file mode 100644
index 0000000000..b69eb8f565
--- /dev/null
+++ b/test_app/dir2/testModule/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
+ implementation 'androidx.core:core-ktx:1.5.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+ // Espresso.
+ // https://developer.android.com/jetpack/androidx/releases/test
+ androidTestUtil 'androidx.test:orchestrator:1.2.0'
+ androidTestImplementation 'pl.pragmatists:JUnitParams:1.1.1'
+ androidTestImplementation("androidx.test:runner:1.3.0-rc01")
+ androidTestImplementation("androidx.test.ext:junit:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:truth:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-concurrent:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-net:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-accessibility:3.3.0-rc01")
+ androidTestImplementation("androidx.test:rules:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-idling-resource:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-intents:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-web:3.3.0-rc01")
+}
diff --git a/test_app/dir2/testModule/src/androidTest/java/com/example/test2/DuplicatedApkNameTests.kt b/test_app/dir2/testModule/src/androidTest/java/com/example/test2/DuplicatedApkNameTests.kt
new file mode 100644
index 0000000000..62a3bc22a7
--- /dev/null
+++ b/test_app/dir2/testModule/src/androidTest/java/com/example/test2/DuplicatedApkNameTests.kt
@@ -0,0 +1,18 @@
+package com.example.test2
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DuplicatedApkNameTests {
+
+ @Test
+ fun test0() = Unit
+
+ @Test
+ fun test1() = Unit
+
+ @Test
+ fun test2() = Unit
+}
diff --git a/test_app/dir2/testModule/src/main/AndroidManifest.xml b/test_app/dir2/testModule/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..df2ab4e63e
--- /dev/null
+++ b/test_app/dir2/testModule/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/test_app/dir3/testModule/.gitignore b/test_app/dir3/testModule/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/test_app/dir3/testModule/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/test_app/dir3/testModule/build.gradle b/test_app/dir3/testModule/build.gradle
new file mode 100644
index 0000000000..b69eb8f565
--- /dev/null
+++ b/test_app/dir3/testModule/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
+ implementation 'androidx.core:core-ktx:1.5.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+ // Espresso.
+ // https://developer.android.com/jetpack/androidx/releases/test
+ androidTestUtil 'androidx.test:orchestrator:1.2.0'
+ androidTestImplementation 'pl.pragmatists:JUnitParams:1.1.1'
+ androidTestImplementation("androidx.test:runner:1.3.0-rc01")
+ androidTestImplementation("androidx.test.ext:junit:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2-rc01")
+ androidTestImplementation("androidx.test.ext:truth:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-concurrent:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso.idling:idling-net:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-accessibility:3.3.0-rc01")
+ androidTestImplementation("androidx.test:rules:1.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-idling-resource:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-intents:3.3.0-rc01")
+ androidTestImplementation("androidx.test.espresso:espresso-web:3.3.0-rc01")
+}
diff --git a/test_app/dir3/testModule/src/androidTest/java/com/example/test3/DuplicatedApkNameTests.kt b/test_app/dir3/testModule/src/androidTest/java/com/example/test3/DuplicatedApkNameTests.kt
new file mode 100644
index 0000000000..df571ab9be
--- /dev/null
+++ b/test_app/dir3/testModule/src/androidTest/java/com/example/test3/DuplicatedApkNameTests.kt
@@ -0,0 +1,18 @@
+package com.example.test3
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DuplicatedApkNameTests {
+
+ @Test
+ fun test0() = Unit
+
+ @Test
+ fun test1() = Unit
+
+ @Test
+ fun test2() = Unit
+}
diff --git a/test_app/dir3/testModule/src/main/AndroidManifest.xml b/test_app/dir3/testModule/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..df2ab4e63e
--- /dev/null
+++ b/test_app/dir3/testModule/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/test_app/settings.gradle b/test_app/settings.gradle
index e7b4def49c..7d7a016b41 100644
--- a/test_app/settings.gradle
+++ b/test_app/settings.gradle
@@ -1 +1,5 @@
-include ':app'
+include ':app',
+ ':dir0:testModule',
+ ':dir1:testModule',
+ ':dir2:testModule',
+ ':dir3:testModule'
diff --git a/test_runner/src/test/kotlin/Debug.kt b/test_runner/src/test/kotlin/Debug.kt
index 1d6d65256e..b065435f4c 100644
--- a/test_runner/src/test/kotlin/Debug.kt
+++ b/test_runner/src/test/kotlin/Debug.kt
@@ -11,7 +11,7 @@ fun main() {
val projectId = System.getenv("GOOGLE_CLOUD_PROJECT")
?: "YOUR PROJECT ID"
val quantity = "multiple"
- val type = "success"
+ val type = "duplicated"
// Bugsnag keeps the process alive so we must call exitProcess
// https://github.com/bugsnag/bugsnag-java/issues/151
@@ -21,7 +21,7 @@ fun main() {
"firebase", "test",
"android", "run",
// "--dry",
- "--dump-shards",
+// "--dump-shards",
"--output-style=single",
"--full-junit-result",
"-c=src/test/kotlin/ftl/fixtures/test_app_cases/flank-$quantity-$type.yml",
diff --git a/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-multiple-duplicated.yml b/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-multiple-duplicated.yml
new file mode 100644
index 0000000000..43788b6968
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-multiple-duplicated.yml
@@ -0,0 +1,11 @@
+gcloud:
+ app: ./src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names/app-debug.apk
+ test: ./src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names/dir0/testModule-debug-androidTest.apk
+ use-orchestrator: false
+flank:
+ disable-sharding: false
+ max-test-shards: 3
+ additional-app-test-apks:
+ - test: ./src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names/dir1/testModule-debug-androidTest.apk
+ - test: ./src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names/dir2/testModule-debug-androidTest.apk
+ - test: ./src/test/kotlin/ftl/fixtures/tmp/apk/duplicated_names/dir3/testModule-debug-androidTest.apk