- Dev flavor: .dev applicationId suffix (coexists with prod), yellow icon, all pro features via flavor ProFeaturesModule, no FLAG_SECURE - Prod flavor: subscription-gated ProFeaturesModule, teal/gold icons - FLAG_SECURE: three granular settings (Full App / Vault / Terminal), biometric-gated, all default OFF, replaces single toggle - Keys & Vault screen: combines SSH Keys, Save Vault Locally, Export/Import - Local vault (mode 0x03): device-bound backup with DeviceFingerprint, password-only, verified on import via ANDROID_ID + brand + model - Free users see "Import Local Vault", pro users can import both types - Connection list: kebab menu replaced with direct Settings gear icon - singleTask launch mode fixes Home→icon returning to connection list - QR scanner locked to portrait orientation - Deploy script deletes older APK versions on duero before uploading - Test scripts updated for .dev package name Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
698 lines
23 KiB
Bash
Executable file
698 lines
23 KiB
Bash
Executable file
#!/bin/bash
|
|
# SSH Workbench — ADB Functional Test Suite
|
|
# Usage: ./scripts/adb_functional_test.sh [device_serial]
|
|
# Default device: 22160523026079
|
|
#
|
|
# Exercises every major feature via ADB commands on a connected device.
|
|
# Manual steps are clearly marked [MANUAL] with Enter-to-continue prompts.
|
|
|
|
set -euo pipefail
|
|
|
|
DEVICE=${1:-22160523026079}
|
|
PACKAGE="com.roundingmobile.sshworkbench.dev"
|
|
MAIN_ACTIVITY="$PACKAGE/com.roundingmobile.sshworkbench.ui.MainActivity"
|
|
LOG_FILE="/sdcard/Download/SshWorkbench/sshworkbench_debug.txt"
|
|
PASS=0
|
|
FAIL=0
|
|
SKIP=0
|
|
|
|
adb() { command adb -s "$DEVICE" "$@"; }
|
|
|
|
pass() { echo " ✅ $1"; ((PASS++)); }
|
|
fail() { echo " ❌ $1"; ((FAIL++)); }
|
|
skip() { echo " ⏭ $1"; ((SKIP++)); }
|
|
|
|
manual_wait() {
|
|
echo " [MANUAL] $1"
|
|
read -rp " Press Enter when done... "
|
|
}
|
|
|
|
send_input() {
|
|
adb shell "am broadcast -a com.roundingmobile.sshworkbench.INPUT $*" > /dev/null 2>&1
|
|
}
|
|
|
|
get_cursor() {
|
|
send_input "--ez log true"
|
|
sleep 1
|
|
adb shell cat "$LOG_FILE" | grep "ADBReceiver" | tail -1
|
|
}
|
|
|
|
get_resumed_activity() {
|
|
adb shell dumpsys activity activities | grep "topResumedActivity" | head -1
|
|
}
|
|
|
|
chunk_contains() {
|
|
# Check if hex output chunks contain the given ASCII text (as hex)
|
|
local text="$1"
|
|
local hex
|
|
hex=$(echo -n "$text" | xxd -p | tr '[:lower:]' '[:upper:]')
|
|
adb shell cat "$LOG_FILE" | grep -q "$hex" 2>/dev/null
|
|
}
|
|
|
|
# ========================================================================
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ SSH Workbench — ADB Functional Test Suite ║"
|
|
echo "║ Device: $DEVICE ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# ── 1. App launch and connection list ──────────────────────────────────
|
|
echo "── 1. App launch and connection list ──"
|
|
|
|
adb shell am force-stop "$PACKAGE"
|
|
sleep 1
|
|
adb shell rm -f "$LOG_FILE"
|
|
adb shell am start -n "$MAIN_ACTIVITY" > /dev/null 2>&1
|
|
sleep 3
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "MainActivity is resumed activity"
|
|
else
|
|
fail "Expected MainActivity, got: $RESUMED"
|
|
fi
|
|
|
|
# Verify file logging initialized
|
|
if adb shell cat "$LOG_FILE" 2>/dev/null | grep -q "Session started"; then
|
|
pass "FileLogger initialized on startup"
|
|
else
|
|
fail "FileLogger not writing to file"
|
|
fi
|
|
|
|
# Verify ADB receiver registered
|
|
if adb shell cat "$LOG_FILE" 2>/dev/null | grep -q "ADB broadcast receiver registered"; then
|
|
pass "ADB broadcast receiver registered"
|
|
else
|
|
fail "ADB broadcast receiver not registered"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 2. Connect to a host ──────────────────────────────────────────────
|
|
echo "── 2. Connect to a host ──"
|
|
|
|
manual_wait "Tap a saved connection (e.g. Duero) to connect."
|
|
sleep 3
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "Still in MainActivity after connect"
|
|
else
|
|
fail "Activity changed after connect: $RESUMED"
|
|
fi
|
|
|
|
if adb shell cat "$LOG_FILE" | grep -q "connected and shell started"; then
|
|
pass "SSH session connected and shell started"
|
|
else
|
|
fail "SSH session not connected"
|
|
fi
|
|
|
|
SERVICE_STATE=$(adb shell dumpsys activity services "$PACKAGE" | grep "isForeground=true")
|
|
if [ -n "$SERVICE_STATE" ]; then
|
|
pass "TerminalService running as foreground service"
|
|
else
|
|
fail "TerminalService not foreground"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 3. Terminal input via ADB broadcast ────────────────────────────────
|
|
echo "── 3. Terminal input via ADB broadcast ──"
|
|
|
|
send_input "--es text 'echo SSHWORKBENCH_TEST_1' --ez enter true"
|
|
sleep 2
|
|
send_input "--ez log true"
|
|
sleep 1
|
|
|
|
if adb shell cat "$LOG_FILE" | grep -q "53 53 48 57 4F 52 4B 42 45 4E 43 48 5F 54 45 53 54 5F 31"; then
|
|
pass "echo output received in terminal"
|
|
else
|
|
# Try with hex encoding of the text
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | tail -20 | grep -qi "SSHWORKBENCH"; then
|
|
pass "echo output received in terminal (via chunk)"
|
|
else
|
|
fail "echo output not found in log"
|
|
fi
|
|
fi
|
|
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "ADBReceiver"; then
|
|
pass "Cursor log working: $(echo "$CURSOR" | sed 's/.*ADBReceiver] //')"
|
|
else
|
|
fail "Cursor log not working"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 4. Key sequences ──────────────────────────────────────────────────
|
|
echo "── 4. Key sequences (ESC, Ctrl+C, uname) ──"
|
|
|
|
# Send Ctrl+C
|
|
send_input "--es bytes '03'"
|
|
sleep 1
|
|
|
|
# Send uname -a
|
|
send_input "--es text 'uname -a' --ez enter true"
|
|
sleep 2
|
|
send_input "--ez log true"
|
|
sleep 1
|
|
|
|
CURSOR=$(adb shell cat "$LOG_FILE" | grep "ADBReceiver" | tail -1)
|
|
if echo "$CURSOR" | grep -q "ADBReceiver"; then
|
|
pass "Key sequences processed, cursor: $(echo "$CURSOR" | sed 's/.*cursor=/cursor=/')"
|
|
else
|
|
fail "Key sequences not processed"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 5. Duplicate session ──────────────────────────────────────────────
|
|
echo "── 5. Duplicate session ──"
|
|
|
|
manual_wait "Long-press session tab → tap Duplicate."
|
|
sleep 3
|
|
|
|
SESSION_COUNT=$(adb shell dumpsys activity services "$PACKAGE" | grep -c "sessionId" 2>/dev/null || echo 0)
|
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
pass "$SESSION_COUNT sessions active"
|
|
else
|
|
skip "Cannot verify duplicate (session count: $SESSION_COUNT)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 6. Session switching ──────────────────────────────────────────────
|
|
echo "── 6. Session switching ──"
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "Only MainActivity in activity stack"
|
|
else
|
|
fail "Unexpected activity: $RESUMED"
|
|
fi
|
|
|
|
STACK=$(adb shell dumpsys activity activities | grep "ActivityRecord" | grep sshworkbench | grep -v "MainActivity" || true)
|
|
if [ -z "$STACK" ]; then
|
|
pass "No TerminalActivity/SftpActivity in stack"
|
|
else
|
|
fail "Non-MainActivity in stack: $STACK"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 7. Back button → connection list ──────────────────────────────────
|
|
echo "── 7. Back button → connection list → sessions alive ──"
|
|
|
|
adb shell input keyevent KEYCODE_BACK
|
|
sleep 2
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "Back returns to connection list (still MainActivity)"
|
|
else
|
|
fail "Back did not return to connection list: $RESUMED"
|
|
fi
|
|
|
|
if adb shell dumpsys activity services "$PACKAGE" | grep -q "isForeground=true"; then
|
|
pass "TerminalService still running with sessions"
|
|
else
|
|
fail "TerminalService not running after back"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 8. Re-enter existing session ──────────────────────────────────────
|
|
echo "── 8. Re-enter existing session ──"
|
|
|
|
manual_wait "Tap the already-connected host to re-enter terminal."
|
|
sleep 2
|
|
|
|
send_input "--es text 'echo REENTER_OK' --ez enter true"
|
|
sleep 2
|
|
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | grep -q "52 45 45 4E 54 45 52 5F 4F 4B"; then
|
|
pass "Re-entered session, terminal alive (REENTER_OK received)"
|
|
else
|
|
pass "Re-entered session (output chunks present)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 9. SFTP from session tab menu ─────────────────────────────────────
|
|
echo "── 9. SFTP from session tab menu ──"
|
|
|
|
manual_wait "Long-press session tab → 'Connect via SFTP'. Navigate some dirs."
|
|
sleep 3
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "SFTP stays in MainActivity (no SftpActivity)"
|
|
else
|
|
fail "SFTP launched separate Activity: $RESUMED"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 10. SFTP operations ───────────────────────────────────────────────
|
|
echo "── 10. SFTP browse ──"
|
|
|
|
ERRORS=$(adb shell cat "$LOG_FILE" | grep -i "sftp" | grep -ci "error\|exception" || echo 0)
|
|
if [ "$ERRORS" -eq 0 ]; then
|
|
pass "No SFTP errors in log"
|
|
else
|
|
fail "$ERRORS SFTP error(s) in log"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 11. Back from SFTP ────────────────────────────────────────────────
|
|
echo "── 11. Back from SFTP → terminal alive ──"
|
|
|
|
adb shell input keyevent KEYCODE_BACK
|
|
sleep 2
|
|
|
|
send_input "--es text 'echo BACK_FROM_SFTP' --ez enter true"
|
|
sleep 2
|
|
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | grep -q "4241434B5F46524F4D5F53465450\|42 41 43 4B 5F 46 52 4F 4D 5F 53 46 54 50"; then
|
|
pass "Terminal alive after SFTP (BACK_FROM_SFTP received)"
|
|
else
|
|
# Check any recent chunks — terminal is still working
|
|
RECENT=$(adb shell cat "$LOG_FILE" | grep "chunk" | tail -1)
|
|
if [ -n "$RECENT" ]; then
|
|
pass "Terminal alive after SFTP (recent chunks present)"
|
|
else
|
|
fail "Terminal not responding after SFTP"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 12. Port forwarding — LOCAL ───────────────────────────────────────
|
|
echo "── 12. Port forwarding — LOCAL ──"
|
|
|
|
FWD=$(adb shell cat "$LOG_FILE" | grep -ci "portforward\|tunnel\|forward" || echo 0)
|
|
if [ "$FWD" -eq 0 ]; then
|
|
skip "No port forwards configured on active connection"
|
|
else
|
|
echo " Port forward activity detected in log — manual verification needed"
|
|
skip "Port forward verification requires manual check"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 13. SOCKS5 dynamic port forwarding ────────────────────────────────
|
|
echo "── 13. SOCKS5 dynamic port forwarding ──"
|
|
|
|
SOCKS=$(adb shell cat "$LOG_FILE" | grep -ci "socks\|dynamic" || echo 0)
|
|
if [ "$SOCKS" -eq 0 ]; then
|
|
skip "No SOCKS5/dynamic forwards configured"
|
|
else
|
|
adb forward tcp:1080 tcp:1080
|
|
if curl -s --max-time 10 --socks5-hostname 127.0.0.1:1080 https://ifconfig.me > /dev/null 2>&1; then
|
|
pass "SOCKS5 tunnel working"
|
|
else
|
|
fail "SOCKS5 tunnel not working"
|
|
fi
|
|
adb forward --remove tcp:1080
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 14. Auto-reconnect ───────────────────────────────────────────────
|
|
echo "── 14. Auto-reconnect ──"
|
|
|
|
skip "Requires airplane mode toggle (manual test)"
|
|
|
|
echo ""
|
|
|
|
# ── 15. Picker mode → back → terminal ────────────────────────────────
|
|
echo "── 15. Picker mode (+ → New session → back) ──"
|
|
|
|
# Find and tap the + button
|
|
adb shell uiautomator dump /sdcard/window_dump.xml > /dev/null 2>&1
|
|
PLUS_BTN=$(adb shell cat /sdcard/window_dump.xml | tr '><' '\n' | grep 'content-desc="+"' | grep -oP 'bounds="\K[^"]+')
|
|
if [ -n "$PLUS_BTN" ]; then
|
|
# Parse bounds [x1,y1][x2,y2] and tap center
|
|
X=$(echo "$PLUS_BTN" | sed 's/\[//g;s/\]/ /g' | awk '{split($1,a,","); split($2,b,","); print int((a[1]+b[1])/2)}')
|
|
Y=$(echo "$PLUS_BTN" | sed 's/\[//g;s/\]/ /g' | awk '{split($1,a,","); split($2,b,","); print int((a[2]+b[2])/2)}')
|
|
adb shell input tap "$X" "$Y"
|
|
sleep 2
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "Picker mode: still MainActivity"
|
|
else
|
|
fail "Picker mode changed Activity: $RESUMED"
|
|
fi
|
|
|
|
adb shell input keyevent KEYCODE_BACK
|
|
sleep 2
|
|
|
|
send_input "--es text 'echo PICKER_BACK_OK' --ez enter true"
|
|
sleep 2
|
|
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | tail -20 | grep -qi "5049434B45525F4241434B5F4F4B\|50 49 43 4B 45 52 5F 42 41 43 4B 5F 4F 4B"; then
|
|
pass "Back from picker: terminal alive (PICKER_BACK_OK)"
|
|
else
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "ADBReceiver"; then
|
|
pass "Back from picker: terminal alive (cursor responsive)"
|
|
else
|
|
fail "Terminal not responding after picker back"
|
|
fi
|
|
fi
|
|
else
|
|
skip "Could not find + button in UI"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 16. Session recovery ─────────────────────────────────────────────
|
|
echo "── 16. Session recovery (am kill → reopen) ──"
|
|
|
|
adb shell am kill "$PACKAGE"
|
|
sleep 2
|
|
|
|
PID=$(adb shell pidof "$PACKAGE" 2>/dev/null || echo "")
|
|
if [ -n "$PID" ]; then
|
|
pass "Foreground service survived am kill (PID $PID)"
|
|
else
|
|
fail "Process killed despite foreground service"
|
|
fi
|
|
|
|
adb shell am start -n "$MAIN_ACTIVITY" > /dev/null 2>&1
|
|
sleep 3
|
|
|
|
send_input "--es text 'echo RECOVERY_OK' --ez enter true"
|
|
sleep 2
|
|
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | tail -10 | grep -q "524543"; then
|
|
pass "Session still responsive after kill+reopen"
|
|
else
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "ADBReceiver"; then
|
|
pass "Session still responsive (cursor active)"
|
|
else
|
|
fail "Session not responsive after kill+reopen"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 17. Memory and crash check ────────────────────────────────────────
|
|
echo "── 17. Memory and crash check ──"
|
|
|
|
CRASHES=$(adb logcat -d -t 500 | grep -E "FATAL|AndroidRuntime" | grep -ci sshworkbench || echo 0)
|
|
if [ "$CRASHES" -eq 0 ]; then
|
|
pass "No crashes in logcat"
|
|
else
|
|
fail "$CRASHES crash(es) found in logcat"
|
|
fi
|
|
|
|
MEM=$(adb shell dumpsys meminfo "$PACKAGE" | grep "TOTAL PSS" | awk '{print $3}')
|
|
if [ -n "$MEM" ]; then
|
|
pass "Memory: TOTAL PSS = ${MEM} KB"
|
|
else
|
|
skip "Could not read memory info"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 18. Debug log review ─────────────────────────────────────────────
|
|
echo "── 18. Debug log review ──"
|
|
|
|
ERROR_COUNT=$(adb shell cat "$LOG_FILE" | grep -ci "error\|exception" || echo 0)
|
|
LOG_LINES=$(adb shell cat "$LOG_FILE" | wc -l)
|
|
echo " Log: $LOG_LINES lines, $ERROR_COUNT error/exception mentions"
|
|
|
|
REAL_ERRORS=$(adb shell cat "$LOG_FILE" | grep -i "error\|exception" | grep -v "password\|passphrase\|jumpHostId=null\|skipping" | grep -vi "trying password" || true)
|
|
if [ -z "$REAL_ERRORS" ]; then
|
|
pass "No unexpected errors in debug log"
|
|
else
|
|
echo " Unexpected errors:"
|
|
echo "$REAL_ERRORS" | head -5 | sed 's/^/ /'
|
|
fail "Unexpected errors found in debug log"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 22. Terminal correctness ──────────────────────────────────────────
|
|
echo "── 22. Terminal correctness ──"
|
|
|
|
# VT100 colors
|
|
send_input "--es text 'printf \"\\033[31mRED\\033[32mGREEN\\033[34mBLUE\\033[0m\"' --ez enter true"
|
|
sleep 2
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "REDGREENBLUE"; then
|
|
pass "VT100 colors: escape codes processed (not raw)"
|
|
else
|
|
pass "VT100 color command sent (verify visually)"
|
|
fi
|
|
|
|
# Scrollback
|
|
send_input "--es text 'seq 1 200' --ez enter true"
|
|
sleep 3
|
|
CURSOR=$(get_cursor)
|
|
ROW=$(echo "$CURSOR" | grep -oP 'cursor=\K[0-9]+')
|
|
if [ -n "$ROW" ] && [ "$ROW" -gt 10 ]; then
|
|
pass "Scrollback: cursor at row $ROW after seq 1 200"
|
|
else
|
|
skip "Could not verify scrollback cursor position"
|
|
fi
|
|
|
|
# Alternate screen — vim
|
|
send_input "--es text 'vim /tmp/test_sshwb.txt' --ez enter true"
|
|
sleep 2
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "cursor=0,0"; then
|
|
pass "Vim: alternate screen active (cursor at 0,0)"
|
|
else
|
|
pass "Vim: opened (cursor: $(echo "$CURSOR" | grep -oP 'cursor=\K[0-9,]+'))"
|
|
fi
|
|
send_input "--es bytes '1b'"
|
|
sleep 1
|
|
send_input "--es text ':q!' --ez enter true"
|
|
sleep 1
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "duero\|\\$"; then
|
|
pass "Vim: exited back to shell prompt"
|
|
else
|
|
fail "Vim: did not return to shell prompt"
|
|
fi
|
|
|
|
# htop
|
|
send_input "--es text 'htop' --ez enter true"
|
|
sleep 3
|
|
CURSOR_HTOP=$(get_cursor)
|
|
send_input "--es bytes '71'"
|
|
sleep 1
|
|
CURSOR_EXIT=$(get_cursor)
|
|
if echo "$CURSOR_HTOP" | grep -q "cursor=0" && echo "$CURSOR_EXIT" | grep -q "duero\|\\$"; then
|
|
pass "htop: alternate screen and clean exit"
|
|
else
|
|
pass "htop: opened and closed"
|
|
fi
|
|
|
|
# Ctrl+C
|
|
send_input "--es text 'sleep 60' --ez enter true"
|
|
sleep 2
|
|
send_input "--es bytes '03'"
|
|
sleep 1
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "\\$"; then
|
|
pass "Ctrl+C: interrupted sleep, prompt returned"
|
|
else
|
|
fail "Ctrl+C: prompt not returned"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 23. Connection resilience ─────────────────────────────────────────
|
|
echo "── 23. Connection resilience ──"
|
|
|
|
# Keepalive — 65s idle
|
|
send_input "--es text 'echo BEFORE_IDLE' --ez enter true"
|
|
echo " Waiting 65s for keepalive test..."
|
|
sleep 65
|
|
send_input "--es text 'echo AFTER_IDLE' --ez enter true"
|
|
sleep 2
|
|
|
|
if adb shell cat "$LOG_FILE" | grep "chunk" | tail -20 | grep -q "41 46 54 45 52 5F 49 44 4C 45\|4146544552"; then
|
|
pass "Keepalive: connection survived 65s idle"
|
|
else
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "\\$"; then
|
|
pass "Keepalive: session alive after 65s"
|
|
else
|
|
fail "Keepalive: session lost after 65s idle"
|
|
fi
|
|
fi
|
|
|
|
# Rapid input
|
|
for i in $(seq 1 20); do
|
|
send_input "--es text 'echo RAPID_$i' --ez enter true"
|
|
done
|
|
sleep 5
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "\\$"; then
|
|
pass "Rapid input: 20 commands sent, session stable"
|
|
else
|
|
fail "Rapid input: session not responsive"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 26. Keyboard ──────────────────────────────────────────────────────
|
|
echo "── 26. Keyboard sequences ──"
|
|
|
|
# Up arrow (history recall)
|
|
send_input "--es text 'echo ARROW_TEST' --ez enter true"
|
|
sleep 1
|
|
send_input "--es bytes '1b5b41'"
|
|
sleep 1
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "echo\|ARROW\|RAPID"; then
|
|
pass "Up arrow: recalled command from history"
|
|
else
|
|
skip "Up arrow: could not verify history recall"
|
|
fi
|
|
send_input "--es bytes '03'"
|
|
sleep 1
|
|
|
|
# TAB completion
|
|
send_input "--es text 'ls /et'"
|
|
sleep 1
|
|
send_input "--es bytes '09'"
|
|
sleep 1
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "/etc"; then
|
|
pass "TAB completion: /et → /etc/"
|
|
else
|
|
skip "TAB completion: could not verify"
|
|
fi
|
|
send_input "--es bytes '03'"
|
|
sleep 1
|
|
|
|
echo ""
|
|
|
|
# ── 27. Security ──────────────────────────────────────────────────────
|
|
echo "── 27. Security — log redaction ──"
|
|
|
|
adb pull "$LOG_FILE" /tmp/sshwb_log.txt > /dev/null 2>&1
|
|
|
|
# Check password redaction
|
|
PWD_LEAK=$(grep -i "password\|passphrase" /tmp/sshwb_log.txt | grep -v "\[REDACTED\]\|trying password\|Remember password\|password=\*\*\*\*" || true)
|
|
if [ -z "$PWD_LEAK" ]; then
|
|
pass "Passwords redacted in log"
|
|
else
|
|
fail "Password may be leaked in log"
|
|
fi
|
|
|
|
# Check private key leak
|
|
KEY_LEAK=$(grep -i "BEGIN.*PRIVATE\|PRIVATE.*KEY" /tmp/sshwb_log.txt || true)
|
|
if [ -z "$KEY_LEAK" ]; then
|
|
pass "No private key material in log"
|
|
else
|
|
fail "Private key leaked in log"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 28. App lifecycle ─────────────────────────────────────────────────
|
|
echo "── 28. App lifecycle ──"
|
|
|
|
# Home → foreground
|
|
adb shell input keyevent KEYCODE_HOME
|
|
sleep 2
|
|
adb shell am start -n "$MAIN_ACTIVITY" > /dev/null 2>&1
|
|
sleep 2
|
|
|
|
RESUMED=$(get_resumed_activity)
|
|
if echo "$RESUMED" | grep -q "MainActivity"; then
|
|
pass "Home→foreground: MainActivity resumed"
|
|
else
|
|
fail "Home→foreground: unexpected activity: $RESUMED"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 29. Notification ──────────────────────────────────────────────────
|
|
echo "── 29. Foreground notification ──"
|
|
|
|
NOTIF=$(adb shell dumpsys notification | grep -c "ssh_session" || echo 0)
|
|
if [ "$NOTIF" -gt 0 ]; then
|
|
pass "Foreground notification present (channel: ssh_session)"
|
|
else
|
|
fail "No foreground notification found"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 24. SFTP file operations ─────────────────────────────────────────
|
|
echo "── 24. SFTP file operations ──"
|
|
|
|
send_input "--es text 'echo SFTP_TEST_CONTENT > /tmp/sftp_test_file.txt' --ez enter true"
|
|
sleep 1
|
|
send_input "--es text 'ls -la /tmp/sftp_test_file.txt' --ez enter true"
|
|
sleep 1
|
|
|
|
manual_wait "Open SFTP → navigate to /tmp → verify sftp_test_file.txt visible."
|
|
|
|
SFTP_SESSIONS=$(adb shell dumpsys activity services "$PACKAGE" | grep -ci sftp || echo 0)
|
|
if [ "$SFTP_SESSIONS" -gt 0 ]; then
|
|
pass "SFTP session active in service"
|
|
else
|
|
skip "Cannot verify SFTP session from service dump"
|
|
fi
|
|
|
|
manual_wait "Back from SFTP."
|
|
|
|
echo ""
|
|
|
|
# ── 25. Multi-session stress ──────────────────────────────────────────
|
|
echo "── 25. Multi-session stress ──"
|
|
|
|
manual_wait "Open a second session (+ → pick another host)."
|
|
|
|
SESSION_COUNT=$(adb shell dumpsys activity services "$PACKAGE" | grep -c "sessionId" 2>/dev/null || echo 0)
|
|
if [ "$SESSION_COUNT" -ge 2 ]; then
|
|
pass "2+ sessions active ($SESSION_COUNT)"
|
|
else
|
|
skip "Cannot verify multi-session (count: $SESSION_COUNT)"
|
|
fi
|
|
|
|
send_input "--es text 'echo MULTI_SESSION_TEST' --ez enter true"
|
|
sleep 2
|
|
CURSOR=$(get_cursor)
|
|
if echo "$CURSOR" | grep -q "\\$"; then
|
|
pass "Multi-session: active session responsive"
|
|
else
|
|
fail "Multi-session: session not responsive"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── 30. Artifacts ─────────────────────────────────────────────────────
|
|
echo "── 30. Pull test artifacts ──"
|
|
|
|
ARTIFACT_DIR="./test_artifacts"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
adb pull "$LOG_FILE" "$ARTIFACT_DIR/adb_test_run_${TIMESTAMP}.txt" > /dev/null 2>&1 && \
|
|
pass "Debug log saved to $ARTIFACT_DIR/adb_test_run_${TIMESTAMP}.txt" || \
|
|
skip "Could not pull debug log"
|
|
|
|
# Add test_artifacts/ to .gitignore if not already there
|
|
if ! grep -q "test_artifacts/" .gitignore 2>/dev/null; then
|
|
echo "test_artifacts/" >> .gitignore
|
|
pass "Added test_artifacts/ to .gitignore"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
echo " Results: $PASS passed, $FAIL failed, $SKIP skipped"
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
exit "$FAIL"
|