Implements force stop scenario detection and comprehensive alarm recovery. Adds scenario differentiation (FORCE_STOP, BOOT, COLD_START, NONE) to route recovery logic appropriately. Changes: - Add RecoveryScenario enum and detectScenario() method - Add performForceStopRecovery() for force stop scenario - Add alarmsExist() helper to check AlarmManager state - Update isBootRecovery() to ignore flags < 1 second old (emulator quirk fix) - Update BootReceiver to only set boot flag for actual boot events - Add test script step to clear boot flag before testing - Fix compiler warnings (remove unused parameters) Fixes false BOOT detection when alarms are cleared after force stop. Boot flag age validation prevents emulator quirks from triggering BOOT scenario during app launch. Implements: Plugin Requirements §3.1.4 - Force Stop Recovery
353 lines
12 KiB
Bash
Executable File
353 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
set -euo pipefail
|
||
|
||
# ========================================
|
||
# Phase 2 Testing Script – Force Stop Recovery
|
||
# ========================================
|
||
|
||
# Source shared library
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
source "${SCRIPT_DIR}/alarm-test-lib.sh"
|
||
|
||
# Phase 2 specific configuration
|
||
# Log tags / patterns (matched to actual ReactivationManager logs)
|
||
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
|
||
COLD_START_SCENARIO_VALUE="COLD_START"
|
||
NONE_SCENARIO_VALUE="NONE"
|
||
BOOT_SCENARIO_VALUE="BOOT"
|
||
|
||
# Allow selecting specific tests on the command line (e.g. ./test-phase2.sh 2 3)
|
||
SELECTED_TESTS=()
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 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 system_count
|
||
before_count="$(get_plugin_alarm_count)"
|
||
system_count="$(get_system_alarm_count)"
|
||
info "Plugin alarms before force stop: $before_count (expected: 1)"
|
||
info "System/other alarms: $system_count (for context)"
|
||
|
||
if [[ "$before_count" -eq 0 ]]; then
|
||
warn "No plugin alarms found before force stop; TEST 1 may not be meaningful."
|
||
elif [[ "$before_count" -eq 1 ]]; then
|
||
ok "Single plugin alarm confirmed (one per day)"
|
||
else
|
||
warn "Found $before_count plugin alarms (expected: 1)"
|
||
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 system_after
|
||
after_count="$(get_plugin_alarm_count)"
|
||
system_after="$(get_system_alarm_count)"
|
||
info "Plugin alarms after force stop: $after_count (expected: 0)"
|
||
info "System/other alarms: $system_after (for context)"
|
||
show_alarms
|
||
|
||
if [[ "$after_count" -gt 0 ]]; then
|
||
warn "Plugin 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 4.5: Clear boot flag (prevent false BOOT detection)"
|
||
# Clear boot flag to ensure force stop detection works correctly
|
||
# Boot flag might be set from previous runs or emulator quirks
|
||
adb shell "run-as ${APP_ID} rm -f shared_prefs/dailynotification_recovery.xml 2>/dev/null || true"
|
||
info "Boot flag cleared (if it existed)"
|
||
|
||
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 scenario rescheduled verified errors
|
||
scenario="$(extract_scenario_from_logs "$logs")"
|
||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||
|
||
echo
|
||
info "Parsed recovery summary:"
|
||
echo " scenario = ${scenario:-<none>}"
|
||
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: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
|
||
|
||
pause
|
||
|
||
substep "Step 1: Launch app & schedule notifications"
|
||
launch_app
|
||
ui_prompt "In the app UI, ensure plugin is configured and schedule at least one future notification.\n\nPress Enter when done."
|
||
|
||
substep "Step 2: Verify alarms are scheduled"
|
||
show_alarms
|
||
local before system_before
|
||
before="$(get_plugin_alarm_count)"
|
||
system_before="$(get_system_alarm_count)"
|
||
info "Plugin alarms before stop: $before (expected: 1)"
|
||
info "System/other alarms: $system_before (for context)"
|
||
|
||
if [[ "$before" -eq 0 ]]; then
|
||
warn "No plugin alarms found; TEST 2 may not be meaningful."
|
||
elif [[ "$before" -eq 1 ]]; then
|
||
ok "Single plugin alarm confirmed (one per day)"
|
||
else
|
||
warn "Found $before plugin alarms (expected: 1)"
|
||
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 system_after
|
||
after="$(get_plugin_alarm_count)"
|
||
system_after="$(get_system_alarm_count)"
|
||
info "Plugin alarms after soft stop: $after (expected: 1)"
|
||
info "System/other alarms: $system_after (for context)"
|
||
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: Relaunch app and check recovery logs"
|
||
clear_logs
|
||
launch_app
|
||
sleep 5
|
||
|
||
info "Collecting recovery logs..."
|
||
local logs
|
||
logs="$(get_recovery_logs)"
|
||
echo "$logs"
|
||
|
||
local scenario rescheduled missed verified errors
|
||
scenario="$(extract_scenario_from_logs "$logs")"
|
||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||
|
||
echo
|
||
info "Parsed recovery summary:"
|
||
echo " scenario = ${scenario:-<none>}"
|
||
echo " rescheduled= ${rescheduled}"
|
||
echo " missed = ${missed}"
|
||
echo " verified = ${verified}"
|
||
echo " errors = ${errors}"
|
||
echo
|
||
|
||
if [[ "$errors" -gt 0 ]]; then
|
||
error "Recovery reported errors>0 (errors=$errors)"
|
||
fi
|
||
|
||
if [[ "$after" -gt 0 && "$rescheduled" -eq 0 && "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||
ok "TEST 2 PASSED: Alarms remained intact, and FORCE_STOP scenario did not run (scenario=$scenario, rescheduled=0)."
|
||
else
|
||
warn "TEST 2: Verify that FORCE_STOP recovery didn't misfire when alarms were intact."
|
||
info "Scenario=${scenario:-<none>}, rescheduled=$rescheduled, after_count=$after"
|
||
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 for the first time"
|
||
launch_app
|
||
sleep 5
|
||
|
||
substep "Step 4: Collect logs and ensure no force-stop recovery ran"
|
||
local logs
|
||
logs="$(get_recovery_logs)"
|
||
echo "$logs"
|
||
|
||
local scenario rescheduled
|
||
scenario="$(extract_scenario_from_logs "$logs")"
|
||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||
|
||
echo
|
||
info "Parsed summary:"
|
||
echo " scenario = ${scenario:-<none>}"
|
||
echo " rescheduled= ${rescheduled}"
|
||
echo
|
||
|
||
if [[ -z "$logs" ]]; then
|
||
ok "TEST 3 PASSED: No force-stop recovery logs on first launch."
|
||
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||
ok "TEST 3 PASSED: NONE scenario logged with rescheduled=0 on first launch."
|
||
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() {
|
||
# Allow selecting specific tests: e.g. `./test-phase2.sh 1 3`
|
||
if [[ "$#" -gt 0 && ( "$1" == "-h" || "$1" == "--help" ) ]]; then
|
||
echo "Usage: $0 [TEST_IDS...]"
|
||
echo
|
||
echo "If no TEST_IDS are given, all tests (1, 2, 3) will run."
|
||
echo "Examples:"
|
||
echo " $0 # run all tests"
|
||
echo " $0 1 # run only TEST 1"
|
||
echo " $0 2 3 # run only TEST 2 and TEST 3"
|
||
return 0
|
||
fi
|
||
|
||
SELECTED_TESTS=("$@")
|
||
|
||
echo
|
||
echo "========================================"
|
||
echo "Phase 2 Testing Script – Force Stop Recovery"
|
||
echo "========================================"
|
||
echo
|
||
echo "This script will guide you through Phase 2 tests."
|
||
echo "You'll be prompted when UI interaction is needed."
|
||
echo
|
||
|
||
pause
|
||
|
||
require_adb_device
|
||
build_app
|
||
install_app
|
||
|
||
if should_run_test "1" SELECTED_TESTS; then
|
||
test1_force_stop_cleared_alarms
|
||
pause
|
||
fi
|
||
|
||
if should_run_test "2" SELECTED_TESTS; then
|
||
test2_force_stop_intact_alarms
|
||
pause
|
||
fi
|
||
|
||
if should_run_test "3" SELECTED_TESTS; then
|
||
test3_first_launch_no_schedules
|
||
fi
|
||
|
||
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 " - Verify FORCE_STOP scenario is not incorrectly triggered when alarms are still present"
|
||
echo
|
||
echo "TEST 3: First Launch / No Schedules"
|
||
echo " - Confirm that no force-stop recovery runs, or that NONE/FIRST_LAUNCH 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 "$@"
|