Files
daily-notification-plugin/test-apps/android-test-app/test-phase2.sh
Matthew Raymer f6df9e13fb feat: add operator console and wire test scripts with event emission
- Add TestEventsPlugin for receiving ADB broadcast intents
- Create operator console UI (console/index.html, console.css, console.js)
- Add test plan structure (plan.json) with phases, tests, and steps
- Wire all test scripts (phase1, phase2, phase3) with step context and events
- Add event emission helpers to alarm-test-lib.sh (step_start, step_pass, etc.)
- Update test-phase1.sh with comprehensive prerequisite verification
- Register TestEventsPlugin in capacitor.plugins.json
- Add console documentation (CONSOLE-USAGE.md, CONSOLE-REMAINING-WORK.md)
- Add test implementation alignment tracking (TEST-IMPLEMENTATION-ALIGNMENT.md)

This enables real-time test progress tracking via structured events
from shell scripts to the operator console UI.
2025-12-29 09:37:12 +00:00

614 lines
22 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# ========================================
# 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"
# Initialize run directory (P1)
ensure_run_dir || {
error "Failed to initialize run directory"
exit 1
}
# 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"
# Strictness policy (P3.2)
# soft: minor device quirks = warn (default, for development)
# hard: any unexpected alarm loss / missed schedule = fail (for release gating)
: "${STRICTNESS:=soft}"
# 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"
# Set test context
set_test_context "phase2" "phase2_test1" ""
info "Purpose: Verify force stop detection and alarm rescheduling when alarms are cleared."
info "Expected time: 5-8 minutes"
info "Automatable: Partial (requires manual force-stop verification)"
echo ""
pause
# Capture initial state
set_test_context "phase2" "phase2_test1" "p2_t1_s1"
step_start "p2_t1_s1" "Launch app & check plugin status"
capture_alarms "phase2_test1_initial"
capture_logcat "phase2_test1_initial" "DNP" 50
substep "Step 1: Launch app & check plugin status"
launch_app
ui_prompt "1) In the app UI, verify plugin status:
⚙️ Plugin Settings: ✅ Configured
🔌 Native Fetcher: ✅ Configured
If either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
ui_prompt "2) Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
step_pass "p2_t1_s1" "Plugin configured and notification scheduled"
# Capture before force-stop state
capture_alarms "phase2_test1_before_force_stop"
substep "Step 2: Verify alarms are scheduled"
set_test_context "phase2" "phase2_test1" "p2_t1_s2"
step_start "p2_t1_s2" "Verify alarms 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."
step_warn "p2_t1_s2" "No alarms found"
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
step_pass "p2_t1_s2" "Alarms verified"
else
warn "Found $before_count plugin alarms (expected: 1)"
step_warn "p2_t1_s2" "Unexpected alarm count"
fi
pause
substep "Step 3: Force stop app (should clear alarms on many devices)"
set_test_context "phase2" "phase2_test1" "p2_t1_s3"
step_start "p2_t1_s3" "Force stop app"
force_stop_app
# Capture after force-stop state
capture_alarms "phase2_test1_after_force_stop"
step_pass "p2_t1_s3" "App force stopped"
substep "Step 4: Check alarms after force stop"
set_test_context "phase2" "phase2_test1" "p2_t1_s4"
step_start "p2_t1_s4" "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
if [[ "$STRICTNESS" == "hard" ]]; then
error "Plugin alarms still present after force stop (strict mode: hard)"
step_fail "p2_t1_s4" "Alarms not cleared"
else
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."
step_warn "p2_t1_s4" "Alarms may not be cleared (device-specific)"
fi
else
step_pass "p2_t1_s4" "Alarms cleared"
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_BIN 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"
set_test_context "phase2" "phase2_test1" "p2_t1_s5"
step_start "p2_t1_s5" "Relaunch app & verify recovery"
clear_logs
launch_app
sleep 5 # give recovery a moment to run
# Capture after recovery state
capture_alarms "phase2_test1_after_recovery"
capture_logcat "phase2_test1_after_recovery" "DNP-REACTIVATION" 250
capture_screenshot "phase2_test1_after_recovery"
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
# Determine verdict based on STRICTNESS policy
local test1_passed=false
local test1_message=""
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
if [[ "$STRICTNESS" == "hard" ]]; then
test1_message="Recovery reported errors (errors=$errors)"
else
test1_message="Recovery reported errors but continuing (errors=$errors, strictness=soft)"
fi
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)."
test1_passed=true
test1_message="Force stop detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)"
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
if [[ "$STRICTNESS" == "hard" ]]; then
test1_message="scenario=FORCE_STOP but rescheduled=0 (strict mode: hard)"
else
warn "TEST 1: scenario=FORCE_STOP but rescheduled=0. Check implementation or logs."
test1_message="scenario=FORCE_STOP but rescheduled=0 (strictness=soft)"
fi
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"
test1_passed=true
test1_message="Recovery worked but FORCE_STOP scenario not detected (device kept alarms, rescheduled=$rescheduled, scenario=${scenario:-COLD_START})"
else
if [[ "$STRICTNESS" == "hard" ]]; then
test1_message="Device kept alarms but recovery didn't reschedule (strict mode: hard)"
else
test1_message="Device kept alarms, FORCE_STOP scenario may not trigger (strictness=soft)"
fi
fi
else
if [[ "$STRICTNESS" == "hard" ]]; then
test1_message="Expected FORCE_STOP scenario not detected (strict mode: hard, scenario=${scenario:-<none>}, rescheduled=$rescheduled)"
else
warn "TEST 1: Expected FORCE_STOP scenario not clearly detected. Review logs and scenario detection logic."
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
test1_message="FORCE_STOP scenario not clearly detected (scenario=${scenario:-<none>}, rescheduled=$rescheduled, strictness=soft)"
fi
fi
# Emit verdict
if [[ "$test1_passed" == "true" ]]; then
step_pass "p2_t1_s5" "Recovery successful"
else
step_fail "p2_t1_s5" "Recovery failed or inconclusive"
fi
set_test_context "phase2" "phase2_test1" "p2_t1_s6"
if [[ "$test1_passed" == "true" ]]; then
verdict_pass "phase2_test1_force_stop_cleared" "$test1_message"
elif [[ "$STRICTNESS" == "hard" ]]; then
verdict_fail "phase2_test1_force_stop_cleared" "$test1_message"
else
verdict_warn "phase2_test1_force_stop_cleared" "$test1_message"
fi
evidence_block "phase2_test1_force_stop_cleared"
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"
# Set test context
set_test_context "phase2" "phase2_test2" ""
info "Purpose: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
info "Expected time: 4-6 minutes"
info "Automatable: Partial (requires manual verification)"
echo ""
pause
# Capture initial state
set_test_context "phase2" "phase2_test2" "p2_t2_s1"
step_start "p2_t2_s1" "Schedule notification"
capture_alarms "phase2_test2_initial"
capture_logcat "phase2_test2_initial" "DNP" 50
substep "Step 1: Launch app & schedule notifications"
launch_app
ui_prompt "1) In the app UI, ensure plugin is configured and schedule at least one future notification.
Press Enter when done."
step_pass "p2_t2_s1" "Notification scheduled"
# Capture before soft stop state
capture_alarms "phase2_test2_before_soft_stop"
substep "Step 2: Verify alarms are scheduled"
set_test_context "phase2" "phase2_test2" "p2_t2_s2"
step_start "p2_t2_s2" "Force stop app"
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."
step_warn "p2_t2_s2" "No alarms found"
elif [[ "$before" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
step_pass "p2_t2_s2" "Alarms verified"
else
warn "Found $before plugin alarms (expected: 1)"
step_warn "p2_t2_s2" "Unexpected alarm count"
fi
pause
substep "Step 3: Simulate a 'soft' stop or process kill that does NOT clear alarms"
set_test_context "phase2" "phase2_test2" "p2_t2_s2"
step_start "p2_t2_s2" "Force stop app"
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)"
step_pass "p2_t2_s2" "App force stopped"
# Capture after soft stop state
capture_alarms "phase2_test2_after_soft_stop"
substep "Step 4: Verify alarms are still scheduled"
set_test_context "phase2" "phase2_test2" "p2_t2_s3"
step_start "p2_t2_s3" "Verify alarms intact"
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
if [[ "$STRICTNESS" == "hard" ]]; then
error "Alarms cleared after soft stop (strict mode: hard)"
step_fail "p2_t2_s3" "Alarms cleared"
else
warn "Alarms appear cleared after soft stop; this environment may not distinguish TEST 2 well."
step_warn "p2_t2_s3" "Alarms may be cleared"
fi
else
step_pass "p2_t2_s3" "Alarms intact"
fi
pause
substep "Step 5: Relaunch app and check recovery logs"
set_test_context "phase2" "phase2_test2" "p2_t2_s4"
step_start "p2_t2_s4" "Relaunch & verify behavior"
clear_logs
launch_app
sleep 5
# Capture after recovery state
capture_alarms "phase2_test2_after_recovery"
capture_logcat "phase2_test2_after_recovery" "DNP-REACTIVATION" 250
capture_screenshot "phase2_test2_after_recovery"
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
# Determine verdict based on STRICTNESS policy
local test2_passed=false
local test2_message=""
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
if [[ "$STRICTNESS" == "hard" ]]; then
test2_message="Recovery reported errors (errors=$errors)"
else
test2_message="Recovery reported errors but continuing (errors=$errors, strictness=soft)"
fi
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)."
test2_passed=true
test2_message="Alarms remained intact, FORCE_STOP scenario did not run (scenario=$scenario, rescheduled=0)"
else
if [[ "$STRICTNESS" == "hard" ]]; then
if [[ "$after" -eq 0 ]]; then
test2_message="Alarms were cleared but should have remained (strict mode: hard)"
elif [[ "$rescheduled" -gt 0 ]]; then
test2_message="FORCE_STOP recovery ran when alarms were intact (strict mode: hard, scenario=$scenario, rescheduled=$rescheduled)"
else
test2_message="Unexpected state (strict mode: hard, scenario=$scenario, rescheduled=$rescheduled, after=$after)"
fi
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"
test2_message="FORCE_STOP recovery may have misfired (scenario=$scenario, rescheduled=$rescheduled, after=$after, strictness=soft)"
fi
fi
if [[ "$test2_passed" == "true" ]]; then
step_pass "p2_t2_s4" "Recovery behavior correct"
else
step_fail "p2_t2_s4" "Recovery behavior incorrect"
fi
# Emit verdict
set_test_context "phase2" "phase2_test2" "p2_t2_s5"
if [[ "$test2_passed" == "true" ]]; then
verdict_pass "phase2_test2_force_stop_intact" "$test2_message"
elif [[ "$STRICTNESS" == "hard" ]]; then
verdict_fail "phase2_test2_force_stop_intact" "$test2_message"
else
verdict_warn "phase2_test2_force_stop_intact" "$test2_message"
fi
evidence_block "phase2_test2_force_stop_intact"
}
# ------------------------------------------------------------------------------
# TEST 3 First Launch / Empty DB Safeguard
# ------------------------------------------------------------------------------
test3_first_launch_no_schedules() {
section "TEST 3: First Launch / No Schedules Safeguard"
# Set test context
set_test_context "phase2" "phase2_test3" ""
info "Purpose: Ensure force-stop recovery is NOT triggered when DB is empty or plugin isn't configured."
info "Expected time: 3-5 minutes"
info "Automatable: Yes"
echo ""
pause
# Capture initial state (before uninstall)
set_test_context "phase2" "phase2_test3" "p2_t3_s1"
step_start "p2_t3_s1" "Fresh install"
capture_alarms "phase2_test3_initial"
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"
step_pass "p2_t3_s1" "Fresh install complete"
else
error "Reinstall failed"
step_fail "p2_t3_s1" "Reinstall failed"
exit 1
fi
info "Clearing logcat..."
clear_logs
ok "Logs cleared"
pause
substep "Step 3: Launch app for the first time"
set_test_context "phase2" "phase2_test3" "p2_t3_s2"
step_start "p2_t3_s2" "Launch app & verify no recovery"
launch_app
sleep 5
# Capture after first launch state
capture_alarms "phase2_test3_after_first_launch"
capture_logcat "phase2_test3_after_first_launch" "DNP-REACTIVATION" 250
capture_screenshot "phase2_test3_after_first_launch"
substep "Step 4: Collect logs and ensure no force-stop recovery ran"
set_test_context "phase2" "phase2_test3" "p2_t3_s3"
step_start "p2_t3_s3" "Verify no 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
# Determine verdict based on STRICTNESS policy
local test3_passed=false
local test3_message=""
if [[ -z "$logs" ]]; then
ok "TEST 3 PASSED: No force-stop recovery logs on first launch."
test3_passed=true
test3_message="No force-stop recovery logs on first launch (expected behavior)"
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
ok "TEST 3 PASSED: NONE scenario logged with rescheduled=0 on first launch."
test3_passed=true
test3_message="NONE scenario logged with rescheduled=0 on first launch (expected behavior)"
elif [[ "$rescheduled" -gt 0 ]]; then
if [[ "$STRICTNESS" == "hard" ]]; then
test3_message="rescheduled>0 on first launch / empty DB - force-stop recovery misfired (strict mode: hard, rescheduled=$rescheduled)"
else
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring."
test3_message="rescheduled>0 on first launch (rescheduled=$rescheduled, strictness=soft)"
fi
else
if [[ "$STRICTNESS" == "hard" ]]; then
test3_message="Logs present but scenario unclear (strict mode: hard, scenario=${scenario:-<none>}, rescheduled=$rescheduled)"
else
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / FIRST_LAUNCH."
test3_passed=true # Not a failure, just needs review
test3_message="Logs present but no rescheduling (scenario=${scenario:-<none>}, rescheduled=$rescheduled, strictness=soft)"
fi
fi
if [[ "$test3_passed" == "true" ]]; then
step_pass "p2_t3_s3" "No recovery ran (correct)"
else
step_fail "p2_t3_s3" "Recovery ran when it shouldn't"
fi
# Emit verdict
set_test_context "phase2" "phase2_test3" "p2_t3_s4"
if [[ "$test3_passed" == "true" ]]; then
verdict_pass "phase2_test3_first_launch_no_schedules" "$test3_message"
elif [[ "$STRICTNESS" == "hard" ]]; then
verdict_fail "phase2_test3_first_launch_no_schedules" "$test3_message"
else
verdict_warn "phase2_test3_first_launch_no_schedules" "$test3_message"
fi
evidence_block "phase2_test3_first_launch_no_schedules"
}
# ------------------------------------------------------------------------------
# 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...] [STRICTNESS=soft|hard]"
echo
echo "If no TEST_IDS are given, all tests (1, 2, 3) will run."
echo
echo "STRICTNESS policy (P3.2):"
echo " soft (default): minor device quirks = warn"
echo " hard: any unexpected alarm loss / missed schedule = fail"
echo
echo "Examples:"
echo " $0 # run all tests (soft mode)"
echo " $0 1 # run only TEST 1 (soft mode)"
echo " $0 2 3 # run only TEST 2 and TEST 3 (soft mode)"
echo " STRICTNESS=hard $0 # run all tests (hard mode, release gating)"
return 0
fi
SELECTED_TESTS=("$@")
section "Phase 2 Testing Script Force Stop Recovery"
info "Mode: Standard"
info "Strictness: ${STRICTNESS} (soft=warn on quirks, hard=fail on issues)"
info "Run ID: ${RUN_ID}"
info "Evidence directory: $(get_run_dir)"
echo ""
info "This script will guide you through Phase 2 tests."
info "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"
info "Test Results Summary:"
echo ""
echo "All test verdicts are shown above with evidence locations."
echo "Review evidence in: $(get_run_dir)"
echo ""
echo "Strictness mode: ${STRICTNESS}"
echo " - soft: Minor device quirks treated as warnings"
echo " - hard: Any unexpected behavior treated as failures"
echo ""
ok "Phase 2 testing script complete!"
echo ""
echo "Next steps:"
echo " - Review evidence in: $(get_run_dir)"
echo " - Verify all test verdicts above"
echo " - Update documentation with test results"
echo ""
}
main "$@"