Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 11 additions & 39 deletions .github/workflows/gradle_test_app_random_native_crashes.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Gradle Test App Random Native Crashes
name: Trigger Android Crash Test Loop E2E test

on:
workflow_dispatch:
Expand All @@ -11,8 +11,10 @@ on:

jobs:
run_random_native_crashes:
name: Android gradle-test-app crash loop test
runs-on: ubuntu-latest-8-cores
env:
CRASH_LOOP_API_KEY: ${{ secrets.CRASH_LOOP_API_KEY }}
SKIP_PROTO_GEN: 1
steps:
- name: Checkout project sources
Expand All @@ -23,53 +25,23 @@ jobs:
with:
setup-emulator: 'true'

- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: ${{ runner.os }}-avd-api-23-random-native-crashes-v1

- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
timeout-minutes: 30
uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b
with:
channel: stable
force-avd-creation: false
api-level: 23
target: default
ram-size: 2048M
arch: x86_64
disk-size: 6144M
profile: Nexus 6
disable-animations: true
emulator-options: -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: echo "Generated AVD snapshot for caching."
- name: Build gradle-test-app debug APK
timeout-minutes: 45
run: ./tools/android_sdk_wrapper.sh platform/jvm/gradlew -Prust-target=x86_64 -PskipGradleTestAppLintOnAssemble=true -p platform/jvm gradle-test-app:assembleDebug --stacktrace

- name: Run random native crashes
timeout-minutes: 360
uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b
with:
avd-name: android-gradle-test-app-crash-loop-test
channel: stable
force-avd-creation: false
api-level: 23
target: default
api-level: 31
target: google_apis
ram-size: 2048M
arch: x86_64
disk-size: 6144M
profile: Nexus 6
disable-animations: true
emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: |
ITERATIONS="${{ github.event.inputs.iterations || '5000' }}"
if ! [[ "$ITERATIONS" =~ ^[0-9]+$ ]] || [[ "$ITERATIONS" -lt 1 ]]; then
echo "iterations must be a positive integer"
exit 1
fi
curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="$PATH:$HOME/.maestro/bin"
./tools/android_sdk_wrapper.sh platform/jvm/gradlew -Prust-target=x86_64 -p platform/jvm gradle-test-app:assembleDebug gradle-test-app:installDebug --stacktrace
ITERATIONS="$ITERATIONS" maestro test tools/maestro/native-crash-loop.yaml
emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
script: bash ./ci/run_gradle_test_app_random_native_crashes.sh "${{ github.event.inputs.iterations }}"
99 changes: 99 additions & 0 deletions ci/run_gradle_test_app_random_native_crashes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash

set -euo pipefail

readonly iterations="${1:-5000}"
readonly emulator_serial="${ANDROID_SERIAL:-emulator-5554}"
readonly crash_loop_api_key="${CRASH_LOOP_API_KEY:-}"
readonly shared_prefs_path="shared_prefs/io.bitdrift.gradletestapp_preferences.xml"
readonly apk_path="platform/jvm/gradle-test-app/build/outputs/apk/debug/gradle-test-app-debug.apk"
readonly maestro_flow="tools/maestro/native-crash-loop.yaml"
readonly maestro_retries=3

if ! [[ "$iterations" =~ ^[0-9]+$ ]] || [[ "$iterations" -lt 1 ]]; then
echo "iterations must be a positive integer"
exit 1
fi

if [[ -z "$crash_loop_api_key" ]]; then
echo "CRASH_LOOP_API_KEY must be set"
exit 1
fi

if [[ ! -f "$apk_path" ]]; then
echo "Expected APK not found at $apk_path"
exit 1
fi

wait_for_emulator_ready() {
adb -s "$emulator_serial" wait-for-device

for _ in $(seq 1 45); do
local sys_boot_completed
local dev_boot_completed
local boot_anim

sys_boot_completed="$(adb -s "$emulator_serial" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
dev_boot_completed="$(adb -s "$emulator_serial" shell getprop dev.bootcomplete 2>/dev/null | tr -d '\r')"
boot_anim="$(adb -s "$emulator_serial" shell getprop init.svc.bootanim 2>/dev/null | tr -d '\r')"

if [[ "$sys_boot_completed" == "1" ]] &&
[[ "$dev_boot_completed" == "1" ]] &&
[[ "$boot_anim" == "stopped" ]] &&
adb -s "$emulator_serial" shell cmd package list packages >/dev/null 2>&1; then
return 0
fi

adb reconnect offline >/dev/null 2>&1 || true
sleep 2
done

echo "Timed out waiting for emulator package manager readiness"
return 1
}

seed_gradle_test_app_settings() {
local escaped_api_key
local prefs_xml

escaped_api_key="$(printf '%s' "$crash_loop_api_key" | sed -e 's/&/\&amp;/g' -e 's/"/\&quot;/g' -e "s/'/\\&apos;/g" -e 's/</\&lt;/g' -e 's/>/\&gt;/g')"
prefs_xml="$(cat <<EOF
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name='apiUrl'>https://api.bitdrift.dev</string>
<string name='api_key'>$escaped_api_key</string>
</map>
EOF
)"

printf '%s' "$prefs_xml" | adb -s "$emulator_serial" shell "run-as io.bitdrift.gradletestapp sh -c 'mkdir -p shared_prefs && cat > $shared_prefs_path'"
}

curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="$PATH:$HOME/.maestro/bin"
export MAESTRO_CLI_NO_ANALYTICS=1
export ANDROID_SERIAL="$emulator_serial"

adb start-server
wait_for_emulator_ready
adb -s "$emulator_serial" install -r "$apk_path"
adb -s "$emulator_serial" shell input keyevent 82 >/dev/null 2>&1 || true
seed_gradle_test_app_settings

for attempt in $(seq 1 "$maestro_retries"); do
echo "Running Maestro attempt $attempt/$maestro_retries"

if maestro test -e ITERATIONS="$iterations" "$maestro_flow"; then
exit 0
fi

if [[ "$attempt" -eq "$maestro_retries" ]]; then
exit 1
fi

echo "Maestro failed, restarting adb before retry"
adb kill-server || true
adb start-server
wait_for_emulator_ready
seed_gradle_test_app_settings
done
3 changes: 2 additions & 1 deletion tools/maestro/jvm-crash-loop.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
appId: io.bitdrift.gradletestapp
---
- repeat:
times: 50
times: ${ITERATIONS}
commands:
- back
- launchApp
- tapOn: "App Exits"
- tapOn: "JVM crash"
1 change: 1 addition & 0 deletions tools/maestro/native-crash-loop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ appId: io.bitdrift.gradletestapp
- repeat:
times: ${ITERATIONS}
commands:
- back
- launchApp
- tapOn: "App Exits"
- tapOn: "Native Crash"
Loading