diff --git a/.github/workflows/emulator_tests.yml b/.github/workflows/emulator_tests.yml new file mode 100644 index 000000000..f585a2a86 --- /dev/null +++ b/.github/workflows/emulator_tests.yml @@ -0,0 +1,186 @@ +# Thanks to AnkiDroid +# https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_emulator.yml +name: Emulator Tests + +on: + workflow_dispatch: + inputs: + clearCaches: + description: "Clear workflow caches where possible" + required: false + type: string + push: + branches: + # - master + - gh-actions + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + emulator_test: + name: Android Emulator Test + runs-on: macos-latest + timeout-minutes: 75 + strategy: + fail-fast: false + matrix: + # Refactor to make these dynamic with a low/high bracket only on schedule, not push + # For now this is the latest supported API. Previously API 29 was fastest. + # #13695: This was reverted to API 30, 31 was unstable. This should be fixed + api-level: [30] + arch: [x86_64] + target: [google_apis] + first-boot-delay: [600] + # This is useful for benchmarking, do 0, 1, 2, etc (up to 256 max job-per-matrix limit) for averages + iteration: [0] + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 0 + + - name: Configure JDK + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" # newer libraries require 17 now, force it everywhere + + - name: Verify JDK17 + # Default JDK varies depending on different runner flavors, make sure we are on 17 + # Run a check that exits with error unless it is 17 version to future-proof against unexpected upgrades + run: java -fullversion 2>&1 | grep '17.0' + shell: bash + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + timeout-minutes: 5 + with: + # Only write to the cache for builds on the 'main' branches, stops branches evicting main cache + # Builds on other branches will only read from main branch cache writes + # Comment this and the with: above out for performance testing on a branch + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + gradle-home-cache-cleanup: true + + # This appears to be 'Cache Size: ~1230 MB (1290026823 B)' based on watching action logs + # Repo limit is 10GB; branch caches are independent; branches may read default branch cache. + # We don't want branches to evict main branch snapshot, so save on main, read-only all else + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v1-${{ github.event.inputs.clearCaches }} + restore-keys: | + avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v1 + + - name: Clear Caches Optionally + if: "${{ github.event.inputs.clearCaches != '' }}" + shell: bash + run: | + du -sk ~/.gradle + du -sk ~/.android + rm -fr ~/.gradle + rm -fr ~/.android + du -sk ~/.gradle || echo ~/.gradle is gone + du -sk ~/.android || echo ~/.android is gone + + - name: Warm Gradle Cache + # This makes sure we fetch gradle network resources with a retry + uses: nick-invision/retry@v2 + with: + timeout_minutes: 15 + retry_wait_seconds: 60 + max_attempts: 3 + command: ./gradlew packageDebug packageDebugAndroidTest --daemon + + - name: AVD Boot and Snapshot Creation + # Only generate a snapshot for saving if we are on main branch with a cache miss + # Comment the if out to generate snapshots on branch for performance testing + if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}" + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + # Give the emulator a little time to run and do first boot stuff before taking snapshot + script: | + touch adb-log.txt + $ANDROID_HOME/platform-tools/adb logcat '*:D' >> adb-log.txt & + adb logcat --clear + echo "Generated AVD snapshot for caching." + + # This step is separate so pure install time may be calculated as a step + - name: Emulator Snapshot After Firstboot Warmup + # Only generate a snapshot for saving if we are on main branch with a cache miss + # Switch the if statements via comment if generating snapshots for performance testing + # if: matrix.first-boot-delay != '0' + if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}" + env: + FIRST_BOOT_DELAY: ${{ matrix.first-boot-delay }} + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + # Restart zygote to win a config race / Give emulator time to run and do first boot stuff before taking snapshot + script: | + touch adb-log.txt + $ANDROID_HOME/platform-tools/adb logcat '*:D' >> adb-log.txt & + $ANDROID_HOME/platform-tools/adb shell su root "setprop ctl.restart zygote" + sleep 10 + sleep $FIRST_BOOT_DELAY + adb logcat --clear + echo "First boot warmup completed." + + - name: Run Emulator Tests + uses: reactivecircus/android-emulator-runner@v2 + timeout-minutes: 30 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + script: | + touch adb-log.txt + $ANDROID_HOME/platform-tools/adb logcat '*:D' >> adb-log.txt & + adb emu screenrecord start --time-limit 1800 video.webm + sleep 5 + ./gradlew uninstallAll connectedDebugAndroidTest --daemon + + - name: Compress Emulator Log + if: always() + run: gzip -9 adb-log.txt + shell: bash + + - name: Upload Emulator Log + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-${{matrix.first-boot-delay}}-${{matrix.iteration}}-adb_logs + path: adb-log.txt.gz + + - name: Upload Emulator Screen Record + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-${{matrix.first-boot-delay}}-${{matrix.iteration}}-adb_video + path: video.webm + + # - uses: codecov/codecov-action@v3 + # with: + # verbose: true