An AAR for phones, tablets and televisions whose applications are written in Kotlin (extensions/native-sink.md): a JNI layer over wai_sink_* (every status carried to Kotlin with libwai's message, strings passed as UTF-8 bytes, a NUL inside one refused); libwai and that layer built by cargo and the pinned NDK (29.0.14206865) for arm64-v8a, armeabi-v7a and x86_64, linked for 16 KiB pages and checked for it and for every exported method; the wrapper, the loopback relay (128-bit path token, 502 for anything refused, ranges from what verified, content codings removed), the observation loop, the collector client; the player session over the platform's media player library and the attention signals from the activity's lifecycle, the window's and display's state, the view's place in its window and the window's input. The platform-free half also compiles for the host JVM, so its unit tests drive the JNI layer over a host build of libwai: the corpus live-conformance/native-sink (every case, every refused configuration, the page-parity bundle byte for byte), the relay over the reference floor, and the loop's hidden and idle endings. The instrumented tests run in a headless emulator: the corpus and the relay on the device, the floor played through the sink to its end, a changed fragment refused on every retry, a session nobody can see ended hidden, and the attention signals from a real activity. Pinned: AGP 8.13.0, Gradle 8.13 (by SHA-256), Kotlin 2.3.21, JDK 17, compileSdk 36, minSdk 24, the media player library 1.11.1. Co-Authored-By: Claude Opus 5.5 <[email protected]>
86 lines
4.4 KiB
Shell
86 lines
4.4 KiB
Shell
#!/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 ]
|