Files
Ferrous-Solitaire/scripts/ANDROID_TESTING.md
T
funman300 becfda0f6c fix(android): auto-discover SDK/NDK in build script, strip native libs
build_android_apk.sh no longer requires all four env vars to be set
manually. It probes common SDK paths and uses the newest installed
build-tools/NDK/platform when vars are absent. Also adds llvm-strip
pass to strip debug symbols from .so files before packaging (controlled
by STRIP_NATIVE_LIBS, default 1), moves the debug keystore to a stable
target/android/debug.keystore path, and prints resolved paths at start.

Also adds scripts/ANDROID_TESTING.md and scripts/android_smoke.sh for
on-device smoke testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:05:31 -07:00

7.6 KiB
Raw Blame History

Android testing

This directory contains lightweight Android test helpers for Ferrous Solitaire. They are intended to run against either a physical Android device or an emulator connected through adb. When no device is connected the smoke script can automatically launch an AVD for you.

Prerequisites

  • Android SDK and NDK installed.
  • adb available on PATH.
  • One device/emulator visible in adb devices, or at least one AVD created (the script will launch one automatically if LAUNCH_AVD=1, which is the default).
  • If multiple devices are connected, set ADB_SERIAL to the target device serial.
  • Environment variables required by scripts/build_android_apk.sh when building:
export ANDROID_HOME=/path/to/android-sdk
export ANDROID_NDK_HOME=/path/to/android-ndk
export BUILD_TOOLS_VERSION=34.0.0
export PLATFORM=android-34

Smoke test

From the workspace root (Rusty_Solitaire/):

scripts/android_smoke.sh

The smoke test first checks whether adb can see a ready device. If no device is connected and LAUNCH_AVD=1 (default), it:

  1. locates the emulator binary under ANDROID_HOME or PATH,
  2. picks the first available AVD (or uses AVD_NAME),
  3. launches the emulator in the foreground (or headless with AVD_HEADLESS=1),
  4. waits for sys.boot_completed=1 before proceeding,
  5. dismisses the lock screen so the screenshot shows the app.

Once a device is ready (auto-launched or pre-existing) the script:

  1. builds the APK using scripts/build_android_apk.sh,
  2. installs it with adb install -r -d so debug smoke builds can replace newer local builds,
  3. force-stops the package by default for a clean launch,
  4. clears logcat,
  5. launches com.ferrousapp.solitaire/android.app.NativeActivity,
  6. waits for the app to settle,
  7. verifies the process is still running,
  8. captures a screenshot and logcat, and
  9. fails on fatal log patterns such as native crashes, JNI fatal errors, real ANRs, and Rust panics.

On exit the script kills any emulator it launched (SHUTDOWN_AVD_ON_EXIT=1 by default). Set SHUTDOWN_AVD_ON_EXIT=0 to keep the emulator open for inspection.

Artifacts are written to target/android-smoke/<timestamp>/ by default. A successful run includes:

  • device.txt — selected device and display metadata,
  • df-data-before.txt / df-data-after.txt — emulator/device storage snapshots,
  • emulator.log — stdout/stderr from the emulator process (AVD runs only),
  • emulator.pid — PID of the emulator process (AVD runs only),
  • launch.png — screenshot after the wait period,
  • logcat.txt — full captured log,
  • log-summary.txt — grep summary for warnings, errors, JNI, safe-area, and crash terms, and
  • pid.txt — running app process id.

Creating an AVD

If no AVDs exist, create one before running the smoke test:

# Install a system image
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
  'system-images;android-34;google_apis;x86_64'

# Create the AVD
"$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" create avd \
  -n Pixel_7_API_34 \
  -k 'system-images;android-34;google_apis;x86_64' \
  --device 'pixel_7'

Then run the smoke test — it will pick Pixel_7_API_34 automatically:

scripts/android_smoke.sh

Faster iteration

If you already built the APK and only want to reinstall/relaunch:

BUILD_APK=0 scripts/android_smoke.sh

If the APK is already installed and you only want to relaunch/capture logs:

BUILD_APK=0 INSTALL_APK=0 scripts/android_smoke.sh

By default the script force-stops the package before launch so logcat and screenshots represent a clean app start. To test warm-launch behavior instead:

BUILD_APK=0 INSTALL_APK=0 FORCE_STOP=0 scripts/android_smoke.sh

This is also useful when an already-installed build is good enough for launch/log checks. On install failure, the script writes adb-install.txt, storage snapshots, and installed-package diagnostics to the output directory.

If install fails with INSTALL_FAILED_UPDATE_INCOMPATIBLE, the smoke script uninstalls the package and retries once by default (RESET_ON_SIGNATURE_MISMATCH=1). This resets app data on the device/emulator. Disable it with:

RESET_ON_SIGNATURE_MISMATCH=0 scripts/android_smoke.sh

To write artifacts to a stable path:

OUT_DIR=target/android-smoke/latest scripts/android_smoke.sh

When reusing an output directory, previous files are removed by default so stale artifacts do not contaminate the latest result. To keep existing files:

CLEAN_OUT_DIR=0 OUT_DIR=target/android-smoke/latest scripts/android_smoke.sh

To target a specific device when more than one is attached:

ADB_SERIAL=emulator-5554 scripts/android_smoke.sh

To wait longer for safe-area inset polling or slow devices:

WAIT_SECS=8 scripts/android_smoke.sh

AVD options

To pick a specific AVD by name instead of auto-selecting the first one:

AVD_NAME=Pixel_7_API_34 scripts/android_smoke.sh

To run headless (no emulator window) — useful in CI or on a display-less machine:

AVD_HEADLESS=1 scripts/android_smoke.sh

To give a slow machine more time to boot the emulator (default is 120 s):

AVD_BOOT_TIMEOUT=180 scripts/android_smoke.sh

To keep the emulator running after the test (useful for manual inspection):

SHUTDOWN_AVD_ON_EXIT=0 scripts/android_smoke.sh

To pass extra flags to the emulator (e.g. disable snapshot for a completely cold boot, or change GPU mode):

AVD_EXTRA_ARGS="-gpu swiftshader_indirect" scripts/android_smoke.sh

To disable AVD auto-launch entirely and fail immediately if no device is connected:

LAUNCH_AVD=0 scripts/android_smoke.sh

For build-only validation without requiring a connected device, use the lower-level APK builder directly:

scripts/build_android_apk.sh

For smoke testing, scripts/android_smoke.sh defaults to the connected device's primary ABI when BUILD_APK=1, which keeps emulator APKs much smaller than the full multi-ABI default. You can still override it explicitly:

ABIS=x86_64 scripts/android_smoke.sh

For build-only validation, scripts/build_android_apk.sh still defaults to all configured ABIs unless you set ABIS yourself:

ABIS=x86_64 scripts/build_android_apk.sh

The APK builder signs debug builds with a persistent keystore at target/android/debug.keystore by default. This avoids signature churn across smoke-test runs.

The APK builder also strips native debug symbols by default before packaging (STRIP_NATIVE_LIBS=1). This keeps debug APKs installable on emulators with limited /data storage. To preserve native debug symbols for low-level debugging:

STRIP_NATIVE_LIBS=0 ABIS=x86_64 scripts/build_android_apk.sh

Device checklist

The script is only a smoke test. Before shipping Android builds, also verify:

  • safe-area insets arrive and shift the HUD after a few seconds,
  • HUD does not overlap the top status bar,
  • modal Done buttons are above the gesture/navigation bar,
  • stock tap works,
  • drag-and-drop works on tableau, waste, and foundation piles,
  • Settings/Help/Profile modals open and close,
  • login tokens persist after app restart, and
  • target/android-smoke/.../logcat.txt contains no fatal JNI/native crash output.

Notes

  • adb shell input tap X Y uses physical pixels, not Bevy logical pixels.
  • The projects common test device mapping is physical 1080×2400, Bevy logical 900×2000, scale factor 1.20; multiply logical coordinates by 1.20 for scripted adb shell input commands on that device.
  • Keep generated screenshots/logs under target/android-smoke/ so they stay out of source control.