mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-05 17:07:50 +00:00
- Tests for connection plans, prepared chats, edit/delete/selection, forwarding, drafts, group preferences and admission, group lifecycle, compose, live messages, item info, reports and moderation, privacy and SimpleX Lock, chat data and database, address and contact preferences, profiles, contact info, unread and search, files and reactions, group info and events, chat shell and statuses, joining, support and business chats, settings and appearance, network and servers, channels, remote desktop, Android intents and notifications. - Harness: server stop/start, smp2/xftp2, SOCKS stub, CLI chat relays, CLI remote host/controller, chat scroll and drag helpers, file chooser answers, clipboard in popups. - ui-tests workflow (manual dispatch only) running both suites on GitHub runners. - App findings recorded in plan section 15.5 (items 7-23), each covered by a test marked @KnownAppIssue.
155 lines
8.5 KiB
Bash
Executable File
155 lines
8.5 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, or `scripts/uitest.sh --prepare-only` (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 scripts/waydroid/wd.sh
|
|
# WD_SH another wd.sh to use (default scripts/waydroid/wd.sh; see scripts/waydroid/README.md)
|
|
# 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: the adb wd.sh uses, `wd.sh adb-path`; java from JAVA_HOME or PATH)
|
|
set -eu
|
|
mp="$(dirname "$(readlink -f "$0")")/.."
|
|
wd="${WD_SH:-$mp/scripts/waydroid/wd.sh}"
|
|
ADB="${ADB:-$("$wd" adb-path)}"
|
|
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] $*"; }
|
|
|
|
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 (or scripts/uitest.sh --prepare-only) 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/Socks5Stub.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" 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" claim "$WD_OWNER"
|
|
if ! "$wd" status | grep -q "Session:.*RUNNING"; then
|
|
"$wd" 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" 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" 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)) $((base + 11)) $((base + 12)) $((base + 20)) $((base + 90)); do adb -s "$serial" reverse "tcp:$p" "tcp:$p" >/dev/null; done
|
|
prepare_app() {
|
|
adb -s "$serial" shell pm clear "$pkg" >/dev/null
|
|
adb -s "$serial" shell settings put global heads_up_notifications_enabled 0
|
|
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
|
|
}
|
|
# Runs `am instrument` into $1. If its output does not change for UITEST_STALL_S seconds (default 420, longer than the
|
|
# in-process 300 s watchdog), the app process is force-stopped so the pass ends as "Process crashed" and the run resumes:
|
|
# under arm64 translation the native allocator can deadlock (scudo::HybridMutex::lockSlow in several threads), which
|
|
# blocks every thread of the process, the in-process watchdog included (scripts/waydroid/README.md).
|
|
instrument() {
|
|
file=$1; shift
|
|
adb -s "$serial" shell am instrument -w -r "$@" "$runner" > "$file" 2>&1 &
|
|
ipid=$!
|
|
last=-1; idle=0; stall="${UITEST_STALL_S:-420}"
|
|
while kill -0 "$ipid" 2>/dev/null; do
|
|
sleep 15
|
|
size=$(wc -c < "$file")
|
|
if [ "$size" = "$last" ]; then idle=$((idle + 15)); else idle=0; last=$size; fi
|
|
if [ "$idle" -ge "$stall" ]; then
|
|
step "no instrumentation output for $idle s; force-stopping $pkg (stalled process)"
|
|
adb -s "$serial" shell am force-stop "$pkg" >/dev/null 2>&1
|
|
idle=0
|
|
fi
|
|
done
|
|
wait "$ipid"
|
|
}
|
|
# 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
|
|
instrument "$out/instrument-$pass.txt" "$@" -e notClass "$skip"
|
|
else
|
|
instrument "$out/instrument-$pass.txt" "$@"
|
|
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 "^INSTRUMENTATION_RESULT: shortMsg=" || 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" ]
|