truetrue
diff --git a/.idea/runConfigurations/ktlint.xml b/.idea/runConfigurations/ktlintFormat.xml
similarity index 70%
rename from .idea/runConfigurations/ktlint.xml
rename to .idea/runConfigurations/ktlintFormat.xml
index 8fcf9918..dc632087 100644
--- a/.idea/runConfigurations/ktlint.xml
+++ b/.idea/runConfigurations/ktlintFormat.xml
@@ -1,19 +1,17 @@
-
+
-
+
-
-
-
+
-
+
truetrue
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4d6a8807..f68fbc30 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -101,7 +101,6 @@ android {
listOf(
"**/*.kotlin_metadata",
".readme",
- "build-data.properties",
"META-INF/*.kotlin_module",
"META-INF/android.arch**",
"META-INF/androidx**",
@@ -111,6 +110,7 @@ android {
"META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor",
"META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar",
"META-INF/services/org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages\$Extension",
+ "build-data.properties",
"firebase-**.properties",
"kotlin/**",
"play-services-**.properties",
@@ -225,49 +225,66 @@ 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_SDK = 23
+fladle {
+ // 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_SDK = 23
-@Suppress("MagicNumber", "PropertyName", "VariableNaming")
-val FIREBASE_TEST_LAB_MAX_SDK = 30
+ @Suppress("MagicNumber", "PropertyName", "VariableNaming")
+ 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_APP_MIN_SDK_VERSION"].toString().toInt()
+ val buildMinSdk = 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()
+ val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_SDK).toString()
}
- fladle {
+ val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
+ val firebaseProject = project.properties["ZCASH_FIREBASE_TEST_LAB_PROJECT"].toString()
+
+ if (firebaseTestLabKeyPath.isNotEmpty()) {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))
+ } else if (firebaseProject.isNotEmpty()) {
+ projectId.set(firebaseProject)
+ }
- configs {
- create("sanityConfig") {
- clearPropertiesForSanityRobo()
+ configs {
+ create("sanityConfig") {
+ clearPropertiesForSanityRobo()
- debugApk.set(
- project.provider {
- "${buildDir}/outputs/universal_apk/zcashmainnetRelease/app-zcashmainnet-release-universal.apk"
- }
- )
+ debugApk.set(
+ project.provider {
+ "${buildDir}/outputs/universal_apk/zcashmainnetRelease/app-zcashmainnet-release-universal.apk"
+ }
+ )
- testTimeout.set("5m")
-
- devices.addAll(
- mapOf("model" to "Nexus6P", "version" to minSdkVersion),
- mapOf("model" to "Pixel2", "version" to targetSdkVersion)
- )
+ testTimeout.set("5m")
- flankVersion.set(libs.versions.flank.get())
- }
+ devices.addAll(
+ mapOf("model" to "Pixel2", "version" to minSdkVersion),
+ mapOf("model" to "Pixel2", "version" to targetSdkVersion)
+ )
+
+ flankVersion.set(libs.versions.flank.get())
}
}
}
+
+emulatorwtf {
+ // This path needs to be coordinated with the implementation in the app module's tests
+ directoriesToPull.set(listOf("/sdcard/Pictures/zcash_screenshots"))
+
+ devices.set(
+ listOf(
+ // TODO [#285]: Our screenshot tests don't work on older devices
+ // mapOf("model" to "Pixel2", "version" to minSdkVersion),
+ // TODO [#430]: App won't run on API 31 Intel emulators
+ @Suppress("MagicNumber")
+ mapOf("model" to "Pixel2", "version" to 30)
+ )
+ )
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 9cc41d41..ed91815f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -50,47 +50,60 @@ fun isNonStable(version: String): Boolean {
return unstableKeywords.any { versionLowerCase.contains(it) }
}
-// 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_SDK = 23
-@Suppress("MagicNumber", "PropertyName", "VariableNaming")
-val FIREBASE_TEST_LAB_MAX_SDK = 30
+fladle {
+ // 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_SDK = 23
+
+ @Suppress("MagicNumber", "PropertyName", "VariableNaming")
+ 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_LIB_MIN_SDK_VERSION"].toString().toInt()
+ // Fladle will use the app APK as the additional APK, so we have to
+ // use the app's minSdkVersion here.
+ val buildMinSdk = 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_SDK).toString()
}
- fladle {
+
+ val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
+ val firebaseProject = project.properties["ZCASH_FIREBASE_TEST_LAB_PROJECT"].toString()
+
+ if (firebaseTestLabKeyPath.isNotEmpty()) {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))
- // TODO [#282]: Replace this with NexusLowRes once tests pass on larger screen sizes
- devices.addAll(
- mapOf("model" to "Nexus6", "version" to minSdkVersion),
- mapOf("model" to "Pixel2", "version" to targetSdkVersion)
- )
-
- @Suppress("MagicNumber")
- flakyTestAttempts.set(2)
-
- if (project.properties["IS_USE_TEST_ORCHESTRATOR"].toString().toBoolean()) {
- useOrchestrator.set(true)
- environmentVariables.set(mapOf("clearPackageData" to "true"))
- } else {
- useOrchestrator.set(false)
- }
-
- flankVersion.set(libs.versions.flank.get())
-
- filesToDownload.set(listOf(
- "*/matrix_*/*test_results_merged\\.xml"
- ))
+ } else if (firebaseProject.isNotEmpty()) {
+ projectId.set(firebaseProject)
}
+
+ devices.addAll(
+ mapOf("model" to "Pixel2", "version" to minSdkVersion),
+ mapOf("model" to "Pixel2", "version" to targetSdkVersion)
+ )
+
+ @Suppress("MagicNumber")
+ flakyTestAttempts.set(2)
+
+ // Always use orchestrator for Firebase Test Lab.
+ // Some submodules don't need it, but it is difficult to configure on a per-module basis from here
+ // since this configuration applies to all modules.
+ useOrchestrator.set(true)
+ environmentVariables.set(mapOf("clearPackageData" to "true"))
+
+ flankVersion.set(libs.versions.flank.get())
+
+ filesToDownload.set(listOf(
+ "*/matrix_*/*test_results_merged\\.xml",
+ "*/matrix_*/*/artifacts/sdcard/Pictures/zcash_screenshots/*\\.png"
+ ))
+
+ directoriesToPull.set(listOf(
+ // This path needs to be coordinated with the implementation in the app module's tests
+ "/sdcard/Pictures/zcash_screenshots"
+ ))
}
// All of this should be refactored to build-conventions
diff --git a/docs/Setup.md b/docs/Setup.md
index 709715ff..d225b65b 100644
--- a/docs/Setup.md
+++ b/docs/Setup.md
@@ -48,32 +48,36 @@ Start by making sure the command line with Gradle works first, because **all the
## Gradle Tasks
A variety of Gradle tasks are set up within the project, and these tasks are also accessible in Android Studio as run configurations.
* `assemble` - Compiles the application but does not deploy it
- * `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests
+ * `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests. The Android Studio run configuration actually runs all of these tasks because the debug APKs are necessary to run the tests: `assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
* `detektAll` - Performs static analysis with Detekt
- * `ktlint` - Performs code formatting checks with ktlint
+ * `ktlintFormat` - Performs code formatting checks with ktlint
* `lint` - Performs static analysis with Android lint
- * `dependencyUpdates` - Checks for available dependency updates
+ * `dependencyUpdates` - Checks for available dependency updates. It will only suggest final releases, unless a particular dependency is already using a non-final release (e.g. alpha, beta, RC).
A few notes on running instrumentation tests on the app module:
- Screenshots are generated automatically and copied to (/app/build/reports/androidTests/connected/zcash_screenshots)[../app/build/reports/androidTests/connected/zcash_screenshots]
- Running the Android tests on the app module will erase the data stored by the app. This is because Test Orchestrator is required to reset app state to successfully perform integration tests.
## Gradle Properties
-A variety of Gradle properties can be used to configure the build.
+A variety of Gradle properties can be used to configure the build. Most of these properties are optional and help with advanced configuration. If you're just doing local development or making a small pull request contribution, you likely do not need to worry about these.
### Debug Signing
By default, the application is signed by the developers automatically generated debug signing key. In a team of developers, it may be advantageous to share a debug key so that debug builds can access key-restricted services such as Firebase or Google Maps. For such a setup, the path to a shared debug signing key can be set with the property `ZCASH_DEBUG_KEYSTORE_PATH`.
### Release Signing
+This section is optional.
+
To enable release signing, a release keystore needs to be provided during the build. This can be injected securely by setting the following Gradle properties.
-* ZCASH_RELEASE_KEYSTORE_PATH
-* ZCASH_RELEASE_KEYSTORE_PASSWORD
-* ZCASH_RELEASE_KEY_ALIAS
-* ZCASH_RELEASE_KEY_ALIAS_PASSWORD
+* `ZCASH_RELEASE_KEYSTORE_PATH`
+* `ZCASH_RELEASE_KEYSTORE_PASSWORD`
+* `ZCASH_RELEASE_KEY_ALIAS`
+* `ZCASH_RELEASE_KEY_ALIAS_PASSWORD`
On a developer machine, these might be set under the user's global properties (e.g. `~/.gradle/gradle.properties` on macOS and Linux). On a continuous integration machine, these can also be set using environment variables with the prefix `ORG_GRADLE_PROJECT_` (e.g. `ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH`). DO NOT set these in the gradle.properties inside the Git repository, as this will leak your keystore password.
### Included builds
+This section is optional.
+
To simplify implementation of SDK features in conjunction with changes to the app, a Gradle [Included Build](https://docs.gradle.org/current/userguide/composite_builds.html) can be configured.
1. Check out the SDK to a directory path of `../zcash-android-sdk` relative to the root of this app's repo. For example:
@@ -87,4 +91,20 @@ To simplify implementation of SDK features in conjunction with changes to the ap
There are some limitations of included builds:
1. Properties from `secant-android-wallet` will override those set in `zcash-android-sdk` with the same name
-1. Modules in each project cannot share the same name. For this reason, build-conventions have different names in each repo (`zcash-android-sdk/build-conventions` vs `secant-android-wallet/build-convention`)
\ No newline at end of file
+1. Modules in each project cannot share the same name. For this reason, build-conventions have different names in each repo (`zcash-android-sdk/build-conventions` vs `secant-android-wallet/build-convention`)
+
+### Firebase Test Lab
+This section is optional.
+
+For Continuous Integration, see [CI.md](CI.md). The rest of this section is regarding local development.
+
+1. Set up a Firebase project, for the purpose of running Firebase Test Lab
+1. Set the Firebase Google Cloud project name as a global Gradle property `ZCASH_FIREBASE_TEST_LAB_PROJECT` under `~/.gradle/gradle.properties`
+1. Run the Gradle task `flankAuth` to generate a Firebase authentication token on your machine
+
+Tests can now be run on Firebase Test Lab from your local machine.
+
+The Firebase Test Lab tasks DO NOT build the app, so they rely on existing build outputs. This means you should:
+1. Build the debug and test APKs: `./gradlew assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
+1. Run the tests: `./gradlew runFlank`
+
diff --git a/gradle.properties b/gradle.properties
index b1bc594e..2ecf47c2 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,8 +22,11 @@ IS_COVERAGE_ENABLED=false
# It is disabled by default, because it causes tests to take about 2x longer to run.
IS_USE_TEST_ORCHESTRATOR=false
-# Optional API key for Firebase Test Lab
+# Either provide a path to a Firebase Test Lab service key (best for CI)
+# OR
+# login with `./gradlew flankAuth` and provide the project name (best for local development)
ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH=
+ZCASH_FIREBASE_TEST_LAB_PROJECT=
# Optionally disable minification
IS_MINIFY_ENABLED=true
diff --git a/spackle-lib/src/main/java/co/electriccoin/zcash/spackle/EmulatorWtfUtil.kt b/spackle-lib/src/main/java/co/electriccoin/zcash/spackle/EmulatorWtfUtil.kt
new file mode 100644
index 00000000..0fad240c
--- /dev/null
+++ b/spackle-lib/src/main/java/co/electriccoin/zcash/spackle/EmulatorWtfUtil.kt
@@ -0,0 +1,35 @@
+package co.electriccoin.zcash.spackle
+
+import android.content.Context
+import android.provider.Settings
+
+/*
+ * This is not under a test module, because there are some code paths that we might want to alter
+ * during Emulator WTF tests.
+ */
+object EmulatorWtfUtil {
+ private const val EMULATOR_WTF_SETTING = "emulator.wtf" // $NON-NLS
+ private const val SETTING_TRUE = "true" // $NON-NLS
+
+ private val isEmulatorWtfCached = LazyWithArgument {
+ isEmulatorWtfImpl(it)
+ }
+
+ /**
+ * @return True if the environment is emulator.wtf
+ */
+ fun isEmulatorWtf(context: Context) = isEmulatorWtfCached.getInstance(context)
+
+ private fun isEmulatorWtfImpl(context: Context): Boolean {
+ // Tested with the benchmark library, this is very fast. There shouldn't be a need to make
+ // this a suspend function. That said, we'll still cache the result as a just-in-case
+ // since IPC may be involved.
+ return runCatching {
+ SETTING_TRUE == Settings.System.getString(context.contentResolver, EMULATOR_WTF_SETTING)
+ }.recover {
+ // Fail-safe in case an error occurs
+ // 99.9% of the time, it won't be Firebase Test Lab
+ false
+ }.getOrThrow()
+ }
+}