mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-12 09:28:09 +00:00
Compose UI tests that drive the real app against local SMP servers and CLI peers: - harness for desktop (in-process skiko scene, paused clock, parallel forks) and Android (instrumentation on any adb device or Waydroid, CLI peers through a host bridge); - 112 tests shared by both platforms, 25 desktop-only, 15 Android-only; - app code: test keys on the elements tests act on, and idle tracking of API calls and events, both inactive unless test hooks are enabled (debug Android builds, desktop tests); - runners scripts/uitest.sh and scripts/uitest-android.sh, uitest-servers executable (cabal flag uitest); - plans/ui-tests: the desktop and Android plans, the P1a start page, the Android research log, and the test catalogue with its review notes and scripts.
132 lines
7.3 KiB
Bash
Executable File
132 lines
7.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# Usage: scripts/uitest-android.sh <app.apk> <androidTest.apk> [am instrument -e args...]
|
|
# Needs scripts/uitest.sh to have run once (it builds the CLI and test servers and writes build/uitest/env.properties).
|
|
# Env:
|
|
# UITEST_SERIAL adb serial of the device or emulator to use; when unset, a Waydroid instance is claimed with wd.sh from PATH
|
|
# WD_INSTANCE Waydroid instance (default 2), WD_OWNER claim owner (default uitest-$USER)
|
|
# UITEST_APP_ID application id of the app APK (default chat.simplex.app.debug)
|
|
# ADB, JAVA_HOME tool locations (default: adb from ANDROID_HOME, ANDROID_SDK_ROOT or PATH; java from JAVA_HOME or PATH)
|
|
set -eu
|
|
sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
|
ADB="${ADB:-$( [ -n "$sdk" ] && [ -x "$sdk/platform-tools/adb" ] && echo "$sdk/platform-tools/adb" || echo adb )}"
|
|
adb() { "$ADB" "$@"; }
|
|
app_apk=$1; test_apk=$2; shift 2
|
|
export WD_INSTANCE="${WD_INSTANCE:-2}" WD_OWNER="${WD_OWNER:-uitest-$(id -un)}"
|
|
pkg="${UITEST_APP_ID:-chat.simplex.app.debug}"
|
|
runner=$pkg.test/androidx.test.runner.AndroidJUnitRunner
|
|
out="$(dirname "$(readlink -f "$0")")/../build/uitest-android/run-$(date +%Y%m%d-%H%M%S)"
|
|
mkdir -p "$out"
|
|
t0=$(date +%s)
|
|
step() { echo "uitest-android: [$(( $(date +%s) - t0 )) s] $*"; }
|
|
|
|
mp="$(dirname "$(readlink -f "$0")")/.."
|
|
bridge_log="$out/bridge.log"
|
|
avail_mb=$(awk '/^MemAvailable:/ {print int($2 / 1024)}' /proc/meminfo)
|
|
[ "$avail_mb" -ge 4500 ] || { echo "uitest-android: only $avail_mb MB available, need 4500 (bridge + Waydroid); free memory first" >&2; exit 1; }
|
|
java="${JAVA_HOME:+$JAVA_HOME/bin/}java"
|
|
[ -s "$mp/build/uitest/env.properties" ] || { echo "uitest-android: run scripts/uitest.sh once first; it builds the CLI and test servers and writes build/uitest/env.properties" >&2; exit 1; }
|
|
cp_file="$mp/build/uitest/bridge.classpath"
|
|
h="$mp/common/src/desktopTest/kotlin/chat/simplex/app/ui/harness"
|
|
bridge_srcs="$h/UiBridge.kt $h/CliPeer.kt $h/VtScreen.kt $h/TestServers.kt $h/UiEnv.kt $h/UiJvm.kt $mp/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/ServersConfig.kt $mp/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/PeerLines.kt"
|
|
wd_pid=
|
|
cleanup() {
|
|
[ -z "${bridge_pid:-}" ] || kill -TERM "$bridge_pid" 2>/dev/null || true
|
|
[ -z "$wd_pid" ] || wait "$wd_pid" 2>/dev/null || true
|
|
[ -n "${UITEST_SERIAL:-}" ] || wd.sh release >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
if [ ! -s "$cp_file" ] || [ -n "$(find $bridge_srcs -newer "$cp_file" 2>/dev/null)" ]; then
|
|
step "compiling the bridge"
|
|
(cd "$mp" && ./gradlew -g "$mp/.gradle/uitest-home" --no-daemon -q -Pkotlin.compiler.execution.strategy=in-process :common:uitestBridgeClasspath) > "$out/bridge-build.log" 2>&1 \
|
|
|| { echo "uitest-android: bridge build failed; see $out/bridge-build.log" >&2; exit 1; }
|
|
fi
|
|
if [ -z "${UITEST_SERIAL:-}" ]; then
|
|
wd.sh claim "$WD_OWNER"
|
|
if ! wd.sh status | grep -q "Session:.*RUNNING"; then
|
|
wd.sh up > "$out/waydroid-up.log" 2>&1 &
|
|
wd_pid=$!
|
|
fi
|
|
fi
|
|
setsid "$java" -Djava.awt.headless=true -Dsimplex.uitest.env="$mp/build/uitest/env.properties" -cp "$(cat "$cp_file")" chat.simplex.app.ui.harness.UiBridge 7777 > "$bridge_log" 2>&1 &
|
|
bridge_pid=$!
|
|
i=0
|
|
until grep -q "^uitest-bridge: smp" "$bridge_log" 2>/dev/null; do
|
|
i=$((i + 1)); [ "$i" -lt 1200 ] || { echo "uitest-android: bridge did not start; see $bridge_log" >&2; exit 1; }
|
|
kill -0 "$bridge_pid" 2>/dev/null || { echo "uitest-android: bridge exited; see $bridge_log" >&2; exit 1; }
|
|
sleep 0.1
|
|
done
|
|
base=$(sed -n 's/^uitest-bridge: .*port base \([0-9]*\),.*/\1/p' "$bridge_log")
|
|
step "bridge up, server port base $base"
|
|
|
|
if [ -n "$wd_pid" ]; then
|
|
wait "$wd_pid" || { echo "uitest-android: wd.sh up failed; see $out/waydroid-up.log" >&2; wd_pid=; exit 1; }
|
|
wd_pid=
|
|
fi
|
|
serial="${UITEST_SERIAL:-$(wd.sh serial)}"
|
|
step "device $serial ready"
|
|
echo "uitest-android: note: pm clear removes the app's data on $serial"
|
|
app_sum=$(sha256sum "$app_apk" | cut -c1-16)
|
|
stamp="$mp/build/uitest-android/installed-app-$(printf %s "$serial" | tr -c "A-Za-z0-9" _)"
|
|
installed_ver=$(adb -s "$serial" shell dumpsys package "$pkg" 2>/dev/null | tr -d '\r' | sed -n 's/^ *lastUpdateTime=//p' | head -1)
|
|
if [ -n "$installed_ver" ] && [ "$(cat "$stamp" 2>/dev/null)" = "$app_sum $installed_ver" ]; then
|
|
echo "uitest-android: app APK unchanged on $serial, skipping its install"
|
|
else
|
|
if [ -n "${UITEST_SERIAL:-}" ]; then adb -s "$serial" install -r -g "$app_apk" >/dev/null; else wd.sh install "$app_apk"; fi
|
|
installed_ver=$(adb -s "$serial" shell dumpsys package "$pkg" | tr -d '\r' | sed -n 's/^ *lastUpdateTime=//p' | head -1)
|
|
echo "$app_sum $installed_ver" > "$stamp"
|
|
fi
|
|
adb -s "$serial" install -r -t "$test_apk"
|
|
step "APKs installed"
|
|
for p in 7777 $((base + 1)) $((base + 2)); do adb -s "$serial" reverse "tcp:$p" "tcp:$p" >/dev/null; done
|
|
prepare_app() {
|
|
adb -s "$serial" shell pm clear "$pkg" >/dev/null
|
|
for perm in android.permission.POST_NOTIFICATIONS android.permission.CAMERA android.permission.RECORD_AUDIO; do
|
|
adb -s "$serial" shell pm grant "$pkg" "$perm" 2>/dev/null || true
|
|
done
|
|
}
|
|
# Prints one line per test in an `am instrument -r` output: "<class>#<test> <code>" for finished tests (0 pass, -2 fail, -3/-4 skipped)
|
|
# and "<class>#<test> crashed" for a test that started without finishing.
|
|
results() {
|
|
tr -d '\r' < "$1" | awk '
|
|
/^INSTRUMENTATION_STATUS: class=/ { c = substr($0, 31) }
|
|
/^INSTRUMENTATION_STATUS: test=/ { t = substr($0, 30) }
|
|
/^INSTRUMENTATION_STATUS_CODE: / { code = $2; if (code == 1) { running = c "#" t } else { print c "#" t, code; running = "" } }
|
|
END { if (running != "") print running, "crashed" }'
|
|
}
|
|
shots=/sdcard/Android/data/$pkg/files/failures
|
|
adb -s "$serial" shell rm -rf "$shots" || true
|
|
adb -s "$serial" logcat -G 16M >/dev/null 2>&1 || true
|
|
adb -s "$serial" logcat -c || true
|
|
: > "$out/results.txt"
|
|
pass=1
|
|
gave_up=
|
|
while :; do
|
|
prepare_app
|
|
skip=$(cut -d' ' -f1 "$out/results.txt" | paste -sd, -)
|
|
set +e
|
|
if [ -n "$skip" ]; then
|
|
adb -s "$serial" shell am instrument -w -r "$@" -e notClass "$skip" "$runner" > "$out/instrument-$pass.txt" 2>&1
|
|
else
|
|
adb -s "$serial" shell am instrument -w -r "$@" "$runner" > "$out/instrument-$pass.txt" 2>&1
|
|
fi
|
|
rc=$?
|
|
set -e
|
|
results "$out/instrument-$pass.txt" >> "$out/results.txt"
|
|
adb -s "$serial" pull "$shots" "$out/" >/dev/null 2>&1 || true
|
|
step "instrumentation pass $pass finished (am instrument exit $rc)"
|
|
tr -d '\r' < "$out/instrument-$pass.txt" | grep -q "shortMsg=Process crashed" || break
|
|
[ "$pass" -lt 4 ] || { echo "uitest-android: process crashed in 4 passes, giving up" >&2; gave_up=1; break; }
|
|
pass=$((pass + 1))
|
|
echo "uitest-android: the app process crashed; resuming with the remaining tests (pass $pass)"
|
|
done
|
|
cat "$out"/instrument-*.txt > "$out/instrument.txt"
|
|
adb -s "$serial" logcat -d > "$out/logcat.txt" 2>&1 || true
|
|
grep -E "INSTRUMENTATION_STATUS: stack=" "$out/instrument.txt" | cut -c1-1500 || true
|
|
grep -E "UITEST|TestRunner: (started|finished|failed)" "$out/logcat.txt" | sed "s/^[0-9-]* //" > "$out/timings.txt" || true
|
|
echo "uitest-android: results ($out/results.txt; 0 pass, -2 fail, -3/-4 skipped):"
|
|
sed 's/^chat\.simplex\.app\.ui\.//' "$out/results.txt"
|
|
echo "uitest-android: $(grep -c ' 0$' "$out/results.txt") passed, $(grep -c ' -2$' "$out/results.txt") failed, $(grep -c ' crashed$' "$out/results.txt") crashed; results in $out"
|
|
[ -z "$gave_up" ] && ! grep -qE ' (-2|crashed)$' "$out/results.txt" && [ -s "$out/results.txt" ]
|