Skip to content

refactor(CardTemplateEditor): add ViewModel + State infrastructure #33007

refactor(CardTemplateEditor): add ViewModel + State infrastructure

refactor(CardTemplateEditor): add ViewModel + State infrastructure #33007

Workflow file for this run

name: 🛠️ Emulator Tests
on:
workflow_dispatch:
inputs:
iterations:
description: "Number of iterations to run. Default 1. Max 256."
required: true
default: 1
type: number
pull_request:
push:
# Ignore merge queue branches on push; avoids merge_group+push concurrency race since ref is same
branches-ignore:
- 'gh-readonly-queue/**'
merge_group:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# We want to generate our matrix dynamically
# Initial job generates the matrix as a JSON, and following job will use deserialize and use the result
matrix_prep:
# Do not run the scheduled jobs on forks
if: (github.event_name == 'schedule' && github.repository == 'ankidroid/Anki-Android') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.build-matrix.outputs.result }}
steps:
- id: build-matrix
uses: actions/github-script@v8
with:
script: |
// by default, we will run one iteration
let iterationArray = [0]
// workflow dispatch will be a drop-down of different options
if (context.eventName === "workflow_dispatch") {
const inputs = ${{ toJSON(inputs) }}
console.log('inputs is: ' + JSON.stringify(inputs))
const iterationInput = inputs.iterations
console.log('iterations input is: ' + iterationInput)
// this will expand for example with input 5 => [0, 1, 2, 3, 4]
iterationArray = []
for (let i = 0; i < iterationInput; i++) {
iterationArray.push(i);
}
console.log('iterationArray is: ' + iterationArray)
}
// If we are running on a schedule it's our periodic passive scan for flakes
// Goal is to run enough iterations on all systems that we have confidence there are no flakes
if (context.eventName === "schedule") {
const iterationCount = 15
for (let i = 0; i < iterationCount; i++) {
iterationArray.push(i);
}
}
// TODO Refactor to make these dynamic with a minSdk/max bracket only on schedule, not push
let apiLevel = [36];
// These are used for performance tuning what emulator to use.
// try different architectures, targets, delays etc
let arch = ['x86_64'];
let target = ['google_apis'];
let firstBootDelay = [600];
return {
"iteration": iterationArray,
"api-level": apiLevel,
"arch": arch,
"target": target,
"first-boot-delay": firstBootDelay
}
- name: Debug Output
run: echo "${{ steps.build-matrix.outputs.result }}"
# This uses the matrix generated from the matrix-prep stage
# it will run unit tests on whatever OS combinations are desired
emulator_test:
name: Android Emulator Test (${{ matrix.api-level }}, ${{ matrix.arch }}, ${{ matrix.target }}, ${{ matrix.first-boot-delay }}, ${{ matrix.iteration }})
runs-on: ubuntu-latest
needs: matrix_prep
timeout-minutes: 75
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
TEST_RELEASE_BUILD: true
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- name: Free Disk Space (Ubuntu)
uses: insightsengineering/disk-space-reclaimer@v1
with:
tools-cache: false
android: false
dotnet: true
haskell: false
large-packages: false
swap-storage: false
docker-images: false
- uses: actions/checkout@v6
with:
fetch-depth: 50
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "21" # also change jitpack.yml
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
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' }}
# 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@v5
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: Warm Gradle Cache
# This makes sure we fetch gradle network resources with a retry
uses: nick-invision/retry@v3
with:
timeout_minutes: 15
retry_wait_seconds: 60
max_attempts: 3
command: ./gradlew packagePlayRelease packagePlayReleaseAndroidTest --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
# update workflows/README.md when modifying this script
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 jacocoAndroidTestReport --daemon
- name: Upload Test Report
uses: actions/upload-artifact@v6
if: always()
with:
name: ${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-${{matrix.first-boot-delay}}-${{matrix.iteration}}-jacocoAndroidTestReport
path: ~/work/Anki-Android/Anki-Android/AnkiDroid/build/reports/jacoco/
- name: Upload Emulator Log
uses: actions/upload-artifact@v6
if: always()
with:
name: ${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-${{matrix.first-boot-delay}}-${{matrix.iteration}}-adb_logs
path: adb-log.txt
- name: Upload Emulator Screen Record
uses: actions/upload-artifact@v6
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@v5
with:
verbose: true
fail_ci_if_error: ${{ github.repository == 'ankidroid/Anki-Android' }} # optional (default = false)