docs(test): add Phase 2 force stop recovery testing infrastructure
Adds documentation and test harness for Phase 2 (Force Stop Detection & Recovery). Changes: - Add PHASE2-EMULATOR-TESTING.md with detailed test procedures - Add PHASE2-VERIFICATION.md with test matrix and verification template - Add test-phase2.sh automated test harness Test harness features: - 3 test cases: force stop with cleared alarms, intact alarms, empty DB - Automatic force stop simulation via adb - Log parsing for scenario detection and recovery results - UI prompts for plugin configuration and scheduling Related: - Directive: android-implementation-directive-phase2.md - Requirements: docs/alarms/03-plugin-requirements.md §3.1.4 - Testing: docs/alarms/PHASE2-EMULATOR-TESTING.md - Verification: docs/alarms/PHASE2-VERIFICATION.md
This commit is contained in:
530
test-apps/android-test-app/test-phase2.sh
Executable file
530
test-apps/android-test-app/test-phase2.sh
Executable file
@@ -0,0 +1,530 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ========================================
|
||||
# Phase 2 Testing Script – Force Stop Recovery
|
||||
# ========================================
|
||||
|
||||
# --- Config -------------------------------------------------------------------
|
||||
|
||||
APP_ID="com.timesafari.dailynotification"
|
||||
APK_PATH="./app/build/outputs/apk/debug/app-debug.apk"
|
||||
ADB_BIN="${ADB_BIN:-adb}"
|
||||
|
||||
# Log tags / patterns (matched to actual ReactivationManager logs)
|
||||
REACTIVATION_TAG="DNP-REACTIVATION"
|
||||
SCENARIO_KEY="Detected scenario: "
|
||||
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
|
||||
COLD_START_SCENARIO_VALUE="COLD_START"
|
||||
NONE_SCENARIO_VALUE="NONE"
|
||||
BOOT_SCENARIO_VALUE="BOOT"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "$1"
|
||||
echo "========================================"
|
||||
echo
|
||||
}
|
||||
|
||||
substep() {
|
||||
echo "→ $1"
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "ℹ️ $1"
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo -e "✅ $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "⚠️ $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "❌ $1"
|
||||
}
|
||||
|
||||
pause() {
|
||||
echo
|
||||
read -rp "Press Enter when ready to continue..."
|
||||
echo
|
||||
}
|
||||
|
||||
ui_prompt() {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "👆 UI ACTION REQUIRED"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo -e "$1"
|
||||
echo
|
||||
read -rp "Press Enter after completing the action above..."
|
||||
echo
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
# Wrapper so output is visible but failures stop the script
|
||||
# Usage: run_cmd "description" cmd...
|
||||
local desc="$1"; shift
|
||||
substep "$desc"
|
||||
echo " $*"
|
||||
if "$@"; then
|
||||
ok "$desc"
|
||||
else
|
||||
error "$desc failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_adb_device() {
|
||||
section "Pre-Flight Checks"
|
||||
|
||||
if ! $ADB_BIN devices | awk 'NR>1 && $2=="device"{found=1} END{exit !found}'; then
|
||||
error "No emulator/device in 'device' state. Start your emulator first."
|
||||
exit 1
|
||||
fi
|
||||
ok "ADB device connected"
|
||||
|
||||
info "Checking emulator status..."
|
||||
if ! $ADB_BIN shell getprop sys.boot_completed | grep -q "1"; then
|
||||
info "Waiting for emulator to boot..."
|
||||
$ADB_BIN wait-for-device
|
||||
while [ "$($ADB_BIN shell getprop sys.boot_completed)" != "1" ]; do
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
ok "Emulator is ready"
|
||||
}
|
||||
|
||||
build_app() {
|
||||
section "Building Test App"
|
||||
|
||||
substep "Step 1: Building debug APK..."
|
||||
if ./gradlew :app:assembleDebug; then
|
||||
ok "Build successful"
|
||||
else
|
||||
error "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$APK_PATH" ]]; then
|
||||
ok "APK ready: $APK_PATH"
|
||||
else
|
||||
error "APK not found at $APK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_app() {
|
||||
section "Installing App"
|
||||
|
||||
substep "Step 1: Uninstalling existing app (if present)..."
|
||||
set +e
|
||||
uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)"
|
||||
uninstall_status=$?
|
||||
set -e
|
||||
|
||||
if [[ $uninstall_status -ne 0 ]]; then
|
||||
if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then
|
||||
info "No existing app to uninstall (continuing)"
|
||||
else
|
||||
warn "Uninstall returned non-zero status: $uninstall_output (continuing anyway)"
|
||||
fi
|
||||
else
|
||||
ok "Previous app uninstall succeeded"
|
||||
fi
|
||||
|
||||
substep "Step 2: Installing new APK..."
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed successfully"
|
||||
else
|
||||
error "App installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
substep "Step 3: Verifying installation..."
|
||||
if $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then
|
||||
ok "App verified in package list"
|
||||
else
|
||||
error "App not found in package list"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
launch_app() {
|
||||
info "Launching app..."
|
||||
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" >/dev/null 2>&1
|
||||
sleep 3 # Give app time to load
|
||||
ok "App launched"
|
||||
}
|
||||
|
||||
clear_logs() {
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
show_alarms() {
|
||||
info "Checking AlarmManager status..."
|
||||
echo
|
||||
$ADB_BIN shell dumpsys alarm | grep -A3 "$APP_ID" || true
|
||||
echo
|
||||
}
|
||||
|
||||
count_alarms() {
|
||||
# Returns count of alarms for our app
|
||||
$ADB_BIN shell dumpsys alarm | grep -c "$APP_ID" || echo "0"
|
||||
}
|
||||
|
||||
force_stop_app() {
|
||||
info "Force-stopping app..."
|
||||
$ADB_BIN shell am force-stop "$APP_ID"
|
||||
sleep 2
|
||||
ok "Force stop requested"
|
||||
}
|
||||
|
||||
get_recovery_logs() {
|
||||
# Collect recent reactivation logs
|
||||
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true
|
||||
}
|
||||
|
||||
extract_field_from_logs() {
|
||||
# Usage: extract_field_from_logs "<logs>" "<field_name>"
|
||||
local logs="$1"
|
||||
local field="$2"
|
||||
# Looks for patterns like "field=NUMBER" and returns NUMBER (or 0)
|
||||
local value
|
||||
value="$(grep -oE "${field}=[0-9]+" <<<"$logs" | tail -n1 | sed "s/${field}=//" || true)"
|
||||
if [[ -z "$value" ]]; then
|
||||
echo "0"
|
||||
else
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_scenario_from_logs() {
|
||||
local logs="$1"
|
||||
local scen
|
||||
# Looks for "Detected scenario: FORCE_STOP" format
|
||||
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
|
||||
echo "$scen"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 1 – Force Stop with Cleared Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test1_force_stop_cleared_alarms() {
|
||||
section "TEST 1: Force Stop – Alarms Cleared"
|
||||
|
||||
echo "Purpose: Verify force stop detection and alarm rescheduling when alarms are cleared."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & check plugin status"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
|
||||
|
||||
ui_prompt "Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before_count
|
||||
before_count="$(count_alarms)"
|
||||
info "Alarm count before force stop: $before_count"
|
||||
|
||||
if [[ "$before_count" -eq 0 ]]; then
|
||||
warn "No alarms found before force stop; TEST 1 may not be meaningful."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Force stop app (should clear alarms on many devices)"
|
||||
force_stop_app
|
||||
|
||||
substep "Step 4: Check alarms after force stop"
|
||||
local after_count
|
||||
after_count="$(count_alarms)"
|
||||
info "Alarm count after force stop: $after_count"
|
||||
show_alarms
|
||||
|
||||
if [[ "$after_count" -gt 0 ]]; then
|
||||
warn "Alarms still present after force stop. This device/OS may not clear alarms on force stop."
|
||||
warn "TEST 1 will continue but may not fully validate FORCE_STOP scenario."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 5: Launch app (triggers recovery) and capture logs"
|
||||
clear_logs
|
||||
launch_app
|
||||
sleep 5 # give recovery a moment to run
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 1 PASSED: Force stop detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
warn "TEST 1: scenario=FORCE_STOP but rescheduled=0. Check implementation or logs."
|
||||
elif [[ "$after_count" -gt 0 ]]; then
|
||||
info "TEST 1: Device/emulator kept alarms after force stop; FORCE_STOP scenario may not trigger here."
|
||||
if [[ "$rescheduled" -gt 0 ]]; then
|
||||
info "Recovery still worked (rescheduled=$rescheduled), but scenario was ${scenario:-COLD_START} instead of FORCE_STOP"
|
||||
fi
|
||||
else
|
||||
warn "TEST 1: Expected FORCE_STOP scenario not clearly detected. Review logs and scenario detection logic."
|
||||
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
|
||||
fi
|
||||
|
||||
substep "Step 6: Verify alarms are rescheduled in AlarmManager"
|
||||
show_alarms
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 2 – Force Stop / Process Stop with Intact Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test2_force_stop_intact_alarms() {
|
||||
section "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
|
||||
echo "Purpose: Ensure we do NOT run heavy force-stop recovery when alarms are intact."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & ensure plugin configured"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
|
||||
|
||||
ui_prompt "Click 'Test Notification' to schedule another notification a few minutes in the future."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before
|
||||
before="$(count_alarms)"
|
||||
info "Alarm count before stop: $before"
|
||||
|
||||
if [[ "$before" -eq 0 ]]; then
|
||||
warn "No alarms found; TEST 2 may not be meaningful."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Simulate a 'soft' stop or process kill that does NOT clear alarms"
|
||||
info "Killing app process (non-destructive - may not clear alarms)..."
|
||||
$ADB_BIN shell am kill "$APP_ID" || true
|
||||
sleep 2
|
||||
ok "Kill signal sent (soft stop)"
|
||||
|
||||
substep "Step 4: Verify alarms are still scheduled"
|
||||
local after
|
||||
after="$(count_alarms)"
|
||||
info "Alarm count after soft stop: $after"
|
||||
show_alarms
|
||||
|
||||
if [[ "$after" -eq 0 ]]; then
|
||||
warn "Alarms appear cleared after soft stop; this environment may not distinguish TEST 2 well."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 5: Launch app (triggers recovery) and capture logs"
|
||||
clear_logs
|
||||
launch_app
|
||||
sleep 5
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 2 PASSED: No heavy force-stop recovery when alarms intact (scenario=$scenario, rescheduled=$rescheduled)."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||||
warn "TEST 2: scenario=FORCE_STOP detected but alarms were intact. Check scenario detection logic."
|
||||
else
|
||||
info "TEST 2: Some rescheduling occurred (rescheduled=$rescheduled). This might be OK if alarms were actually missing."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 3 – First Launch / Empty DB Safeguard
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test3_first_launch_no_schedules() {
|
||||
section "TEST 3: First Launch / No Schedules Safeguard"
|
||||
|
||||
echo "Purpose: Ensure force-stop recovery is NOT triggered when DB is empty or plugin isn't configured."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Uninstall app to clear DB/state"
|
||||
set +e
|
||||
$ADB_BIN uninstall "$APP_ID" >/dev/null 2>&1
|
||||
set -e
|
||||
ok "App uninstalled (state cleared)"
|
||||
|
||||
substep "Step 2: Reinstall app"
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed"
|
||||
else
|
||||
error "Reinstall failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Launch app WITHOUT scheduling anything"
|
||||
launch_app
|
||||
sleep 5
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
local logs
|
||||
logs="$($ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled missed
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " missed = ${missed}"
|
||||
echo
|
||||
|
||||
# Check for explicit "No schedules" or "skipping recovery" messages
|
||||
if echo "$logs" | grep -qiE "No schedules|skipping recovery|first launch"; then
|
||||
ok "TEST 3 PASSED: No recovery logs or explicit skip message when there are no schedules (safe behavior)."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "$logs" ]]; then
|
||||
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)."
|
||||
return
|
||||
fi
|
||||
|
||||
# If you explicitly log a NONE scenario, check for that:
|
||||
if [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 3 PASSED: NONE scenario detected with no rescheduling."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||||
error "TEST 3 FAILED: FORCE_STOP scenario detected on first launch / empty DB. This should not happen."
|
||||
elif [[ "$rescheduled" -gt 0 ]]; then
|
||||
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring."
|
||||
else
|
||||
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / FIRST_LAUNCH."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "Phase 2 Testing Script – Force Stop Recovery"
|
||||
echo "========================================"
|
||||
echo
|
||||
echo "This script will guide you through all Phase 2 tests."
|
||||
echo "You'll be prompted when UI interaction is needed."
|
||||
echo
|
||||
|
||||
pause
|
||||
|
||||
require_adb_device
|
||||
build_app
|
||||
install_app
|
||||
|
||||
test1_force_stop_cleared_alarms
|
||||
pause
|
||||
|
||||
test2_force_stop_intact_alarms
|
||||
pause
|
||||
|
||||
test3_first_launch_no_schedules
|
||||
|
||||
section "Testing Complete"
|
||||
|
||||
echo "Test Results Summary (see logs above for details):"
|
||||
echo
|
||||
echo "TEST 1: Force Stop – Alarms Cleared"
|
||||
echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0"
|
||||
echo
|
||||
echo "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
echo " - Check that heavy FORCE_STOP recovery did NOT run when alarms are still present"
|
||||
echo
|
||||
echo "TEST 3: First Launch / No Schedules"
|
||||
echo " - Check that no force-stop recovery runs, or that NONE scenario is logged with rescheduled=0"
|
||||
echo
|
||||
|
||||
ok "Phase 2 testing script complete!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo " - Review logs above"
|
||||
echo " - Capture snippets into PHASE2-EMULATOR-TESTING.md"
|
||||
echo " - Update PHASE2-VERIFICATION.md and unified directive status matrix"
|
||||
echo
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user