mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
* feat: tools/device-smoke.sh checks a build on a real device over plain adb Installs and launches the app, changes the density and the window size, cycles a foldable's device state, then reads the newest debug session (first frame, last frame rate, memory page size, process exits). It restores the display and the device state it changed. No python needed; complements tools/droiddeck-scenario, which drives games. * fix: device-smoke.sh restores only what it changed and checks more carefully Restore the device state by cancelling the override the script made, or by putting back the override that was already pinned (a test lab pins one), instead of pinning the committed state. Restore density and size only after the script changed them. Keep adb's exit status, count only crashes of the tested app and fail when the crash buffer cannot be read, compare the top activity after the density change, and exit 2 when an option value is missing. * fix: check wm density/size exit statuses and keep restore flags on failure * fix: verify smoke-test display changes and restoration * fix: reject false smoke-test launches and quote remote paths * Run final display cleanup before reporting smoke results --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
288 lines
12 KiB
Bash
Executable File
288 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Smoke test of a DroidDeck build on a real device, over plain adb (no python, no root).
|
|
#
|
|
# What it does, in order, and restores afterwards:
|
|
# 1. prints the device (model, Android, kernel, page size, SoC);
|
|
# 2. installs --apk when given and launches the app;
|
|
# 3. checks that the app starts and that nothing in the crash buffer belongs to it;
|
|
# 4. changes the display density and the window size and checks the app keeps its process
|
|
# (and does not recreate the activity that is on top);
|
|
# 5. on a foldable cycles the device state (half-opened, and with --closed also closed) through
|
|
# `cmd device_state`, then puts the original state back;
|
|
# 6. reads the newest session folder of a debug build: first frame, last frame rate, the
|
|
# memory page size and the last process exits.
|
|
#
|
|
# Usage: tools/device-smoke.sh [--serial SERIAL] [--apk FILE] [--pkg PACKAGE]
|
|
# [--no-density] [--no-size] [--no-fold] [--closed]
|
|
# Exit status: 0 when no check failed, 1 when one did, 2 for a usage or device problem.
|
|
#
|
|
# Not a replacement for tools/droiddeck-scenario, which drives a game through the agent bridge;
|
|
# this covers what that does not: display changes and foldable states.
|
|
set -u
|
|
export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'
|
|
|
|
pkg=com.droiddeck.launcher
|
|
serial=${ANDROID_SERIAL:-}
|
|
apk=
|
|
do_density=1
|
|
do_size=1
|
|
do_fold=1
|
|
do_closed=0
|
|
adb_bin=${ADB:-adb}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case $1 in
|
|
--serial) [ $# -ge 2 ] || { echo "--serial needs a value" >&2; exit 2; }; serial=$2; shift ;;
|
|
--apk) [ $# -ge 2 ] || { echo "--apk needs a value" >&2; exit 2; }; apk=$2; shift ;;
|
|
--pkg) [ $# -ge 2 ] || { echo "--pkg needs a value" >&2; exit 2; }; pkg=$2; shift ;;
|
|
--no-density) do_density=0 ;;
|
|
--no-size) do_size=0 ;;
|
|
--no-fold) do_fold=0 ;;
|
|
--closed) do_closed=1 ;;
|
|
-h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "unknown option: $1" >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
adb_cmd=("$adb_bin")
|
|
[ -n "$serial" ] && adb_cmd+=(-s "$serial")
|
|
A() {
|
|
local out rc
|
|
out=$("${adb_cmd[@]}" "$@" 2>&1); rc=$?
|
|
[ -z "$out" ] || printf '%s\n' "$out" | tr -d '\r'
|
|
return $rc
|
|
}
|
|
sh_() {
|
|
local command= arg
|
|
for arg in "$@"; do command+="'${arg//\'/\'\\\'\'}' "; done
|
|
A shell "$command"
|
|
}
|
|
|
|
pass=0 fail=0 skip=0
|
|
ok() { echo " PASS $*"; pass=$((pass + 1)); }
|
|
bad() { echo " FAIL $*"; fail=$((fail + 1)); }
|
|
note() { echo " SKIP $*"; skip=$((skip + 1)); }
|
|
head_() { printf '\n== %s\n' "$*"; }
|
|
|
|
state=$(A get-state)
|
|
if [ "$state" != "device" ]; then
|
|
echo "no authorized device (adb get-state: $state)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
density_snapshot=$(sh_ wm density) || density_snapshot=
|
|
size_snapshot=$(sh_ wm size) || size_snapshot=
|
|
orig_density=$(echo "$density_snapshot" | sed -n 's/.*Physical density: \([0-9]*\).*/\1/p' | head -1)
|
|
override_density=$(echo "$density_snapshot" | sed -n 's/.*Override density: \([0-9]*\).*/\1/p' | head -1)
|
|
override_size=$(echo "$size_snapshot" | sed -n 's/.*Override size: \([0-9]*x[0-9]*\).*/\1/p' | head -1)
|
|
ds=$(sh_ cmd device_state state 2>/dev/null)
|
|
orig_state=$(echo "$ds" | sed -n "s/.*Committed state: DeviceState{identifier=\([0-9]*\),.*/\1/p" | head -1)
|
|
pre_override=$(echo "$ds" | sed -n "s/.*Override state: DeviceState{identifier=\([0-9]*\),.*/\1/p" | head -1)
|
|
|
|
density_changed=0 size_changed=0 fold_changed=0
|
|
wm_override() { sed -n "s/.*Override $1: \\([0-9x]*\\).*/\\1/p" | head -1; }
|
|
restore_density() {
|
|
sh_ wm density "${override_density:-reset}" >/dev/null || return 1
|
|
local current
|
|
current=$(sh_ wm density) || return 1
|
|
echo "$current" | grep -q 'Physical density:' || return 1
|
|
[ "$(echo "$current" | wm_override density)" = "$override_density" ]
|
|
}
|
|
restore_size() {
|
|
sh_ wm size "${override_size:-reset}" >/dev/null || return 1
|
|
local current
|
|
current=$(sh_ wm size) || return 1
|
|
echo "$current" | grep -q 'Physical size:' || return 1
|
|
[ "$(echo "$current" | wm_override size)" = "$override_size" ]
|
|
}
|
|
# An override that was already in place (a test lab pins one) is put back; otherwise the request is cancelled.
|
|
restore_fold() { if [ -n "$pre_override" ]; then sh_ cmd device_state state "$pre_override" >/dev/null; else sh_ cmd device_state state reset >/dev/null; fi; }
|
|
|
|
restore() {
|
|
local failed=0
|
|
if [ "$density_changed" = 1 ]; then
|
|
if restore_density; then density_changed=0; else bad "could not restore display density"; failed=1; fi
|
|
fi
|
|
if [ "$size_changed" = 1 ]; then
|
|
if restore_size; then size_changed=0; else bad "could not restore window size"; failed=1; fi
|
|
fi
|
|
if [ "$fold_changed" = 1 ]; then
|
|
if restore_fold; then fold_changed=0; else bad "could not restore fold override"; failed=1; fi
|
|
fi
|
|
return "$failed"
|
|
}
|
|
finish() {
|
|
local status=$?
|
|
restore || status=1
|
|
printf '\n%d passed, %d failed, %d skipped\n' "$pass" "$fail" "$skip"
|
|
[ "$fail" = 0 ] || status=1
|
|
exit "$status"
|
|
}
|
|
trap finish EXIT
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
top_activity() {
|
|
sh_ dumpsys activity activities 2>/dev/null | sed -n 's/.*topResumedActivity=ActivityRecord{[^ ]* u[0-9]* \([^ ]*\) .*/\1/p' | head -1
|
|
}
|
|
app_pid() { sh_ pidof "$pkg" | awk '{print $1}'; }
|
|
app_crashes() {
|
|
local out pat
|
|
out=$(sh_ logcat -b crash -d) || return 1
|
|
pat=$(printf '%s' "$pkg" | sed 's/[.[\*^$]/\\&/g')
|
|
printf '%s\n' "$out" | grep -c -E "(Process: |Cmdline: |>>> )$pat([ ,<:]|$)" || true
|
|
}
|
|
relaunches_of() { # $1 = activity class fragment
|
|
sh_ logcat -b events -d 2>/dev/null | grep -E 'wm_(relaunch|destroy|create)_activity' | grep -c "$1"
|
|
}
|
|
|
|
head_ "Device"
|
|
echo " model $(sh_ getprop ro.product.manufacturer) $(sh_ getprop ro.product.model)"
|
|
echo " android $(sh_ getprop ro.build.version.release) (API $(sh_ getprop ro.build.version.sdk)), One UI $(sh_ getprop ro.build.version.oneui)"
|
|
echo " kernel $(sh_ uname -r)"
|
|
page=$(sh_ getconf PAGE_SIZE)
|
|
echo " page size $page"
|
|
echo " soc $(sh_ getprop ro.soc.manufacturer) $(sh_ getprop ro.soc.model) ($(sh_ getprop ro.board.platform))"
|
|
echo " gpu $(sh_ cat /sys/class/kgsl/kgsl-3d0/gpu_model 2>/dev/null)"
|
|
echo " display $(sh_ wm size | tr '\n' ' ') $(sh_ wm density | tr '\n' ' ')"
|
|
[ "$page" = 4096 ] || [ "$page" = 16384 ] && ok "page size is $page" || bad "page size reads '$page'"
|
|
|
|
head_ "Install and launch"
|
|
sh_ logcat -b crash -c >/dev/null
|
|
sh_ logcat -b events -c >/dev/null
|
|
if [ -n "$apk" ]; then
|
|
r=$(A install -r -t "$apk" | tail -1)
|
|
case $r in *Success*) ok "installed $(basename "$apk")" ;; *) bad "install: $r"; exit 1 ;; esac
|
|
fi
|
|
packages=$(sh_ pm list packages "$pkg") || { echo "could not query installed packages" >&2; exit 2; }
|
|
if ! printf '%s\n' "$packages" | grep -Fxq "package:$pkg"; then echo "package $pkg is not installed" >&2; exit 2; fi
|
|
if ! launch=$(sh_ am start -n "$pkg/.MainActivity") || printf '%s\n' "$launch" | grep -q '^Error'; then
|
|
bad "could not launch $pkg/.MainActivity"
|
|
fi
|
|
sleep 6
|
|
pid=$(app_pid)
|
|
[ -n "$pid" ] && ok "process running (pid $pid)" || bad "the app has no process after launch"
|
|
t=$(top_activity)
|
|
case $t in
|
|
"$pkg"/*|*permissioncontroller*|*GrantPermissions*) ok "top activity: $t" ;;
|
|
*) bad "top activity is '$t'" ;;
|
|
esac
|
|
if c=$(app_crashes); then
|
|
[ "${c:-0}" = 0 ] && ok "nothing in the crash buffer belongs to the app" || bad "$c crash entries mention the app"
|
|
else
|
|
bad "could not read the crash buffer"
|
|
fi
|
|
|
|
if [ "$do_density" = 1 ] && [ -n "$orig_density" ]; then
|
|
head_ "Density change"
|
|
target=$((orig_density * 5 / 4))
|
|
before=$(top_activity); pid0=$(app_pid)
|
|
sh_ logcat -b events -c >/dev/null
|
|
density_changed=1
|
|
wm_status=0
|
|
sh_ wm density "$target" >/dev/null || wm_status=$?
|
|
sleep 6
|
|
applied=$(sh_ wm density) || wm_status=1
|
|
after=$(top_activity); pid1=$(app_pid)
|
|
if [ "$wm_status" -ne 0 ] || [ "$(echo "$applied" | wm_override density)" != "$target" ]; then
|
|
bad "wm density failed"
|
|
else
|
|
if [ -n "$pid1" ] && [ "$pid0" = "$pid1" ]; then
|
|
ok "same process after $orig_density -> $target dpi"
|
|
else
|
|
bad "process changed ($pid0 -> $pid1)"
|
|
fi
|
|
case $before in "$pkg"/*)
|
|
n=$(relaunches_of "${before#*/}")
|
|
[ "${n:-0}" = 0 ] && ok "$before was not recreated" || bad "$before was recreated ${n}x"
|
|
[ "$after" = "$before" ] && ok "same top activity after the change" || bad "top activity changed ($before -> $after)"
|
|
;; *) note "top activity was not the app ($before)" ;; esac
|
|
fi
|
|
if restore_density; then
|
|
density_changed=0
|
|
else
|
|
bad "could not restore display density"
|
|
fi
|
|
sleep 3
|
|
elif [ "$do_density" = 1 ]; then
|
|
note "could not read the original display density"
|
|
fi
|
|
|
|
if [ "$do_size" = 1 ]; then
|
|
head_ "Window size change"
|
|
phys=$(echo "$size_snapshot" | sed -n 's/.*Physical size: \([0-9]*\)x\([0-9]*\).*/\1 \2/p' | head -1)
|
|
set -- $phys
|
|
if [ -n "${1:-}" ]; then
|
|
w=$(( ($2 * 2 / 3) / 2 * 2 )); h=$1 # a narrower, taller window, like a cover screen
|
|
pid0=$(app_pid)
|
|
size_changed=1
|
|
wm_status=0
|
|
sh_ wm size "${w}x${h}" >/dev/null || wm_status=$?
|
|
sleep 6
|
|
applied=$(sh_ wm size) || wm_status=1
|
|
pid1=$(app_pid)
|
|
if [ "$wm_status" -ne 0 ] || [ "$(echo "$applied" | wm_override size)" != "${w}x${h}" ]; then
|
|
bad "wm size failed"
|
|
elif [ -n "$pid1" ] && [ "$pid0" = "$pid1" ]; then
|
|
ok "same process at ${w}x${h}"
|
|
else
|
|
bad "process changed ($pid0 -> $pid1)"
|
|
fi
|
|
if restore_size; then
|
|
size_changed=0
|
|
else
|
|
bad "could not restore window size"
|
|
fi
|
|
sleep 3
|
|
else
|
|
note "could not read the physical size"
|
|
fi
|
|
fi
|
|
|
|
if [ "$do_fold" = 1 ]; then
|
|
head_ "Foldable states"
|
|
states=$(sh_ cmd device_state print-states 2>/dev/null)
|
|
if ! echo "$states" | grep -q "identifier"; then
|
|
note "no device_state service (not a foldable, or not permitted)"
|
|
elif [ -z "$orig_state" ]; then
|
|
note "could not read the current device_state identifier"
|
|
else
|
|
id_of() { echo "$states" | sed -n "s/.*identifier=\([0-9]*\), name='$1'.*/\1/p" | head -1; }
|
|
half=$(id_of HALF_OPENED); closed=$(id_of CLOSED)
|
|
cycle() { # $1 name $2 id
|
|
pid0=$(app_pid)
|
|
fold_changed=1
|
|
sh_ cmd device_state state "$2" >/dev/null; sleep 8
|
|
got=$(sh_ cmd device_state state | sed -n "s/.*Committed state: DeviceState{identifier=[0-9]*, name='\([A-Z_]*\)'.*/\1/p" | head -1)
|
|
pid1=$(app_pid)
|
|
[ "$got" = "$1" ] && ok "device state is $1" || note "asked for $1, committed '$got'"
|
|
[ -n "$pid1" ] && [ "$pid0" = "$pid1" ] && ok "same process in $1" || bad "process changed in $1 ($pid0 -> $pid1)"
|
|
restore_fold; sleep 6
|
|
}
|
|
if [ -n "$half" ]; then cycle HALF_OPENED "$half"; else note "no HALF_OPENED state"; fi
|
|
if [ "$do_closed" = 1 ] && [ -n "$closed" ]; then
|
|
cycle CLOSED "$closed"
|
|
note "CLOSED turns the screens off: the session pauses (Background behaviour), Resume it from the app"
|
|
fi
|
|
got=$(sh_ cmd device_state state | sed -n "s/.*Committed state: DeviceState{identifier=\([0-9]*\),.*/\1/p" | head -1)
|
|
[ "$got" = "$orig_state" ] && ok "device state restored ($orig_state)" || bad "device state is $got, was $orig_state"
|
|
fi
|
|
fi
|
|
|
|
head_ "Session log (debug builds)"
|
|
dir=$(sh_ run-as "$pkg" ls -t files/logs 2>/dev/null | head -1)
|
|
if [ -z "$dir" ] || echo "$dir" | grep -q -i "not debuggable\|unknown package"; then
|
|
note "no readable session folder (release build, or no session has run)"
|
|
else
|
|
echo " newest session $dir"
|
|
ev=$(sh_ run-as "$pkg" cat "files/logs/$dir/events.jsonl" 2>/dev/null)
|
|
echo "$ev" | grep -q '"frame.first"' && ok "first frame was drawn" || note "no frame.first in $dir"
|
|
echo "$ev" | grep -q '"session.failed"' && bad "session.failed: $(echo "$ev" | grep '"session.failed"' | tail -1 | cut -c1-160)"
|
|
fps=$(sh_ run-as "$pkg" cat "files/logs/$dir/app.log" 2>/dev/null | grep 'last 10 s:' | tail -1 | sed 's/.*last 10 s: //' | cut -c1-70)
|
|
[ -n "$fps" ] && echo " last stats $fps"
|
|
sh_ run-as "$pkg" cat "files/logs/$dir/device.txt" 2>/dev/null | grep -E 'Memory page size|^Kernel' | sed 's/^/ report /'
|
|
fi
|
|
|
|
[ "$fail" = 0 ]
|