Android - Build and test #18164
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
name: Android - Build and test | |
on: | |
pull_request: | |
paths: | |
- '**' | |
- '!.github/workflows/**' | |
- '.github/workflows/android-app.yml' | |
- '!.github/CODEOWNERS' | |
- '!audits/**' | |
- '!ci/**' | |
- '!dist-assets/**' | |
- '!docs/**' | |
- '!graphics/**' | |
- '!desktop/**' | |
- '!ios/**' | |
- '!test/**' | |
- '!scripts/**' | |
- '!windows/**' | |
- '!**/**.md' | |
- '!**/osv-scanner.toml' | |
schedule: | |
# At 00:00 UTC every day. | |
# Notifications for scheduled workflows are sent to the user who last modified the cron | |
# syntax in the workflow file. If you update this you must have notifications for | |
# Github Actions enabled, so these don't go unnoticed. | |
# https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/notifications-for-workflow-runs | |
- cron: '0 0 * * *' | |
workflow_dispatch: | |
inputs: | |
override_container_image: | |
description: Override container image | |
type: string | |
required: false | |
run_firebase_tests: | |
description: Run firebase tests | |
type: boolean | |
required: false | |
mockapi_test_repeat: | |
description: Mockapi test repeat (self hosted) | |
default: '1' | |
required: true | |
type: string | |
e2e_test_repeat: | |
description: e2e test repeat (self hosted) | |
default: '0' | |
required: true | |
type: string | |
e2e_tests_infra_flavor: | |
description: > | |
Infra environment to run e2e tests on (prod/stagemole). | |
If set to 'stagemole' test-related artefacts will be uploaded. | |
default: 'stagemole' | |
required: true | |
type: string | |
# Build if main is updated to ensure up-to-date caches are available | |
push: | |
permissions: {} | |
env: | |
BUILD_RUNNER: app-gh-build # ubuntu-latest | |
E2E_TEST_INFRA_FLAVOR: ${{ github.event.inputs.e2e_tests_infra_flavor || 'stagemole' }} | |
jobs: | |
prepare: | |
name: Prepare | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Use custom container image if specified | |
if: ${{ github.event.inputs.override_container_image != '' }} | |
run: | | |
echo "inner_container_image=${{ github.event.inputs.override_container_image }}" \ | |
>> $GITHUB_ENV | |
- name: Use default container image and resolve digest | |
if: ${{ github.event.inputs.override_container_image == '' }} | |
run: | | |
echo "inner_container_image=$(cat ./building/android-container-image.txt)" >> $GITHUB_ENV | |
- name: Use default container image and resolve digest | |
id: determine-runner | |
run: | | |
echo "use-runner=app-gh-build" >> $GITHUB_ENV | |
# TODO: Fix fallback | |
# - name: Determine runner depending on availability | |
# id: determine-runner | |
# uses: jimmygchen/runner-fallback-action@v1 | |
# with: | |
# primary-runner: "app-gh-build" | |
# fallback-runner: "ubuntu-latest" | |
# github-token: ${{ secrets.YOUR_GITHUB_TOKEN }} | |
outputs: | |
container_image: ${{ env.inner_container_image }} | |
build_runner: ${{ env.use-runner }} | |
build-native: | |
name: Build native # Used by wait for jobs. | |
needs: prepare | |
runs-on: ${{ needs.prepare.outputs.build_runner }} | |
env: | |
ANDROID_CONTAINER_NAME: ${{ needs.prepare.outputs.container_image }} | |
strategy: | |
matrix: | |
include: | |
- abi: "x86_64" | |
task-variant: "X86_64" | |
- abi: "x86" | |
task-variant: "X86" | |
- abi: "arm64-v8a" | |
task-variant: "Arm64" | |
- abi: "armeabi-v7a" | |
task-variant: "Arm" | |
steps: | |
- name: Prepare enviornment | |
run: | | |
echo "CARGO_TARGET_VOLUME_NAME=cargo-target-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "CARGO_REGISTRY_VOLUME_NAME=cargo-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "GRADLE_CACHE_VOLUME_NAME=gradle-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Checkout wireguard-go-rs recursively | |
run: | | |
git config --global --add safe.directory '*' | |
git submodule update --init --recursive --depth=1 wireguard-go-rs | |
- name: Build native | |
run: | | |
./building/container-run.sh android android/gradlew -p \ | |
android cargoBuild${{ matrix.task-variant }} | |
- name: Upload native libs | |
uses: actions/upload-artifact@v4 | |
with: | |
name: native-libs-${{ matrix.abi }} | |
path: android/app/build/rustJniLibs/android | |
if-no-files-found: error | |
retention-days: 7 | |
build-app: | |
name: Build app | |
needs: prepare | |
runs-on: ${{ needs.prepare.outputs.build_runner }} | |
env: | |
ANDROID_CONTAINER_NAME: ${{ needs.prepare.outputs.container_image }} | |
steps: | |
- name: Prepare enviornment | |
run: | | |
echo "DEBUG_KEYSTORE_HOST_PATH=${{ runner.temp }}/debug.keystore" >> $GITHUB_ENV | |
echo "CARGO_TARGET_VOLUME_NAME=cargo-target-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "CARGO_REGISTRY_VOLUME_NAME=cargo-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "GRADLE_CACHE_VOLUME_NAME=gradle-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
- name: Prepare dummy debug keystore | |
run: | | |
echo "${{ secrets.ANDROID_DUMMY_DEBUG_KEYSTORE }}" | \ | |
base64 -d > $DEBUG_KEYSTORE_HOST_PATH | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Compile app | |
run: | | |
./building/container-run.sh android android/gradlew -p android compileOssProdDebugKotlin -x cargoBuild --configuration-cache | |
- name: Wait for other jobs (native, relay list) | |
uses: kachick/wait-other-jobs@v3.6.0 | |
with: | |
wait-seconds-before-first-polling: '0' | |
wait-list: | | |
[ | |
{ | |
"workflowFile": "android-app.yml", | |
"jobMatchMode": "prefix", | |
"jobName": "Build native" | |
} | |
] | |
- uses: actions/download-artifact@v4 | |
with: | |
pattern: native-libs-* | |
path: android/app/build/rustJniLibs/android | |
merge-multiple: true | |
- name: Build app | |
run: | | |
./building/container-run.sh android android/gradlew -p android assembleOssProdDebug -x cargoBuild --configuration-cache | |
- name: Build stagemole app | |
if: > | |
(github.event.inputs.e2e_test_repeat != '0' && env.E2E_TEST_INFRA_FLAVOR == 'stagemole') || | |
github.event.inputs.run_firebase_tests == 'true' | |
run: | | |
./building/container-run.sh android android/gradlew -p android assemblePlayStagemoleDebug -x cargoBuild --configuration-cache | |
- name: Upload apks | |
uses: actions/upload-artifact@v4 | |
with: | |
name: apks | |
path: android/app/build/outputs/apk | |
if-no-files-found: error | |
retention-days: 7 | |
build-instrumented-tests: | |
name: Build instrumented test packages | |
needs: prepare | |
runs-on: ${{ needs.prepare.outputs.build_runner }} | |
env: | |
ANDROID_CONTAINER_NAME: ${{ needs.prepare.outputs.container_image }} | |
strategy: | |
matrix: | |
include: | |
- test-type: app | |
assemble-command: assembleOssProdAndroidTest | |
artifact-path: android/app/build/outputs/apk | |
- test-type: mockapi | |
assemble-command: :test:mockapi:assemble | |
artifact-path: android/test/mockapi/build/outputs/apk | |
- test-type: e2e | |
assemble-command: :test:e2e:assemble | |
artifact-path: android/test/e2e/build/outputs/apk | |
steps: | |
- name: Prepare enviornment | |
run: | | |
echo "DEBUG_KEYSTORE_HOST_PATH=${{ runner.temp }}/debug.keystore" >> $GITHUB_ENV | |
echo "CARGO_TARGET_VOLUME_NAME=cargo-target-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "CARGO_REGISTRY_VOLUME_NAME=cargo-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
echo "GRADLE_CACHE_VOLUME_NAME=gradle-cache-$(echo $RUNNER_NAME | tr -cd '[:alnum:]')" >> $GITHUB_ENV | |
- name: Prepare dummy debug keystore | |
run: | | |
echo "${{ secrets.ANDROID_DUMMY_DEBUG_KEYSTORE }}" | \ | |
base64 -d > $DEBUG_KEYSTORE_HOST_PATH | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Assemble instrumented test apk | |
run: | | |
./building/container-run.sh android android/gradlew -p android \ | |
${{ matrix.assemble-command }} \ | |
-x cargoBuild \ | |
-x mergeOssProdDebugJniLibFolders \ | |
-x mergePlayStagemoleDebugJniLibFolders \ | |
--configuration-cache | |
- name: Upload apks | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.test-type }}-instrumentation-apks | |
path: ${{ matrix.artifact-path }} | |
if-no-files-found: error | |
retention-days: 7 | |
instrumented-tests: | |
name: Run instrumented tests | |
runs-on: [self-hosted, android-device] | |
needs: [build-app, build-instrumented-tests] | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
- test-type: app | |
path: android/app/build/outputs/apk | |
test-repeat: 1 | |
- test-type: mockapi | |
path: android/test/mockapi/build/outputs/apk | |
test-repeat: ${{ github.event_name == 'schedule' && 10 || github.event.inputs.mockapi_test_repeat || 1 }} | |
steps: | |
- name: Prepare report dir | |
if: ${{ matrix.test-repeat != 0 }} | |
id: prepare-report-dir | |
env: | |
INNER_REPORT_DIR: /tmp/${{ matrix.test-type }}-${{ github.run_id }}-${{ github.run_attempt }} | |
run: | | |
mkdir -p $INNER_REPORT_DIR | |
echo "report_dir=$INNER_REPORT_DIR" >> $GITHUB_OUTPUT | |
- name: Checkout repository | |
if: ${{ matrix.test-repeat != 0 }} | |
uses: actions/checkout@v4 | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
if: ${{ matrix.test-repeat != 0 }} | |
with: | |
name: apks | |
path: android/app/build/outputs/apk | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
if: ${{ matrix.test-repeat != 0 }} | |
with: | |
name: ${{ matrix.test-type }}-instrumentation-apks | |
path: ${{ matrix.path }} | |
- name: Calculate timeout | |
id: calculate-timeout | |
run: echo "timeout=$(( ${{ matrix.test-repeat }} * 10 ))" >> $GITHUB_OUTPUT | |
shell: bash | |
- name: Run instrumented test script | |
if: ${{ matrix.test-repeat != 0 }} | |
timeout-minutes: ${{ fromJSON(steps.calculate-timeout.outputs.timeout) }} | |
shell: bash -ieo pipefail {0} | |
env: | |
AUTO_FETCH_TEST_HELPER_APKS: true | |
TEST_TYPE: ${{ matrix.test-type }} | |
BILLING_FLAVOR: oss | |
INFRA_FLAVOR: prod | |
REPORT_DIR: ${{ steps.prepare-report-dir.outputs.report_dir }} | |
run: ./android/scripts/run-instrumented-tests-repeat.sh ${{ matrix.test-repeat }} | |
- name: Upload instrumentation report (${{ matrix.test-type }}) | |
uses: actions/upload-artifact@v4 | |
if: always() && matrix.test-repeat != 0 | |
with: | |
name: ${{ matrix.test-type }}-instrumentation-report | |
path: ${{ steps.prepare-report-dir.outputs.report_dir }} | |
if-no-files-found: ignore | |
retention-days: 7 | |
instrumented-e2e-tests: | |
name: Run instrumented e2e tests | |
# Temporary workaround for targeting the runner android-runner-v1 | |
runs-on: [self-hosted, android-device, android-emulator] | |
if: > | |
github.event_name == 'schedule' || | |
(github.event.inputs.e2e_test_repeat != '0' && github.event_name != 'pull_request') | |
needs: [build-app, build-instrumented-tests] | |
strategy: | |
matrix: | |
include: | |
- test-repeat: ${{ github.event.inputs.e2e_test_repeat || 1 }} | |
steps: | |
- name: Prepare report dir | |
if: ${{ matrix.test-repeat != 0 }} | |
id: prepare-report-dir | |
env: | |
INNER_REPORT_DIR: /tmp/${{ github.run_id }}-${{ github.run_attempt }} | |
run: | | |
mkdir -p $INNER_REPORT_DIR | |
echo "report_dir=$INNER_REPORT_DIR" >> $GITHUB_OUTPUT | |
- name: Checkout repository | |
if: ${{ matrix.test-repeat != 0 }} | |
uses: actions/checkout@v4 | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
if: ${{ matrix.test-repeat != 0 }} | |
with: | |
name: apks | |
path: android/app/build/outputs/apk | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
if: ${{ matrix.test-repeat != 0 }} | |
with: | |
name: e2e-instrumentation-apks | |
path: android/test/e2e/build/outputs/apk | |
- name: Calculate timeout | |
id: calculate-timeout | |
run: echo "timeout=$(( ${{ matrix.test-repeat }} * 15 ))" >> $GITHUB_OUTPUT | |
shell: bash | |
- name: Run instrumented test script | |
if: ${{ matrix.test-repeat != 0 }} | |
timeout-minutes: ${{ fromJSON(steps.calculate-timeout.outputs.timeout) }} | |
shell: bash -ieo pipefail {0} | |
env: | |
AUTO_FETCH_TEST_HELPER_APKS: true | |
TEST_TYPE: e2e | |
BILLING_FLAVOR: ${{ env.E2E_TEST_INFRA_FLAVOR == 'prod' && 'oss' || 'play' }} | |
INFRA_FLAVOR: ${{ env.E2E_TEST_INFRA_FLAVOR }} | |
PARTNER_AUTH: |- | |
${{ env.E2E_TEST_INFRA_FLAVOR == 'stagemole' && secrets.STAGEMOLE_PARTNER_AUTH || '' }} | |
VALID_TEST_ACCOUNT_NUMBER: |- | |
${{ env.E2E_TEST_INFRA_FLAVOR == 'prod' && secrets.ANDROID_PROD_TEST_ACCOUNT || '' }} | |
INVALID_TEST_ACCOUNT_NUMBER: '0000000000000000' | |
ENABLE_HIGHLY_RATE_LIMITED_TESTS: ${{ github.event_name == 'schedule' && 'true' || 'false' }} | |
ENABLE_ACCESS_TO_LOCAL_API_TESTS: true | |
REPORT_DIR: ${{ steps.prepare-report-dir.outputs.report_dir }} | |
run: ./android/scripts/run-instrumented-tests-repeat.sh ${{ matrix.test-repeat }} | |
- name: Upload e2e instrumentation report | |
uses: actions/upload-artifact@v4 | |
if: > | |
always() && matrix.test-repeat != 0 && env.E2E_TEST_INFRA_FLAVOR == 'stagemole' | |
with: | |
name: e2e-instrumentation-report | |
path: ${{ steps.prepare-report-dir.outputs.report_dir }} | |
firebase-tests: | |
name: Run firebase tests | |
if: github.event.inputs.run_firebase_tests == 'true' | |
runs-on: ubuntu-latest | |
timeout-minutes: 30 | |
needs: [build-app, build-instrumented-tests] | |
env: | |
FIREBASE_ENVIRONMENT_VARIABLES: "\ | |
clearPackageData=true,\ | |
runnerBuilder=de.mannodermaus.junit5.AndroidJUnit5Builder,\ | |
invalid_test_account_number=0000000000000000,\ | |
ENABLE_HIGHLY_RATE_LIMITED_TESTS=${{ github.event_name == 'schedule' && 'true' || 'false' }},\ | |
partner_auth=${{ secrets.STAGEMOLE_PARTNER_AUTH }},\ | |
ENABLE_ACCESS_TO_LOCAL_API_TESTS=false" | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
- test-type: mockapi | |
arg-spec-file: mockapi-oss.yml | |
path: android/test/mockapi/build/outputs/apk | |
- test-type: e2e | |
arg-spec-file: e2e-play-stagemole.yml | |
path: android/test/e2e/build/outputs/apk | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
with: | |
name: apks | |
path: android/app/build/outputs/apk | |
# Using v3 due to v4 being very slow for this artifact. | |
- uses: actions/download-artifact@v4 | |
with: | |
name: ${{ matrix.test-type }}-instrumentation-apks | |
path: ${{ matrix.path }} | |
- name: Run tests on Firebase Test Lab | |
uses: asadmansr/Firebase-Test-Lab-Action@v1.0 | |
env: | |
SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }} | |
with: | |
arg-spec: | | |
android/test/firebase/${{ matrix.arg-spec-file }}:default | |
--environment-variables ${{ env.FIREBASE_ENVIRONMENT_VARIABLES }} | |
# run-lint-and-tests: | |
# name: Run lint and test tasks | |
# needs: [prepare] | |
# runs-on: ubuntu-latest | |
# container: | |
# image: ${{ needs.prepare.outputs.container_image }} | |
# strategy: | |
# fail-fast: false | |
# matrix: | |
# include: | |
# - gradle-task: | | |
# testDebugUnitTest -x :test:arch:testDebugUnitTest | |
# :app:testOssProdDebugUnitTest | |
# :service:testOssProdDebugUnitTest | |
# :lib:billing:testDebugUnitTest | |
# :lib:daemon-grpc:testDebugUnitTest | |
# :lib:shared:testDebugUnitTest | |
# - gradle-task: :test:arch:test --rerun-tasks | |
# - gradle-task: detekt | |
# - gradle-task: lint | |
# steps: | |
# # Fix for HOME path overridden by GH runners when building in containers, see: | |
# # https://github.com/actions/runner/issues/863 | |
# - name: Fix HOME path | |
# run: echo "HOME=/root" >> $GITHUB_ENV | |
# - name: Checkout repository | |
# uses: actions/checkout@v4 | |
# with: | |
# submodules: true | |
# - name: Run gradle task | |
# uses: burrunan/gradle-cache-action@v1 | |
# with: | |
# job-id: jdk17 | |
# arguments: ${{ matrix.gradle-task }} | |
# gradle-version: wrapper | |
# build-root-directory: android | |
# execution-only-caches: false | |
# # Disable if logs are hard to follow. | |
# concurrent: true | |
# read-only: ${{ github.ref != 'refs/heads/main' }} |