#!/usr/bin/env bash # Run the instrumented tests in an emulator, headless: make the test device if it is missing # (its own directory, WAI_SINK_AVD_HOME), boot it with no window, run :sink:connectedDebugAndroidTest # against it alone, and shut it down. Build the Android libraries first (build-native.sh android). # # The device's ABI is the host's: arm64-v8a on an arm64 host, so the tests run the AAR's # arm64-v8a libraries. The emulator takes a console port nothing else holds and runs read-only, so # two runs at once do not collide; its adb server listens on a port of its own, so other devices on # the host are never touched, and the tests run against this device alone. A run that did not # happen is not a pass: the script fails unless the results name at least one test and every one # passed. set -uo pipefail . "$(dirname "${BASH_SOURCE[0]}")/env.sh" [ -n "${ANDROID_HOME:-}" ] || die "no Android SDK: set ANDROID_HOME" case "$(uname -m)" in arm64|aarch64) abi=arm64-v8a ;; *) abi=x86_64 ;; esac IMAGE="${WAI_SINK_SYSTEM_IMAGE:-system-images;android-36.1;google_apis_playstore;$abi}" NAME="${WAI_SINK_AVD:-wai-sink-api36}" export ANDROID_AVD_HOME="${WAI_SINK_AVD_HOME:-$HOME/.android/wai-sink-avd}" free() { ! (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; } PORT="${WAI_SINK_EMULATOR_PORT:-}" if [ -z "$PORT" ]; then for p in 5584 5582 5580 5578 5576 5574 5572 5570; do if free "$p" && free "$((p + 1))"; then PORT=$p; break; fi done [ -n "$PORT" ] || die "no free emulator console port" fi export ANDROID_ADB_SERVER_PORT="${WAI_SINK_ADB_PORT:-$((PORT + 10000))}" SERIAL="emulator-$PORT" ADB="$ANDROID_HOME/platform-tools/adb" OUT="$WAI_SINK_DIR/sink/build/outputs/androidTest-results/connected" [ -d "$ANDROID_HOME/$(echo "$IMAGE" | tr ';' '/')" ] || die "the system image $IMAGE is not installed" mkdir -p "$ANDROID_AVD_HOME" if [ ! -f "$ANDROID_AVD_HOME/$NAME.ini" ]; then echo no | "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" -s create avd -n "$NAME" -k "$IMAGE" -d pixel_6 >/dev/null \ || die "could not make the device $NAME from $IMAGE" fi LOG="${TMPDIR:-/tmp}/wai-sink-emulator-$PORT.log" "$ANDROID_HOME/emulator/emulator" -avd "$NAME" -port "$PORT" -no-window -no-audio -no-boot-anim -no-snapshot \ -read-only -gpu swiftshader_indirect -memory 3072 >"$LOG" 2>&1 & EMU=$! cleanup() { "$ADB" -s "$SERIAL" emu kill >/dev/null 2>&1 for _ in 1 2 3 4 5 6 7 8 9 10; do kill -0 "$EMU" 2>/dev/null || break; sleep 1; done kill "$EMU" 2>/dev/null "$ADB" kill-server >/dev/null 2>&1 } trap cleanup EXIT booted=0 for _ in $(seq 1 120); do kill -0 "$EMU" 2>/dev/null || { tail -20 "$LOG"; die "the emulator exited while booting"; } if [ "$("$ADB" -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = 1 ]; then booted=1; break; fi sleep 5 done [ $booted = 1 ] || { tail -20 "$LOG"; die "$SERIAL did not boot within 10 minutes"; } # The test device's own settings: no animations, the screen kept on and unlocked. for k in window_animation_scale transition_animation_scale animator_duration_scale; do "$ADB" -s "$SERIAL" shell settings put global "$k" 0 >/dev/null 2>&1 done "$ADB" -s "$SERIAL" shell svc power stayon true >/dev/null 2>&1 "$ADB" -s "$SERIAL" shell input keyevent KEYCODE_WAKEUP >/dev/null 2>&1 "$ADB" -s "$SERIAL" shell wm dismiss-keyguard >/dev/null 2>&1 echo "$SERIAL booted: $("$ADB" -s "$SERIAL" shell getprop ro.build.version.release | tr -d '\r') ($("$ADB" -s "$SERIAL" shell getprop ro.product.cpu.abi | tr -d '\r'))" rm -rf "$OUT" ANDROID_SERIAL="$SERIAL" bash "$WAI_SINK_DIR/scripts/gradle.sh" :sink:connectedDebugAndroidTest "$@" rc=$? # What ran, from the results the run wrote. python3 -I - "$OUT" <<'PY' import sys, pathlib, xml.etree.ElementTree as ET files = list(pathlib.Path(sys.argv[1]).rglob("*.xml")) tests = fails = skips = 0 for f in files: for s in ET.parse(f).getroot().iter("testsuite"): tests += int(s.get("tests", 0)); fails += int(s.get("failures", 0)) + int(s.get("errors", 0)); skips += int(s.get("skipped", 0)) for c in ET.parse(f).getroot().iter("testcase"): bad = c.find("failure") is not None or c.find("error") is not None print(("FAIL " if bad else "ok ") + c.get("classname", "").split(".")[-1] + "." + c.get("name", "")) print(f"emulator tests: {tests} run, {fails} failed, {skips} skipped") sys.exit(0 if tests > 0 and fails == 0 and skips == 0 else 1) PY prc=$? [ $rc = 0 ] && [ $prc = 0 ]