# Expected secrets # EMULATOR_WTF_API_KEY - Optional API key for emulator.wtf # 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 # FIREBASE_DEBUG_JSON_BASE64 - Optional JSON to enable Firebase (e.g. Crashlytics) for debug builds # FIREBASE_RELEASE_JSON_BASE64 - Optional JSON to enable Firebase (e.g. Crashlytics) for release builds # Expected variables # FIREBASE_TEST_LAB_PROJECT - Firebase Test Lab project name name: Pull Request on: pull_request: paths-ignore: - '.github/ISSUE_TEMPLATE/*' - '.github/PULL_REQUEST_TEMPLATE.md' - 'LICENSE' - 'README.md' - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: validate_gradle_wrapper: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # Gradle Wrapper validation can be flaky # https://github.com/gradle/wrapper-validation-action/issues/40 - name: Gradle Wrapper Validation timeout-minutes: 1 uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 prime_cache: needs: validate_gradle_wrapper runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 12 uses: ./.github/actions/setup check_firebase_secrets: runs-on: ubuntu-latest outputs: has-secrets: ${{ steps.check_firebase_secrets.outputs.defined }} steps: - id: check_firebase_secrets env: FIREBASE_TEST_LAB_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} FIREBASE_TEST_LAB_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }} FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }} if: "${{ env.FIREBASE_TEST_LAB_PROJECT != '' && env.FIREBASE_TEST_LAB_SERVICE_ACCOUNT != '' && env.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER != '' }}" run: echo "defined=true" >> $GITHUB_OUTPUT 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 "defined=true" >> $GITHUB_OUTPUT check_properties: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Check properties timeout-minutes: 4 run: | ./gradlew checkProperties static_analysis_detekt: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Detekt timeout-minutes: 4 run: | ./gradlew detektAll - name: Collect Artifacts timeout-minutes: 1 if: ${{ always() }} env: ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_detekt.zip', env.home) }} run: | mkdir ${ARTIFACTS_DIR_PATH} zip -r ${REPORTS_ZIP_PATH} . -i build/reports/detekt/* - name: Upload Artifacts if: ${{ always() }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Detekt static analysis results path: ~/artifacts static_analysis_ktlint: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Ktlint timeout-minutes: 4 run: | ./gradlew ktlint - name: Collect Artifacts timeout-minutes: 1 if: ${{ always() }} env: ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_ktlint.zip', env.home) }} run: | mkdir ${ARTIFACTS_DIR_PATH} zip -r ${REPORTS_ZIP_PATH} . -i build/reports/ktlint/\* - name: Upload Artifacts if: ${{ always() }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Ktlint static analysis results path: ~/artifacts static_analysis_android_lint: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Android Lint timeout-minutes: 15 env: # Disable minify, since it makes lint run faster ORG_GRADLE_PROJECT_IS_MINIFY_ENABLED: false run: | ./gradlew :app:lintZcashmainnetRelease - name: Collect Artifacts if: ${{ always() }} timeout-minutes: 1 env: ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} LINT_ZIP_PATH: ${{ format('{0}/artifacts/android_lint.zip', env.home) }} run: | mkdir ${ARTIFACTS_DIR_PATH} zip -r ${LINT_ZIP_PATH} . -i *build/reports/* - name: Upload Artifacts if: ${{ always() }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Android Lint static analysis results path: ~/artifacts test_kotlin_modules: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Test timeout-minutes: 4 run: | # Note that we explicitly check just the Kotlin modules, to avoid compiling the Android modules here ./gradlew :crash-lib:check :preference-api-lib:check :spackle-lib:check - name: Collect Artifacts if: ${{ always() }} timeout-minutes: 1 env: ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} RESULTS_ZIP_PATH: ${{ format('{0}/artifacts/test_kotlin.zip', env.home) }} run: | mkdir ${ARTIFACTS_DIR_PATH} zip -r ${RESULTS_ZIP_PATH} . -i *build/reports/* - name: Upload Artifacts if: ${{ always() }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Test Kotlin modules results path: ~/artifacts # 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 id-token: write steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Build timeout-minutes: 20 run: | ./gradlew assembleDebug assembleAndroidTest assembleZcashmainnetDebug assembleZcashtestnetDebug - name: Authenticate to Google Cloud for Firebase Test Lab id: auth_test_lab uses: google-github-actions/auth@ef5d53e30bbcd8d0836f4288f5e50ff3e086997d with: create_credentials_file: true project_id: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} service_account: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }} workload_identity_provider: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }} access_token_lifetime: '1200s' - name: Test timeout-minutes: 30 env: # Force blank suffix for screenshot tests ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: "" # Used by Flank, since the temporary token is missing the project name GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }} # Because Fulladle doesn't allow Test Orchestrator to be enabled/disabled for a specific submodule, it must be enabled for all modules ORG_GRADLE_PROJECT_IS_USE_TEST_ORCHESTRATOR: true run: | ./gradlew runFlank --parallel - 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/fladle/\* \*/build/outputs/androidTest-results/\* - name: Upload Artifacts if: ${{ always() }} uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Test Android modules with FTL results path: ~/artifacts test_android_modules_wtf_coverage: 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@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Build and test timeout-minutes: 30 env: # Force blank suffix for screenshot tests ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: "" ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }} # Temporarily disabled https://issuetracker.google.com/issues/266109833 ORG_GRADLE_PROJECT_IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED: false run: | ./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf - 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@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 1 with: name: Test Android libs with WTF results path: ~/artifacts test_android_modules_wtf_no_coverage: 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@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Build and test timeout-minutes: 30 env: # Force blank suffix for screenshot tests ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: "" ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }} run: | ./gradlew :app:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf - 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@83fd05a356d7e2593de66fc9913b3002723633cb timeout-minutes: 1 with: name: Test Android app with WTF results path: ~/artifacts # Performs a button mash test on the debug build of the app with strict mode enabled test_robo_debug: if: needs.check_firebase_secrets.outputs.has-secrets == 'true' needs: [check_firebase_secrets] runs-on: ubuntu-latest permissions: packages: read contents: read id-token: write steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Export Google Services JSON env: FIREBASE_DEBUG_JSON_BASE64: ${{ secrets.FIREBASE_DEBUG_JSON_BASE64 }} FIREBASE_RELEASE_JSON_BASE64: ${{ secrets.FIREBASE_RELEASE_JSON_BASE64 }} if: "${{ env.FIREBASE_DEBUG_JSON_BASE64 != '' && env.FIREBASE_RELEASE_JSON_BASE64 != '' }}" shell: bash run: | mkdir -p app/src/debug/ mkdir -p app/src/release/ echo ${FIREBASE_DEBUG_JSON_BASE64} | base64 --decode > app/src/debug/google-services.json echo ${FIREBASE_RELEASE_JSON_BASE64} | base64 --decode > app/src/release/google-services.json - name: Build timeout-minutes: 20 env: ORG_GRADLE_PROJECT_IS_CRASH_ON_STRICT_MODE_VIOLATION: true run: | ./gradlew :app:assembleDebug - name: Authenticate to Google Cloud for Firebase Test Lab id: auth_test_lab uses: google-github-actions/auth@ef5d53e30bbcd8d0836f4288f5e50ff3e086997d with: create_credentials_file: true project_id: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} service_account: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }} workload_identity_provider: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }} access_token_lifetime: '900s' - name: Robo test timeout-minutes: 20 env: # This first environment variable is used by Flank, since the temporary token is missing the project name GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }} run: | ./gradlew :app:runFlankSanityConfigDebug build: needs: prime_cache runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Export Google Services JSON env: FIREBASE_DEBUG_JSON_BASE64: ${{ secrets.FIREBASE_DEBUG_JSON_BASE64 }} FIREBASE_RELEASE_JSON_BASE64: ${{ secrets.FIREBASE_RELEASE_JSON_BASE64 }} if: "${{ env.FIREBASE_DEBUG_JSON_BASE64 != '' && env.FIREBASE_RELEASE_JSON_BASE64 != '' }}" shell: bash run: | mkdir -p app/src/debug/ mkdir -p app/src/release/ echo ${FIREBASE_DEBUG_JSON_BASE64} | base64 --decode > app/src/debug/google-services.json echo ${FIREBASE_RELEASE_JSON_BASE64} | base64 --decode > app/src/release/google-services.json # A fake signing key to satisfy creating a "release" build - name: Export Signing Key env: SIGNING_KEY_PATH: ${{ format('{0}/release.jks', env.home) }} shell: bash run: | keytool -genkey -v -keystore $SIGNING_KEY_PATH -keypass android -storepass android -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 100000 -dname "CN=, OU=, O=Test, L=, S=, C=" -noprompt - name: Build timeout-minutes: 20 env: ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }} ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: android ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android run: | ./gradlew :app:assembleDebug :app:bundleRelease :app:packageZcashmainnetReleaseUniversalApk - name: Collect Artifacts timeout-minutes: 1 env: ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} BINARIES_ZIP_PATH: ${{ format('{0}/artifacts/binaries.zip', env.home) }} MAPPINGS_ZIP_PATH: ${{ format('{0}/artifacts/mappings.zip', env.home) }} run: | mkdir ${ARTIFACTS_DIR_PATH} zip -r ${BINARIES_ZIP_PATH} . -i app/build/outputs/apk/\*/\*.apk app/build/outputs/apk_from_bundle/\*/\*.apk app/build/outputs/bundle/\*/\*.aab zip -r ${MAPPINGS_ZIP_PATH} . -i *app/build/outputs/mapping/*/mapping.txt - name: Upload Artifacts uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce timeout-minutes: 2 with: name: Binaries path: ~/artifacts # Performs a button mash test on the release build of the app test_robo_release: if: needs.check_firebase_secrets.outputs.has-secrets == 'true' needs: [build, check_firebase_secrets] runs-on: ubuntu-latest permissions: packages: read contents: read id-token: write steps: - name: Checkout timeout-minutes: 1 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup id: setup timeout-minutes: 5 uses: ./.github/actions/setup - name: Authenticate to Google Cloud for Firebase Test Lab id: auth_test_lab uses: google-github-actions/auth@ef5d53e30bbcd8d0836f4288f5e50ff3e086997d with: create_credentials_file: true project_id: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} service_account: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }} workload_identity_provider: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }} access_token_lifetime: '900s' - name: Download a single artifact uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: name: Binaries - name: Robo test timeout-minutes: 20 env: # Path depends on `release_build` job, plus path of `Download a single artifact` step BINARIES_ZIP_PATH: binaries.zip # This first environment variable is used by Flank, since the temporary token is missing the project name GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }} ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }} run: | unzip ${BINARIES_ZIP_PATH} ./gradlew :app:runFlankSanityConfigRelease