[#433] Use emulator.wtf for tests on CI (#428)

This should speed up our builds and reduce flakiness.

Followup items:
 - Enable coverage
 - Collect screenshots
 - Fix target API on sdk-ext-lib #430
This commit is contained in:
Carter Jernigan 2022-05-17 10:06:56 -04:00 committed by GitHub
parent 62f976b1d8
commit a8be47bf89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 182 additions and 27 deletions

View File

@ -41,4 +41,4 @@ runs:
if: steps.gradle-dependency-cache.outputs.cache-hit != 'true'
shell: bash
run: |
./gradlew resolveAll
./gradlew dependencies resolveAll

View File

@ -1,4 +1,5 @@
# Expected secrets
# EMULATOR_WTF_API_KEY - Optional API key for emulator.wtf
# FIREBASE_TEST_LAB_PROJECT - Firebase Test Lab project name
# FIREBASE_TEST_LAB_SERVICE_ACCOUNT - Email address of Firebase Test Lab service account
# FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER - Workload identity provider to generate temporary service account key
@ -47,12 +48,12 @@ jobs:
timeout-minutes: 12
uses: ./.github/actions/setup
check_secrets:
check_firebase_secrets:
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_secrets.outputs.defined }}
has-secrets: ${{ steps.check_firebase_secrets.outputs.defined }}
steps:
- id: check_secrets
- id: check_firebase_secrets
env:
FIREBASE_TEST_LAB_PROJECT: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
FIREBASE_TEST_LAB_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }}
@ -60,6 +61,17 @@ jobs:
if: "${{ env.FIREBASE_TEST_LAB_PROJECT != '' && env.FIREBASE_TEST_LAB_SERVICE_ACCOUNT != '' && env.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER != '' }}"
run: echo "::set-output name=defined::true"
check_emulator_wtf_secrets:
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_emulator_wtf_secrets.outputs.defined }}
steps:
- id: check_emulator_wtf_secrets
env:
EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
if: "${{ env.EMULATOR_WTF_API_KEY != '' }}"
run: echo "::set-output name=defined::true"
static_analysis_detekt:
needs: prime_cache
runs-on: ubuntu-latest
@ -202,9 +214,10 @@ jobs:
name: Test Kotlin modules results
path: ~/artifacts
test_android_modules:
if: needs.check_secrets.outputs.has-secrets == 'true'
needs: [prime_cache, check_secrets]
# Emulator.wtf is preferred if it has an API key.
test_android_modules_ftl:
if: needs.check_firebase_secrets.outputs.has-secrets == 'true' && needs.check_emulator_wtf_secrets.outputs.has-secrets == 'false'
needs: [prime_cache, check_firebase_secrets, check_emulator_wtf_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
@ -255,7 +268,45 @@ jobs:
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
timeout-minutes: 1
with:
name: Test Android modules results
name: Test Android modules with FTL results
path: ~/artifacts
test_android_modules_wtf:
if: needs.check_emulator_wtf_secrets.outputs.has-secrets == 'true'
needs: [ prime_cache, check_emulator_wtf_secrets ]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Build and test
timeout-minutes: 25
env:
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
run: |
./gradlew testDebugWithEmulatorWtf
- name: Collect Artifacts
if: ${{ always() }}
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
TEST_RESULTS_ZIP_PATH: ${{ format('{0}/artifacts/test_results.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${TEST_RESULTS_ZIP_PATH} . -i \*/build/test-results/\*
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
timeout-minutes: 1
with:
name: Test Android modules with WTF results
path: ~/artifacts
release_build:
@ -307,8 +358,8 @@ jobs:
# Performs a button mash test on the release build of the app
# Note that we might need to help it get past the onboarding test with a script
test_robo:
if: needs.check_secrets.outputs.has-secrets == 'true'
needs: [release_build, check_secrets]
if: needs.check_firebase_secrets.outputs.has-secrets == 'true'
needs: [release_build, check_firebase_secrets]
runs-on: ubuntu-latest
permissions:
packages: read

View File

@ -5,6 +5,8 @@ plugins {
id("zcash.android-build-conventions")
id("com.github.triplet.play")
id("com.osacky.fladle")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
val packageName = "co.electriccoin.zcash"
@ -226,22 +228,22 @@ tasks.whenTaskAdded {
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_API = 23
val FIREBASE_TEST_LAB_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_API = 30
val FIREBASE_TEST_LAB_MAX_SDK = 30
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
if (firebaseTestLabKeyPath.isNotBlank()) {
val minSdkVersion = run {
val buildMinSdk =
project.properties["ANDROID_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_API).toString()
project.properties["ANDROID_APP_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_SDK).toString()
}
val targetSdkVersion = run {
val buildTargetSdk =
project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_API).toString()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_SDK).toString()
}
fladle {
@ -260,7 +262,7 @@ if (firebaseTestLabKeyPath.isNotBlank()) {
testTimeout.set("5m")
devices.addAll(
mapOf("model" to "Nexus6", "version" to minSdkVersion),
mapOf("model" to "Nexus6P", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)

View File

@ -27,6 +27,7 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProperties.getProperty("KOTLIN_VERSION")}")
implementation("com.android.tools.build:gradle:${rootProperties.getProperty("ANDROID_GRADLE_PLUGIN_VERSION")}")
implementation("wtf.emulator:gradle-plugin:${rootProperties.getProperty("EMULATOR_WTF_GRADLE_PLUGIN_VERSION")}")
}
// A slightly gross way to use the root gradle.properties as the single source of truth for version numbers

View File

@ -72,6 +72,7 @@ com.squareup:javawriter:2.5.0=compileClasspath,runtimeClasspath
com.sun.activation:javax.activation:1.2.0=runtimeClasspath
com.sun.istack:istack-commons-runtime:3.0.8=runtimeClasspath
com.sun.xml.fastinfoset:FastInfoset:1.2.16=runtimeClasspath
com.vdurmont:semver4j:3.1.0=runtimeClasspath
commons-codec:commons-codec:1.11=runtimeClasspath
commons-io:commons-io:2.4=runtimeClasspath
commons-logging:commons-logging:1.2=runtimeClasspath
@ -175,6 +176,7 @@ org.ow2.asm:asm-util:9.1=runtimeClasspath
org.ow2.asm:asm:9.1=compileClasspath,runtimeClasspath
org.slf4j:slf4j-api:1.7.30=runtimeClasspath
org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=runtimeClasspath
wtf.emulator:gradle-plugin:0.0.10=compileClasspath,runtimeClasspath
xerces:xercesImpl:2.12.0=runtimeClasspath
xml-apis:xml-apis:1.4.01=runtimeClasspath
empty=annotationProcessor

View File

@ -9,7 +9,7 @@ pluginManager.withPlugin("com.android.application") {
configureBaseExtension()
defaultConfig {
minSdk = project.property("ANDROID_MIN_SDK_VERSION").toString().toInt()
minSdk = project.property("ANDROID_APP_MIN_SDK_VERSION").toString().toInt()
targetSdk = project.property("ANDROID_TARGET_SDK_VERSION").toString().toInt()
// en_XA and ar_XB are pseudolocales for debugging.
@ -32,7 +32,7 @@ pluginManager.withPlugin("com.android.library") {
configureBaseExtension()
defaultConfig {
minSdk = project.property("ANDROID_MIN_SDK_VERSION").toString().toInt()
minSdk = project.property("ANDROID_LIB_MIN_SDK_VERSION").toString().toInt()
targetSdk = project.property("ANDROID_TARGET_SDK_VERSION").toString().toInt()
// The last two are for support of pseudolocales in debug builds.

View File

@ -0,0 +1,33 @@
// Emulator WTF has min and max values that might differ from our project's
// These are determined by `ew-cli --models`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MAX_SDK = 31
pluginManager.withPlugin("wtf.emulator.gradle") {
project.the<wtf.emulator.EwExtension>().apply {
val tokenString = project.properties["ZCASH_EMULATOR_WTF_API_KEY"].toString()
if (tokenString.isNotEmpty()) {
token.set(tokenString)
}
val libraryMinSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_LIB_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(EMULATOR_WTF_MIN_SDK).toString()
}
val targetSdkVersion = run {
val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(EMULATOR_WTF_MAX_SDK).toString()
}
devices.set(
listOf(
mapOf("model" to "Pixel2", "version" to libraryMinSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)
)
}
}

View File

@ -53,19 +53,19 @@ fun isNonStable(version: String): Boolean {
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_API = 23
val FIREBASE_TEST_LAB_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_API = 30
val FIREBASE_TEST_LAB_MAX_SDK = 30
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
if (firebaseTestLabKeyPath.isNotBlank()) {
val minSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_API).toString()
val buildMinSdk = project.properties["ANDROID_LIB_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_SDK).toString()
}
val targetSdkVersion = run {
val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_API).toString()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_SDK).toString()
}
fladle {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))

View File

@ -77,6 +77,7 @@ com.sun.activation:javax.activation:1.2.0=classpath
com.sun.istack:istack-commons-runtime:3.0.8=classpath
com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath
com.thoughtworks.xstream:xstream:1.4.17=classpath
com.vdurmont:semver4j:3.1.0=classpath
commons-codec:commons-codec:1.11=classpath
commons-io:commons-io:2.4=classpath
commons-logging:commons-logging:1.2=classpath
@ -171,6 +172,7 @@ org.ow2.asm:asm-util:9.1=classpath
org.ow2.asm:asm:9.1=classpath
org.slf4j:slf4j-api:1.7.30=classpath
org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath
wtf.emulator:gradle-plugin:0.0.10=classpath
xerces:xercesImpl:2.12.0=classpath
xml-apis:xml-apis:1.4.01=classpath
xmlpull:xmlpull:1.1.3.1=classpath

View File

@ -13,11 +13,14 @@ The secrets passed to GitHub Actions then map to Gradle properties set up within
To enhance security, [OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform) is used to generate temporary access tokens for each build.
### Pull request
* `EMULATOR_WTF_API_KEY` - API key for [Emulator.wtf](https://emulator.wtf)
* `FIREBASE_TEST_LAB_PROJECT` - Firebase Test Lab project name.
* `FIREBASE_TEST_LAB_SERVICE_ACCOUNT` - Email address of Firebase Test Lab service account.
* `FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER` - Workload identity provider to generate temporary service account key.
To obtain the values for these, you'll need to enable the necessary Google Cloud APIs to enable automated access to Firebase Test Lab.
The Pull Request workflow supports testing of the app and libraries with both Emulator.wtf and Firebase Test Lab. By default Emulator.wtf is used for library instrumentation tests, while Firebase Test Lab is used for a robo test.
To configure Firebase Test Lab, you'll need to enable the necessary Google Cloud APIs to enable automated access to Firebase Test Lab.
* Configure Firebase Test Lab. Google has [documentation for Jenkins](https://firebase.google.com/docs/test-lab/android/continuous). Although we're using GitHub Actions, the initial requirements are the same.
* Configure [workload identity federation](https://github.com/google-github-actions/auth#setting-up-workload-identity-federation)

View File

@ -52,11 +52,17 @@ ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH=
# Deploy commits the build on Google Play, creating a new release
ZCASH_GOOGLE_PLAY_DEPLOY_MODE=build
ZCASH_EMULATOR_WTF_API_KEY=
# Toggles between using the SDK Maven artifact versus an included build
IS_SDK_INCLUDED_BUILD=false
# Versions
ANDROID_MIN_SDK_VERSION=24
# A lower version on the libraries helps to ensure some degree of backwards compatiblity, for the project
# as a whole. But a higher version on the app ensures that we aren't directly supporting users
# with old devices.
ANDROID_LIB_MIN_SDK_VERSION=23
ANDROID_APP_MIN_SDK_VERSION=27
ANDROID_TARGET_SDK_VERSION=31
ANDROID_COMPILE_SDK_VERSION=31
@ -64,11 +70,12 @@ ANDROID_NDK_VERSION=23.0.7599858
ANDROID_GRADLE_PLUGIN_VERSION=7.2.0
DETEKT_VERSION=1.19.0
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.10
FLANK_VERSION=21.09.0
FULLADLE_VERSION=0.17.3
GRADLE_VERSIONS_PLUGIN_VERSION=0.39.0
KTLINT_VERSION=0.45.2
JGIT_VERSION=6.1.0.202203080745-r
KTLINT_VERSION=0.45.2
PLAY_PUBLISHER_PLUGIN_VERSION=3.7.0
ANDROIDX_ACTIVITY_VERSION=1.4.0

View File

@ -3,6 +3,8 @@ plugins {
kotlin("android")
id("kotlin-parcelize")
id("zcash.android-build-conventions")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
// Force orchestrator to be used for this module, because we need the preference files

View File

@ -1,7 +1,9 @@
package co.electriccoin.zcash.preference
import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import co.electriccoin.zcash.preference.test.fixture.StringDefaultPreferenceFixture
import kotlinx.coroutines.runBlocking
@ -66,6 +68,7 @@ class EncryptedPreferenceProviderTest {
// e.g. the directory path and the fact the preferences are stored as XML
@Test
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
fun verify_no_plaintext() = runBlocking {
val expectedValue = StringDefaultPreferenceFixture.DEFAULT_VALUE + "extra"

View File

@ -2,6 +2,8 @@ plugins {
id("com.android.library")
kotlin("android")
id("zcash.android-build-conventions")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
android {
@ -26,3 +28,25 @@ dependencies {
}
}
}
// This block can eventually go away; it exists to override the max SDK version to avoid a JNI
// crash https://github.com/zcash/secant-android-wallet/issues/430
emulatorwtf {
// Emulator WTF has min and max values that might differ from our project's
// These are determined by `ew-cli --models`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MIN_API = 23
val minSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_LIB_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(EMULATOR_WTF_MIN_API).toString()
}
devices.set(
listOf(
mapOf("model" to "Pixel2", "version" to minSdkVersion),
@Suppress("MagicNumber")
mapOf("model" to "Pixel2", "version" to 30)
)
)
}

View File

@ -2,6 +2,8 @@ plugins {
id("com.android.library")
kotlin("android")
id("zcash.android-build-conventions")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
android {

View File

@ -4,6 +4,13 @@ pluginManagement {
repositories {
val isRepoRestrictionEnabled = true
mavenCentral {
if (isRepoRestrictionEnabled) {
content {
includeGroup("wtf.emulator")
}
}
}
google {
if (isRepoRestrictionEnabled) {
content {
@ -32,6 +39,7 @@ pluginManagement {
plugins {
val androidGradlePluginVersion = extra["ANDROID_GRADLE_PLUGIN_VERSION"].toString()
val emulatorWtfGradlePluginVersion = extra["EMULATOR_WTF_GRADLE_PLUGIN_VERSION"].toString()
val detektVersion = extra["DETEKT_VERSION"].toString()
val fulladleVersion = extra["FULLADLE_VERSION"].toString()
val gradleVersionsPluginVersion = extra["GRADLE_VERSIONS_PLUGIN_VERSION"].toString()
@ -44,6 +52,7 @@ pluginManagement {
id("com.github.triplet.play") version (playPublisherVersion) apply (false)
id("com.osacky.fulladle") version (fulladleVersion) apply (false)
id("io.gitlab.arturbosch.detekt") version (detektVersion) apply (false)
id("wtf.emulator.gradle") version (emulatorWtfGradlePluginVersion) apply (false)
kotlin("android") version (kotlinVersion) apply (false)
kotlin("jvm") version (kotlinVersion)
kotlin("multiplatform") version (kotlinVersion)
@ -51,6 +60,8 @@ pluginManagement {
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
@Suppress("UnstableApiUsage")
repositories {
val isRepoRestrictionEnabled = true
@ -58,8 +69,8 @@ dependencyResolutionManagement {
google {
if (isRepoRestrictionEnabled) {
content {
includeGroup("android.arch.lifecycle")
includeGroup("android.arch.core")
includeGroup("android.arch.lifecycle")
includeGroup("com.google.android.material")
includeGroup("com.google.testing.platform")
includeGroup("com.google.android.play")
@ -73,6 +84,7 @@ dependencyResolutionManagement {
content {
excludeGroup("android.arch.lifecycle")
excludeGroup("android.arch.core")
excludeGroup("wtf.emulator")
excludeGroup("com.google.android.material")
excludeGroup("com.google.android.play")
excludeGroupByRegex("androidx.*")
@ -87,6 +99,13 @@ dependencyResolutionManagement {
}
}
}
maven("https://maven.emulator.wtf/releases/") {
if (isRepoRestrictionEnabled) {
content {
includeGroup("wtf.emulator")
}
}
}
}
@Suppress("UnstableApiUsage", "MaxLineLength")

View File

@ -3,6 +3,8 @@ plugins {
kotlin("android")
id("kotlin-parcelize")
id("zcash.android-build-conventions")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
android {

View File

@ -3,6 +3,8 @@ plugins {
kotlin("android")
id("kotlin-parcelize")
id("zcash.android-build-conventions")
id("wtf.emulator.gradle")
id("zcash.emulator-wtf-conventions")
}
android {