docs(test): add Phase 3 boot recovery testing infrastructure

Adds documentation and test harness for Phase 3 (Boot-Time Recovery).

Changes:
- Update android-implementation-directive-phase3.md with concise boot recovery flow
- Add PHASE3-EMULATOR-TESTING.md with detailed test procedures
- Add PHASE3-VERIFICATION.md with test matrix and verification template
- Add test-phase3.sh automated test harness

Test harness features:
- 4 test cases: future alarms, past alarms, no schedules, silent recovery
- Automatic emulator reboot handling
- Log parsing for boot recovery scenario and results
- UI prompts for plugin configuration and scheduling
- Verifies silent recovery without app launch

Related:
- Directive: android-implementation-directive-phase3.md
- Requirements: docs/alarms/03-plugin-requirements.md §3.1.1
- Testing: docs/alarms/PHASE3-EMULATOR-TESTING.md
- Verification: docs/alarms/PHASE3-VERIFICATION.md
This commit is contained in:
Matthew Raymer
2025-11-27 10:01:46 +00:00
parent c8a3906449
commit 28fb233286
4 changed files with 1258 additions and 560 deletions

View File

@@ -0,0 +1,578 @@
#!/usr/bin/env bash
set -euo pipefail
# ========================================
# Phase 3 Testing Script Boot 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: "
BOOT_SCENARIO_VALUE="BOOT"
NONE_SCENARIO_VALUE="NONE"
# ------------------------------------------------------------------------------
# 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
}
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"
}
reboot_emulator() {
info "Rebooting emulator..."
$ADB_BIN reboot
ok "Reboot initiated"
info "Waiting for emulator to come back online..."
$ADB_BIN wait-for-device
while [ "$($ADB_BIN shell getprop sys.boot_completed)" != "1" ]; do
sleep 2
done
ok "Emulator boot completed"
}
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: BOOT" or "Starting boot recovery" format
if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
echo "$BOOT_SCENARIO_VALUE"
else
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
echo "$scen"
fi
}
# ------------------------------------------------------------------------------
# TEST 1 Boot with Future Alarms
# ------------------------------------------------------------------------------
test1_boot_future_alarms() {
section "TEST 1: Boot with Future Alarms"
echo "Purpose: Verify alarms are recreated on boot when schedules have future run times."
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 reboot: $before_count"
if [[ "$before_count" -eq 0 ]]; then
warn "No alarms found before reboot; TEST 1 may not be meaningful."
fi
pause
substep "Step 3: Reboot emulator"
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2 # Give recovery a moment to complete
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
substep "Step 5: Verify alarms were recreated"
show_alarms
local after_count
after_count="$(count_alarms)"
info "Alarm count after boot: $after_count"
if [[ "$scenario" == "$BOOT_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Boot recovery detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
if [[ "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Boot recovery ran and alarms rescheduled (rescheduled=$rescheduled)."
else
warn "TEST 1: Boot recovery ran but rescheduled=0. Check implementation or logs."
fi
else
warn "TEST 1: Boot recovery not clearly detected. Review logs and boot receiver implementation."
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
fi
}
# ------------------------------------------------------------------------------
# TEST 2 Boot with Past Alarms
# ------------------------------------------------------------------------------
test2_boot_past_alarms() {
section "TEST 2: Boot with Past Alarms"
echo "Purpose: Verify missed alarms are detected and next occurrence is scheduled on boot."
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 a notification for 2 minutes in the future.\n\nAfter scheduling, we'll wait for the alarm time to pass, then reboot."
substep "Step 2: Wait for alarm time to pass"
info "Waiting 3 minutes for scheduled alarm time to pass..."
warn "You can manually advance system time if needed (requires root/emulator)"
sleep 180 # Wait 3 minutes
substep "Step 3: Verify alarm time has passed"
info "Alarm time should now be in the past"
show_alarms
pause
substep "Step 4: Reboot emulator"
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 5: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2
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 [[ "$missed" -ge 1 && "$rescheduled" -ge 1 ]]; then
ok "TEST 2 PASSED: Past alarms detected and next occurrence scheduled (missed=$missed, rescheduled=$rescheduled)."
elif [[ "$missed" -ge 1 ]]; then
warn "TEST 2: Past alarms detected (missed=$missed) but rescheduled=$rescheduled. Check reschedule logic."
else
warn "TEST 2: No missed alarms detected. Verify alarm time actually passed before reboot."
fi
}
# ------------------------------------------------------------------------------
# TEST 3 Boot with No Schedules
# ------------------------------------------------------------------------------
test3_boot_no_schedules() {
section "TEST 3: Boot with No Schedules"
echo "Purpose: Verify boot recovery handles empty database gracefully."
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: Reboot emulator WITHOUT scheduling anything"
warn "Do NOT schedule any notifications. The app should have no schedules in the database."
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2
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
if [[ -z "$logs" ]]; then
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)."
return
fi
if echo "$logs" | grep -qiE "No schedules found|No schedules present"; then
ok "TEST 3 PASSED: Explicit 'No schedules found' message logged with no rescheduling."
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
ok "TEST 3 PASSED: NONE scenario detected with no rescheduling."
elif [[ "$rescheduled" -gt 0 ]]; then
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that boot recovery isn't misfiring."
else
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / NO_SCHEDULES."
fi
}
# ------------------------------------------------------------------------------
# TEST 4 Silent Boot Recovery (App Never Opened)
# ------------------------------------------------------------------------------
test4_silent_boot_recovery() {
section "TEST 4: Silent Boot Recovery (App Never Opened)"
echo "Purpose: Verify boot recovery occurs even when the app is never opened after reboot."
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 a notification 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 reboot: $before_count"
if [[ "$before_count" -eq 0 ]]; then
warn "No alarms found; TEST 4 may not be meaningful."
fi
pause
substep "Step 3: Reboot emulator (DO NOT open app after reboot)"
warn "IMPORTANT: After reboot, DO NOT open the app. Boot recovery should run silently."
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs (without opening app)"
info "Collecting recovery logs from boot (app was NOT opened)..."
sleep 2
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
substep "Step 5: Verify alarms were recreated (without opening app)"
show_alarms
local after_count
after_count="$(count_alarms)"
info "Alarm count after boot (app never opened): $after_count"
if [[ "$after_count" -gt 0 && "$rescheduled" -gt 0 ]]; then
ok "TEST 4 PASSED: Boot recovery occurred silently and alarms were recreated (rescheduled=$rescheduled) without app launch."
elif [[ "$rescheduled" -gt 0 ]]; then
ok "TEST 4 PASSED: Boot recovery occurred silently (rescheduled=$rescheduled), but alarm count check unclear."
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
warn "TEST 4: Boot recovery ran but alarms may not have been recreated. Check logs and implementation."
else
warn "TEST 4: Boot recovery not detected. Verify boot receiver is registered and has BOOT_COMPLETED permission."
fi
}
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
main() {
echo
echo "========================================"
echo "Phase 3 Testing Script Boot Recovery"
echo "========================================"
echo
echo "This script will guide you through all Phase 3 tests."
echo "You'll be prompted when UI interaction is needed."
echo
echo "⚠️ WARNING: This script will reboot the emulator multiple times."
echo " Each reboot takes 30-60 seconds."
echo
pause
require_adb_device
build_app
install_app
test1_boot_future_alarms
pause
test2_boot_past_alarms
pause
test3_boot_no_schedules
pause
test4_silent_boot_recovery
section "Testing Complete"
echo "Test Results Summary (see logs above for details):"
echo
echo "TEST 1: Boot with Future Alarms"
echo " - Check logs for scenario=$BOOT_SCENARIO_VALUE and rescheduled>0"
echo
echo "TEST 2: Boot with Past Alarms"
echo " - Check that missed>=1 and rescheduled>=1"
echo
echo "TEST 3: Boot with No Schedules"
echo " - Check that no recovery runs, or NONE scenario is logged with rescheduled=0"
echo
echo "TEST 4: Silent Boot Recovery"
echo " - Check that boot recovery occurred and alarms were recreated without app launch"
echo
ok "Phase 3 testing script complete!"
echo
echo "Next steps:"
echo " - Review logs above"
echo " - Capture snippets into PHASE3-EMULATOR-TESTING.md"
echo " - Update PHASE3-VERIFICATION.md and unified directive status matrix"
echo
}
main "$@"