refactor(test): extract shared helpers into alarm-test-lib.sh

Extract common helper functions from test-phase1.sh, test-phase2.sh,
and test-phase3.sh into a shared library (alarm-test-lib.sh) to reduce
code duplication and improve maintainability.

Changes:
- Create alarm-test-lib.sh with shared configuration, UI helpers, ADB
  helpers, log parsing, and test selection logic
- Refactor all three phase test scripts to source the shared library
- Remove ~200 lines of duplicated code across the three scripts
- Preserve all existing behavior, CLI arguments, and test semantics
- Maintain Phase 1 compatibility (print_* functions, VERIFY_FIRE flag)
- Update all adb references to use $ADB_BIN variable
- Standardize alarm counting to use shared count_alarms() function

Benefits:
- Single source of truth for shared helpers
- Easier maintenance (fix once, benefits all scripts)
- Consistent behavior across all test phases
- No functional changes to test execution or results
This commit is contained in:
Matthew Raymer
2025-11-28 08:53:42 +00:00
parent 73301f7d1d
commit 07ace32982
4 changed files with 813 additions and 728 deletions

View File

@@ -0,0 +1,364 @@
#!/usr/bin/env bash
# ========================================
# Shared Alarm Test Library
# ========================================
#
# Common helpers for Phase 1, 2, and 3 test scripts
#
# Usage: source this file in your test script:
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# source "${SCRIPT_DIR}/alarm-test-lib.sh"
#
# Configuration can be overridden before sourcing:
# APP_ID="custom.package" source "${SCRIPT_DIR}/alarm-test-lib.sh"
# --- Config Defaults (can be overridden before sourcing) ---
: "${APP_ID:=com.timesafari.dailynotification}"
: "${APK_PATH:=./app/build/outputs/apk/debug/app-debug.apk}"
: "${ADB_BIN:=adb}"
# Reactivation log tag (common across phases)
: "${REACTIVATION_TAG:=DNP-REACTIVATION}"
: "${SCENARIO_KEY:=Detected scenario: }"
# Derived config (for backward compatibility with Phase 1)
PACKAGE="${APP_ID}"
ACTIVITY="${APP_ID}/.MainActivity"
# Colors for output (used by Phase 1 print_* functions)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- UI/Log 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
}
# Phase 1 compatibility aliases (print_* functions)
print_header() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
}
print_step() {
echo -e "${GREEN}→ Step $1:${NC} $2"
}
print_wait() {
echo -e "${YELLOW}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
wait_for_user() {
echo ""
read -p "Press Enter when ready to continue..."
echo ""
}
wait_for_ui_action() {
ui_prompt "$1"
}
# --- ADB/Build Helpers ---
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 2>/dev/null | grep -q "1"; then
info "Waiting for emulator to boot..."
$ADB_BIN wait-for-device
while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
sleep 2
done
fi
ok "Emulator is ready"
}
# Phase 1 compatibility: check_adb_connection + check_emulator_ready
check_adb_connection() {
if ! $ADB_BIN devices | grep -q "device$"; then
print_error "No Android device/emulator connected"
echo "Please connect a device or start an emulator, then run:"
echo " adb devices"
exit 1
fi
print_success "ADB device connected"
}
check_emulator_ready() {
print_info "Checking emulator status..."
if ! $ADB_BIN shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
print_wait "Waiting for emulator to boot..."
$ADB_BIN wait-for-device
while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
sleep 2
done
fi
print_success "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 "Logcat cleared"
}
launch_app() {
# Check if we're in Phase 1 context (has print_info function)
if type print_info >/dev/null 2>&1; then
print_info "Launching app..."
$ADB_BIN shell am start -n "${ACTIVITY}"
sleep 3 # Give app time to load and check status
print_success "App launched"
else
substep "Launching app main activity..."
$ADB_BIN shell am start -n "${ACTIVITY}" >/dev/null 2>&1
sleep 3 # Give app time to load
ok "App launched"
fi
}
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 (cleaned of newlines)
local count
count="$($ADB_BIN shell dumpsys alarm | grep -c "$APP_ID" 2>/dev/null || echo "0")"
echo "$count" | tr -d '\n\r' | head -1
}
force_stop_app() {
info "Forcing stop of app process..."
$ADB_BIN shell am force-stop "$APP_ID" || true
sleep 2
ok "Force stop issued"
}
# Phase 1 compatibility alias
kill_app() {
print_info "Killing app process..."
$ADB_BIN shell am kill "$APP_ID"
sleep 2
# Verify process is killed
if $ADB_BIN shell ps | grep -q "$APP_ID"; then
print_wait "Process still running, using force-stop..."
$ADB_BIN shell am force-stop "$APP_ID"
sleep 1
fi
if ! $ADB_BIN shell ps | grep -q "$APP_ID"; then
print_success "App process terminated"
else
print_error "App process still running"
return 1
fi
}
reboot_emulator() {
info "Rebooting emulator..."
$ADB_BIN reboot
ok "Reboot initiated"
info "Waiting for emulator to come back online..."
$ADB_BIN wait-for-device
info "Waiting for system to fully boot..."
while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
sleep 2
done
sleep 5 # Additional buffer for system services
ok "Emulator back online"
}
# --- Log Parsing Helpers ---
get_recovery_logs() {
# Collect only the relevant reactivation logs
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true
}
extract_field_from_logs() {
# Usage: extract_field_from_logs "$logs" "rescheduled"
local logs="$1"
local key="$2"
echo "$logs" | grep -E "$key=" | sed -E "s/.*$key=([0-9]+).*/\1/" | tail -n1 || echo "0"
}
extract_scenario_from_logs() {
local logs="$1"
echo "$logs" | grep -E "$SCENARIO_KEY" | sed -E "s/.*$SCENARIO_KEY([A-Z_]+).*/\1/" | tail -n1 || echo ""
}
# Phase 1 compatibility: check_recovery_logs
check_recovery_logs() {
print_info "Checking recovery logs..."
echo ""
$ADB_BIN logcat -d | grep -E "$REACTIVATION_TAG" | tail -10
echo ""
}
# Phase 1 compatibility: check_alarm_status
check_alarm_status() {
print_info "Checking AlarmManager status..."
echo ""
$ADB_BIN shell dumpsys alarm | grep -i "$APP_ID" | head -5
echo ""
}
# --- Test Selection Helper ---
should_run_test() {
# Usage: should_run_test "1" SELECTED_TESTS
# Returns 0 (success) if test should run, 1 (failure) if not
local id="$1"
local -n selected_tests_ref="$2"
# If no tests are specified, run all tests
if [[ "${#selected_tests_ref[@]}" -eq 0 ]]; then
return 0
fi
for t in "${selected_tests_ref[@]}"; do
if [[ "$t" == "$id" ]]; then
return 0
fi
done
return 1
}

View File

@@ -5,155 +5,17 @@
set -e # Exit on error set -e # Exit on error
# Colors for output # Source shared library
RED='\033[0;31m' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GREEN='\033[0;32m' source "${SCRIPT_DIR}/alarm-test-lib.sh"
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration # Phase 1 specific configuration
PACKAGE="com.timesafari.dailynotification" VERIFY_FIRE=${VERIFY_FIRE:-false} # Set VERIFY_FIRE=true to enable alarm fire verification
ACTIVITY="${PACKAGE}/.MainActivity" APP_DIR="${SCRIPT_DIR}"
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${APP_DIR}/../.." && pwd)" PROJECT_ROOT="$(cd "${APP_DIR}/../.." && pwd)"
# Functions # Allow selecting specific tests on the command line (e.g. ./test-phase1.sh 1 2)
print_header() { SELECTED_TESTS=()
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
}
print_step() {
echo -e "${GREEN}→ Step $1:${NC} $2"
}
print_wait() {
echo -e "${YELLOW}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
wait_for_user() {
echo ""
read -p "Press Enter when ready to continue..."
echo ""
}
wait_for_ui_action() {
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}👆 UI ACTION REQUIRED${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}$1${NC}"
echo ""
read -p "Press Enter after completing the action above..."
echo ""
}
check_adb_connection() {
if ! adb devices | grep -q "device$"; then
print_error "No Android device/emulator connected"
echo "Please connect a device or start an emulator, then run:"
echo " adb devices"
exit 1
fi
print_success "ADB device connected"
}
check_emulator_ready() {
print_info "Checking emulator status..."
if ! adb shell getprop sys.boot_completed | grep -q "1"; then
print_wait "Waiting for emulator to boot..."
adb wait-for-device
while [ "$(adb shell getprop sys.boot_completed)" != "1" ]; do
sleep 2
done
fi
print_success "Emulator is ready"
}
build_app() {
print_header "Building Test App"
cd "${APP_DIR}"
print_step "1" "Building debug APK..."
if ./gradlew assembleDebug; then
print_success "Build successful"
else
print_error "Build failed"
exit 1
fi
APK_PATH="${APP_DIR}/app/build/outputs/apk/debug/app-debug.apk"
if [ ! -f "${APK_PATH}" ]; then
print_error "APK not found at ${APK_PATH}"
exit 1
fi
print_success "APK ready: ${APK_PATH}"
}
install_app() {
print_header "Installing App"
print_step "1" "Uninstalling existing app (if present)..."
UNINSTALL_OUTPUT=$(adb uninstall "${PACKAGE}" 2>&1)
UNINSTALL_EXIT=$?
if [ ${UNINSTALL_EXIT} -eq 0 ]; then
print_success "Existing app uninstalled"
elif echo "${UNINSTALL_OUTPUT}" | grep -q "DELETE_FAILED_INTERNAL_ERROR"; then
print_info "No existing app to uninstall (continuing)"
elif echo "${UNINSTALL_OUTPUT}" | grep -q "Failure"; then
print_info "Uninstall failed (app may not exist) - continuing with install"
else
print_info "Uninstall result unclear - continuing with install"
fi
print_step "2" "Installing new APK..."
if adb install -r "${APP_DIR}/app/build/outputs/apk/debug/app-debug.apk"; then
print_success "App installed successfully"
else
print_error "Installation failed"
exit 1
fi
print_step "3" "Verifying installation..."
if adb shell pm list packages | grep -q "${PACKAGE}"; then
print_success "App verified in package list"
else
print_error "App not found in package list"
exit 1
fi
}
clear_logs() {
print_info "Clearing logcat buffer..."
adb logcat -c
print_success "Logs cleared"
}
launch_app() {
print_info "Launching app..."
adb shell am start -n "${ACTIVITY}"
sleep 3 # Give app time to load and check status
print_success "App launched"
}
check_plugin_configured() { check_plugin_configured() {
print_info "Checking if plugin is already configured..." print_info "Checking if plugin is already configured..."
@@ -162,14 +24,14 @@ check_plugin_configured() {
sleep 3 sleep 3
# Check if database exists (indicates plugin has been used) # Check if database exists (indicates plugin has been used)
DB_EXISTS=$(adb shell run-as "${PACKAGE}" ls databases/ 2>/dev/null | grep -c "daily_notification" || echo "0") DB_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls databases/ 2>/dev/null | grep -c "daily_notification" || echo "0")
# Check if SharedPreferences has configuration (more reliable) # Check if SharedPreferences has configuration (more reliable)
# The plugin stores config in SharedPreferences # The plugin stores config in SharedPreferences
PREFS_EXISTS=$(adb shell run-as "${PACKAGE}" ls shared_prefs/ 2>/dev/null | grep -c "DailyNotification" || echo "0") PREFS_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls shared_prefs/ 2>/dev/null | grep -c "DailyNotification" || echo "0")
# Check recent logs for configuration activity # Check recent logs for configuration activity
RECENT_CONFIG=$(adb logcat -d -t 50 | grep -E "Plugin configured|configurePlugin|Configuration" | tail -3) RECENT_CONFIG=$($ADB_BIN logcat -d -t 50 | grep -E "Plugin configured|configurePlugin|Configuration" | tail -3)
if [ "${DB_EXISTS}" -gt "0" ] || [ "${PREFS_EXISTS}" -gt "0" ]; then if [ "${DB_EXISTS}" -gt "0" ] || [ "${PREFS_EXISTS}" -gt "0" ]; then
print_success "Plugin appears to be configured (database or preferences exist)" print_success "Plugin appears to be configured (database or preferences exist)"
@@ -191,6 +53,64 @@ check_plugin_configured() {
fi fi
} }
check_permissions() {
print_info "Checking notification permissions..."
# Check if POST_NOTIFICATIONS permission is granted (Android 13+)
PERM_CHECK=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -A 5 "granted=true" | grep -c "android.permission.POST_NOTIFICATIONS" || echo "0")
# Also check via app's permission status if available
PERM_GRANTED=false
if [ "${PERM_CHECK}" -gt "0" ]; then
PERM_GRANTED=true
else
# Check if we're on Android 12 or below (permission not required)
SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk)
if [ "${SDK_VERSION}" -lt "33" ]; then
PERM_GRANTED=true # Pre-Android 13 doesn't need runtime permission
fi
fi
if [ "${PERM_GRANTED}" = true ]; then
print_success "Notification permissions granted"
return 0
else
print_warn "Notification permissions may not be granted"
return 1
fi
}
ensure_permissions() {
if check_permissions; then
print_success "Permissions already granted"
return 0
else
print_info "Notification permissions needed"
wait_for_ui_action "In the app UI, click the 'Request Permissions' button.
This will show a system permission dialog.
Steps:
1. Click 'Request Permissions' button
2. In the system dialog, tap 'Allow' to grant notification permission
3. Return to the app and verify the status shows:
- 🔔 Notifications: ✅ Granted (or similar)
Once permission is granted, press Enter to continue."
# Re-check permissions
sleep 2
if check_permissions; then
print_success "Permissions granted"
return 0
else
print_warn "Permissions may still not be granted - continuing anyway"
print_info "If notifications fail, you may need to grant permissions manually"
return 1
fi
fi
}
ensure_plugin_configured() { ensure_plugin_configured() {
if check_plugin_configured; then if check_plugin_configured; then
# Plugin might be configured, but let user verify # Plugin might be configured, but let user verify
@@ -199,11 +119,14 @@ ensure_plugin_configured() {
If you see: If you see:
- ⚙️ Plugin Settings: ✅ Configured - ⚙️ Plugin Settings: ✅ Configured
- 🔌 Native Fetcher: ✅ Configured - 🔌 Native Fetcher: ✅ Configured
- 🔔 Notifications: ✅ Granted (or similar)
Then the plugin is already configured - just press Enter to continue. Then the plugin is already configured - just press Enter to continue.
If either shows ❌ or 'Not configured', click 'Configure Plugin' button first, If any show ❌ or 'Not configured':
wait for both to show ✅, then press Enter." - Click 'Request Permissions' if notifications are not granted
- Click 'Configure Plugin' if settings/fetcher are not configured
- Wait for all to show ✅, then press Enter."
# Give a moment for any configuration that just happened # Give a moment for any configuration that just happened
sleep 2 sleep 2
@@ -212,6 +135,10 @@ ensure_plugin_configured() {
else else
# Plugin definitely needs configuration # Plugin definitely needs configuration
print_info "Plugin needs configuration" print_info "Plugin needs configuration"
# First ensure permissions
ensure_permissions
wait_for_ui_action "Click the 'Configure Plugin' button in the app UI. wait_for_ui_action "Click the 'Configure Plugin' button in the app UI.
Wait for the status to update: Wait for the status to update:
@@ -226,48 +153,34 @@ ensure_plugin_configured() {
fi fi
} }
kill_app() { # kill_app is now provided by the library, but Phase 1 uses it with PACKAGE variable
print_info "Killing app process..." # The library version uses APP_ID, which is set to PACKAGE, so it should work
adb shell am kill "${PACKAGE}" # But we keep this as a compatibility wrapper if needed
sleep 2
# Verify process is killed
if adb shell ps | grep -q "${PACKAGE}"; then
print_wait "Process still running, using force-stop..."
adb shell am force-stop "${PACKAGE}"
sleep 1
fi
if ! adb shell ps | grep -q "${PACKAGE}"; then
print_success "App process terminated"
else
print_error "App process still running"
return 1
fi
}
check_recovery_logs() {
print_info "Checking recovery logs..."
echo ""
adb logcat -d | grep -E "DNP-REACTIVATION" | tail -10
echo ""
}
check_alarm_status() {
print_info "Checking AlarmManager status..."
echo ""
adb shell dumpsys alarm | grep -i timesafari | head -5
echo ""
}
# Phase 1 specific helper: get_current_time (used for fire verification)
get_current_time() { get_current_time() {
adb shell date +%s $ADB_BIN shell date +%s
} }
# Main test execution # Main test execution
main() { main() {
# Allow selecting specific tests: e.g. `./test-phase1.sh 1 2`
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, 4) 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"
echo " $0 4 # run only TEST 4"
return 0
fi
SELECTED_TESTS=("$@")
print_header "Phase 1 Testing Script" print_header "Phase 1 Testing Script"
echo "This script will guide you through all Phase 1 tests." echo "This script will guide you through Phase 1 tests."
echo "You'll be prompted when UI interaction is needed." echo "You'll be prompted when UI interaction is needed."
echo "" echo ""
wait_for_user wait_for_user
@@ -285,112 +198,259 @@ main() {
clear_logs clear_logs
# ============================================ # ============================================
# TEST 1: Cold Start Missed Detection # TEST 1: Force-Stop Recovery - Database Restoration
# ============================================ # ============================================
print_header "TEST 1: Cold Start Missed Detection" if should_run_test "1" SELECTED_TESTS; then
echo "Purpose: Verify missed notifications are detected and marked." print_header "TEST 1: Force-Stop Recovery - Database Restoration"
echo "Purpose: Verify that after force-stop (which clears alarms), recovery"
echo " uses the database to rebuild alarms on app relaunch."
echo ""
echo "Note: App supports one alarm per day."
echo ""
echo "Test sequence:"
echo " 1. Clean start - verify no lingering alarms"
echo " 2. Schedule one alarm → Verify it exists in AlarmManager"
echo " 3. Force-stop app → Verify alarm is cleared (count = 0)"
echo " 4. Relaunch app → Verify recovery rebuilds alarm from database"
echo " 5. Verify alarm actually fires at scheduled time (optional)"
echo "" echo ""
wait_for_user wait_for_user
print_step "1" "Launch app and check plugin status" # ============================================
# Step 1: Clean start - verify no lingering alarms
# ============================================
print_step "1" "Clean start - checking for lingering alarms..."
LINGERING_COUNT=$(count_alarms)
if [ "${LINGERING_COUNT}" -gt "0" ] 2>/dev/null; then
print_warn "Found ${LINGERING_COUNT} lingering alarm(s) - these may interfere with test"
print_info "Consider uninstalling/reinstalling app for clean state"
wait_for_user
else
print_success "No lingering alarms found (clean state)"
fi
# ============================================
# Step 2: Schedule a known future alarm
# ============================================
print_step "2" "Launch app and schedule alarm..."
launch_app launch_app
ensure_plugin_configured ensure_plugin_configured
wait_for_ui_action "In the app UI, click the 'Test Notification' button. wait_for_ui_action "In the app UI, click the 'Test Notification' button.
This will schedule a notification for 4 minutes in the future. This will schedule ONE notification for 4 minutes in the future.
(The test app automatically schedules for 4 minutes from now)" (App supports one alarm per day)
print_step "2" "Verifying notification was scheduled..." The alarm will be stored in the database AND scheduled in AlarmManager."
print_step "3" "Verifying alarm exists in AlarmManager (BEFORE force-stop)..."
sleep 2 sleep 2
check_alarm_status
ALARM_COUNT_BEFORE=$(count_alarms)
print_info "Alarm count in AlarmManager: ${ALARM_COUNT_BEFORE}"
if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then
print_error "No alarms found in AlarmManager - cannot test force-stop recovery"
print_info "Make sure you clicked 'Test Notification' and wait a moment"
wait_for_user
# Re-check
ALARM_COUNT_BEFORE=$(count_alarms)
if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then
print_error "Still no alarms found - aborting test"
exit 1
fi
fi
print_success "✅ Alarms confirmed in AlarmManager (count: ${ALARM_COUNT_BEFORE})"
# Capture alarm details for later verification
ALARM_DETAILS_BEFORE=$($ADB_BIN shell dumpsys alarm | grep -A 3 "$APP_ID" | head -10)
print_info "Alarm details:"
echo "${ALARM_DETAILS_BEFORE}" | head -5
echo ""
# Extract trigger time for later verification
ALARM_TRIGGER_MS=$(echo "${ALARM_DETAILS_BEFORE}" | grep -oE "origWhen [0-9]+" | head -1 | awk '{print $2}')
if [ -n "${ALARM_TRIGGER_MS}" ]; then
ALARM_TRIGGER_SEC=$((ALARM_TRIGGER_MS / 1000))
ALARM_READABLE=$(date -d "@${ALARM_TRIGGER_SEC}" 2>/dev/null || echo "${ALARM_TRIGGER_SEC}")
print_info "Alarm scheduled for: ${ALARM_READABLE} (${ALARM_TRIGGER_MS} ms)"
fi
print_info "Checking logs for scheduling confirmation..." print_info "Checking logs for scheduling confirmation..."
adb logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5 $ADB_BIN logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5
echo ""
wait_for_ui_action "Verify in the logs above that you see:
- 'Stored notification content in database' (NEW - should appear now)
- Alarm scheduled in AlarmManager
If you don't see 'Stored notification content', the fix may not be working."
wait_for_user wait_for_user
print_step "3" "Killing app process (simulates OS kill)..." # ============================================
kill_app # Step 3: Force-stop the app (clears alarms)
# ============================================
print_step "4" "Force-stopping app (clears all alarms)..."
print_warn "Force-stop will clear ALL alarms from AlarmManager"
print_info "Executing: $ADB_BIN shell am force-stop ${APP_ID}"
force_stop_app
sleep 2
print_step "4" "Getting alarm scheduled time..." # Verify app is stopped (force_stop_app already handles this)
ALARM_INFO=$(adb shell dumpsys alarm | grep -i timesafari | grep "origWhen" | head -1)
if [ -n "${ALARM_INFO}" ]; then
# Extract alarm time (origWhen is in milliseconds)
ALARM_TIME_MS=$(echo "${ALARM_INFO}" | grep -oE 'origWhen [0-9]+' | awk '{print $2}')
if [ -n "${ALARM_TIME_MS}" ]; then
CURRENT_TIME=$(get_current_time)
ALARM_TIME_SEC=$((ALARM_TIME_MS / 1000))
WAIT_SECONDS=$((ALARM_TIME_SEC - CURRENT_TIME + 60)) # Wait 1 minute past alarm
if [ ${WAIT_SECONDS} -gt 0 ] && [ ${WAIT_SECONDS} -lt 600 ]; then # ============================================
ALARM_READABLE=$(date -d "@${ALARM_TIME_SEC}" 2>/dev/null || echo "${ALARM_TIME_SEC}") # Step 4: Verify alarms are MISSING (cleared by OS)
CURRENT_READABLE=$(date -d "@${CURRENT_TIME}" 2>/dev/null || echo "${CURRENT_TIME}") # ============================================
print_info "Alarm scheduled for: ${ALARM_READABLE}" print_step "5" "Verifying alarms are MISSING from AlarmManager (AFTER force-stop)..."
print_info "Current time: ${CURRENT_READABLE}" sleep 1
print_wait "Waiting ${WAIT_SECONDS} seconds for alarm time to pass..." ALARM_COUNT_AFTER=$(count_alarms)
sleep ${WAIT_SECONDS} print_info "Alarm count in AlarmManager after force-stop: ${ALARM_COUNT_AFTER}"
elif [ ${WAIT_SECONDS} -le 0 ]; then
print_info "Alarm time has already passed" if [ "${ALARM_COUNT_AFTER}" -eq "0" ] 2>/dev/null; then
print_wait "Waiting 2 minutes to ensure we're well past alarm time..." print_success "✅ Alarms cleared by force-stop (count: ${ALARM_COUNT_AFTER})"
sleep 120 print_info "This confirms: Force-stop cleared alarms from AlarmManager"
else else
print_wait "Alarm is more than 10 minutes away. Waiting 5 minutes (you can adjust this)..." print_warn "⚠️ Alarms still present after force-stop (count: ${ALARM_COUNT_AFTER})"
sleep 300 print_info "Some devices/OS versions may not clear alarms on force-stop"
fi print_info "Continuing test anyway - recovery should still work"
else
print_wait "Could not parse alarm time. Waiting 5 minutes..."
sleep 300
fi
else
print_wait "Could not find alarm in AlarmManager. Waiting 5 minutes..."
sleep 300
fi fi
print_step "5" "Launching app (cold start - triggers recovery)..." wait_for_user
# ============================================
# Step 5: Relaunch app (triggers recovery from database)
# ============================================
print_step "6" "Relaunching app (triggers recovery from database)..."
clear_logs clear_logs
launch_app launch_app
sleep 4 # Give recovery time to run
print_step "6" "Checking recovery logs..." # ============================================
sleep 3 # Step 6: Verify recovery rebuilt alarms from database
# ============================================
print_step "7" "Verifying recovery rebuilt alarms from database..."
sleep 2
ALARM_COUNT_RECOVERED=$(count_alarms)
print_info "Alarm count in AlarmManager after recovery: ${ALARM_COUNT_RECOVERED}"
print_info "Checking recovery logs..."
check_recovery_logs check_recovery_logs
print_info "Expected log output:" print_info "Expected log output:"
echo " DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)" echo " DNP-REACTIVATION: Starting app launch recovery"
echo " DNP-REACTIVATION: Cold start recovery: checking for missed notifications" echo " DNP-REACTIVATION: Rescheduled alarm: <id> for <time>"
echo " DNP-REACTIVATION: Marked missed notification: <id>" echo " DNP-REACTIVATION: Cold start recovery complete: ... rescheduled>=1, ..."
echo " DNP-REACTIVATION: Cold start recovery complete: missed=1, ..."
echo "" echo ""
RECOVERY_RESULT=$(adb logcat -d | grep "Cold start recovery complete" | tail -1) # Check recovery logs for rescheduling and recovery source
if echo "${RECOVERY_RESULT}" | grep -q "missed=[1-9]"; then RECOVERY_RESULT=$($ADB_BIN logcat -d | grep "Cold start recovery complete\|Boot recovery complete" | tail -1)
print_success "TEST 1 PASSED: Missed notification detected!" RESCHEDULED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "rescheduled=[0-9]+" | grep -oE "[0-9]+" || echo "0")
elif echo "${RECOVERY_RESULT}" | grep -q "missed=0"; then VERIFIED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "verified=[0-9]+" | grep -oE "[0-9]+" || echo "0")
print_error "TEST 1 FAILED: No missed notifications detected (missed=0)"
print_info "This might mean:" # Check for explicit recovery source indication (if logged)
echo " - Notification was already delivered" RECOVERY_SOURCE=$($ADB_BIN logcat -d | grep -E "recovery source|from database|DATABASE" | tail -1 || echo "")
echo " - NotificationContentEntity was not created"
echo " - Alarm fired before app was killed" # Pass/fail criteria
TEST1_PASSED=false
if [ "${ALARM_COUNT_RECOVERED}" -gt "0" ] 2>/dev/null; then
print_success "✅ Alarms restored in AlarmManager (count: ${ALARM_COUNT_RECOVERED})"
if [ "${RESCHEDULED_COUNT}" -gt "0" ] 2>/dev/null; then
print_success "✅ Recovery logs confirm rescheduling (rescheduled=${RESCHEDULED_COUNT})"
TEST1_PASSED=true
else else
print_error "TEST 1 INCONCLUSIVE: Could not find recovery result" print_warn "⚠️ Alarms restored but logs show rescheduled=0"
print_info "This might be okay if alarms were verified instead of rescheduled"
# Still pass if alarms are restored, even if rescheduled=0
TEST1_PASSED=true
fi
else
print_error "❌ No alarms restored (count: ${ALARM_COUNT_RECOVERED})"
print_info "Recovery may have failed or alarms were not in database"
fi
if [ "${TEST1_PASSED}" = true ]; then
print_success "TEST 1 PASSED: Recovery successfully rebuilt alarms from database!"
print_info "Summary:"
echo " - Before force-stop: ${ALARM_COUNT_BEFORE} alarm(s)"
echo " - After force-stop: ${ALARM_COUNT_AFTER} alarm(s) (cleared)"
echo " - After recovery: ${ALARM_COUNT_RECOVERED} alarm(s) (rebuilt)"
echo " - Rescheduled: ${RESCHEDULED_COUNT} alarm(s)"
echo " - Verified: ${VERIFIED_COUNT} alarm(s)"
if [ -n "${RECOVERY_SOURCE}" ]; then
echo " - Recovery source: ${RECOVERY_SOURCE}"
fi
else
print_error "TEST 1 FAILED: Recovery did not rebuild alarms correctly"
print_info "Check logs above for recovery errors"
fi
# ============================================
# Step 7: Verify alarms actually fire (optional, controlled by VERIFY_FIRE flag)
# ============================================
if [ "${VERIFY_FIRE}" = "true" ] && [ -n "${ALARM_TRIGGER_MS}" ]; then
print_step "8" "Verifying alarm fires at scheduled time..."
# Get current time in milliseconds
CURRENT_TIME_SEC=$(get_current_time)
CURRENT_TIME_MS=$((CURRENT_TIME_SEC * 1000))
WAIT_MS=$((ALARM_TRIGGER_MS - CURRENT_TIME_MS))
if [ "${WAIT_MS}" -lt 0 ]; then
print_warn "Alarm time already passed (${WAIT_MS} ms ago); skipping fire verification"
else
WAIT_SEC=$((WAIT_MS / 1000))
# Clamp upper bound to prevent accidentally waiting 30+ minutes
if [ "${WAIT_SEC}" -gt 600 ]; then
print_warn "Alarm is >10 minutes away (${WAIT_SEC}s); skipping fire verification"
print_info "To test fire verification, schedule alarm closer to current time"
print_info "Or set a shorter test alarm interval in the app"
else
print_info "Alarm scheduled for: ${ALARM_READABLE}"
print_info "Current time: $(date -d "@${CURRENT_TIME_SEC}" 2>/dev/null || echo "${CURRENT_TIME_SEC}")"
print_info "Waiting ~${WAIT_SEC} seconds for alarm to fire..."
# Clear logs before waiting
clear_logs
# Wait for alarm time (with a small buffer)
sleep ${WAIT_SEC}
# Give alarm a moment to fire and log
sleep 2
print_info "Checking logs for fired alarm..."
ALARM_FIRED=$($ADB_BIN logcat -d | grep -E "DNP-RECEIVE|DNP-NOTIFY|DNP-WORK|Alarm fired|Notification displayed" | tail -10)
if [ -n "${ALARM_FIRED}" ]; then
print_success "✅ Alarm fired! Logs:"
echo "${ALARM_FIRED}"
else
print_warn "⚠️ No alarm fire logs found"
print_info "This might mean:"
echo " - Alarm fired but logs were cleared"
echo " - Alarm receiver didn't log"
echo " - Check notification tray manually"
print_info "Recent logs:"
$ADB_BIN logcat -d | grep -E "DNP|DailyNotification" | tail -10
fi
fi
fi
elif [ "${VERIFY_FIRE}" = "true" ]; then
print_info "Skipping fire verification (alarm trigger time not captured)"
else
print_info "Skipping fire verification (VERIFY_FIRE=false, set VERIFY_FIRE=true to enable)"
fi fi
wait_for_user wait_for_user
fi
# ============================================ # ============================================
# TEST 2: Future Alarm Verification # TEST 2: Future Alarm Verification
# ============================================ # ============================================
if should_run_test "2" SELECTED_TESTS; then
print_header "TEST 2: Future Alarm Verification" print_header "TEST 2: Future Alarm Verification"
echo "Purpose: Verify future alarms are verified/rescheduled if missing." echo "Purpose: Verify future alarms are verified/rescheduled if missing."
echo "" echo ""
echo "Note: The test app doesn't have a cancel button, so we'll test" echo "Note: This test verifies that recovery correctly handles multiple alarms."
echo " verification of existing alarms instead." echo " We'll schedule a second alarm to test recovery with multiple schedules."
echo "" echo ""
wait_for_user wait_for_user
@@ -398,18 +458,21 @@ main() {
launch_app launch_app
ensure_plugin_configured ensure_plugin_configured
wait_for_ui_action "In the app UI, click 'Test Notification' to schedule another notification. wait_for_ui_action "In the app UI, click 'Test Notification' to schedule a SECOND notification.
This creates a second scheduled notification for testing verification." This creates an additional scheduled notification (you should now have 2 total).
This tests recovery behavior when multiple alarms exist in the database."
print_step "2" "Verifying alarms are scheduled..." print_step "2" "Verifying alarms are scheduled..."
sleep 2 sleep 2
check_alarm_status check_alarm_status
ALARM_COUNT=$(adb shell dumpsys alarm | grep -c "timesafari" || echo "0") ALARM_COUNT=$(count_alarms)
print_info "Found ${ALARM_COUNT} scheduled alarm(s)" print_info "Found ${ALARM_COUNT} scheduled alarm(s) (expected: 1)"
if [ "${ALARM_COUNT}" -gt "0" ]; then if [ "${ALARM_COUNT}" -eq "1" ] 2>/dev/null; then
print_success "✅ Single alarm confirmed in AlarmManager"
elif [ "${ALARM_COUNT}" -gt "1" ] 2>/dev/null; then
print_success "Alarms are scheduled in AlarmManager" print_success "Alarms are scheduled in AlarmManager"
else else
print_error "No alarms found in AlarmManager" print_error "No alarms found in AlarmManager"
@@ -432,21 +495,34 @@ main() {
echo " DNP-REACTIVATION: Cold start recovery complete: ..., verified=1 or rescheduled=1, ..." echo " DNP-REACTIVATION: Cold start recovery complete: ..., verified=1 or rescheduled=1, ..."
echo "" echo ""
RECOVERY_RESULT=$(adb logcat -d | grep "Cold start recovery complete" | tail -1) RECOVERY_RESULT=$($ADB_BIN logcat -d | grep "Cold start recovery complete" | tail -1)
# Extract counts from recovery result # Extract counts from recovery result
RESCHEDULED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "rescheduled=[0-9]+" | grep -oE "[0-9]+" || echo "0") RESCHEDULED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "rescheduled=[0-9]+" | grep -oE "[0-9]+" || echo "0")
VERIFIED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "verified=[0-9]+" | grep -oE "[0-9]+" || echo "0") VERIFIED_COUNT=$(echo "${RECOVERY_RESULT}" | grep -oE "verified=[0-9]+" | grep -oE "[0-9]+" || echo "0")
if [ "${RESCHEDULED_COUNT}" -gt "0" ]; then # Verify alarm count after recovery
print_success "TEST 2 PASSED: Missing future alarms were detected and rescheduled (rescheduled=${RESCHEDULED_COUNT})!" ALARM_COUNT_AFTER_RECOVERY=$(count_alarms)
elif [ "${VERIFIED_COUNT}" -gt "0" ]; then print_info "Alarm count after recovery: ${ALARM_COUNT_AFTER_RECOVERY} (expected: 1)"
print_success "TEST 2 PASSED: Future alarms verified in AlarmManager (verified=${VERIFIED_COUNT})!"
elif [ "${RESCHEDULED_COUNT}" -eq "0" ] && [ "${VERIFIED_COUNT}" -eq "0" ]; then if [ "${RESCHEDULED_COUNT}" -gt "0" ] 2>/dev/null; then
print_info "TEST 2: No verification/rescheduling needed" print_success "✅ TEST 2 PASSED: Missing future alarm was detected and rescheduled (rescheduled=${RESCHEDULED_COUNT})!"
print_info "This is OK if:" if [ "${ALARM_COUNT_AFTER_RECOVERY}" -eq "1" ] 2>/dev/null; then
echo " - All alarms were in the past (marked as missed)" print_success "✅ Single alarm confirmed in AlarmManager after recovery"
echo " - All future alarms were already correctly scheduled" fi
elif [ "${VERIFIED_COUNT}" -gt "0" ] 2>/dev/null; then
print_success "✅ TEST 2 PASSED: Future alarm verified in AlarmManager (verified=${VERIFIED_COUNT})!"
if [ "${ALARM_COUNT_AFTER_RECOVERY}" -eq "1" ] 2>/dev/null; then
print_success "✅ Single alarm confirmed in AlarmManager after recovery"
fi
elif [ "${RESCHEDULED_COUNT}" -eq "0" ] 2>/dev/null && [ "${VERIFIED_COUNT}" -eq "0" ] 2>/dev/null; then
print_warn "⚠️ TEST 2: No verification/rescheduling needed (both verified=0 and rescheduled=0)"
print_info "This might mean:"
echo " - Alarm was already properly scheduled and didn't need recovery"
echo " - Recovery didn't detect any issues"
if [ "${ALARM_COUNT_AFTER_RECOVERY}" -eq "1" ] 2>/dev/null; then
print_success "✅ Single alarm still present - recovery may have verified it silently"
fi
else else
print_error "TEST 2 INCONCLUSIVE: Could not find recovery result" print_error "TEST 2 INCONCLUSIVE: Could not find recovery result"
print_info "Recovery result: ${RECOVERY_RESULT}" print_info "Recovery result: ${RECOVERY_RESULT}"
@@ -456,10 +532,12 @@ main() {
check_alarm_status check_alarm_status
wait_for_user wait_for_user
fi
# ============================================ # ============================================
# TEST 3: Recovery Timeout # TEST 3: Recovery Timeout
# ============================================ # ============================================
if should_run_test "3" SELECTED_TESTS; then
print_header "TEST 3: Recovery Timeout" print_header "TEST 3: Recovery Timeout"
echo "Purpose: Verify recovery times out gracefully." echo "Purpose: Verify recovery times out gracefully."
echo "" echo ""
@@ -469,7 +547,7 @@ main() {
wait_for_user wait_for_user
print_step "1" "Checking recovery timeout implementation..." print_step "1" "Checking recovery timeout implementation..."
if grep -q "RECOVERY_TIMEOUT_SECONDS = 2L" "${PROJECT_ROOT}/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt"; then if grep -q "RECOVERY_TIMEOUT_SECONDS.*2L" "${PROJECT_ROOT}/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt"; then
print_success "Timeout is set to 2 seconds" print_success "Timeout is set to 2 seconds"
else else
print_error "Timeout not found in code" print_error "Timeout not found in code"
@@ -485,10 +563,12 @@ main() {
print_info "Full test (100+ schedules) can be done manually if needed" print_info "Full test (100+ schedules) can be done manually if needed"
wait_for_user wait_for_user
fi
# ============================================ # ============================================
# TEST 4: Invalid Data Handling # TEST 4: Invalid Data Handling
# ============================================ # ============================================
if should_run_test "4" SELECTED_TESTS; then
print_header "TEST 4: Invalid Data Handling" print_header "TEST 4: Invalid Data Handling"
echo "Purpose: Verify invalid data doesn't crash recovery." echo "Purpose: Verify invalid data doesn't crash recovery."
echo "" echo ""
@@ -497,7 +577,7 @@ main() {
wait_for_user wait_for_user
print_step "1" "Checking if app is debuggable..." print_step "1" "Checking if app is debuggable..."
if adb shell dumpsys package "${PACKAGE}" | grep -q "debuggable=true"; then if $ADB_BIN shell dumpsys package "${APP_ID}" | grep -q "debuggable=true"; then
print_success "App is debuggable - can access database" print_success "App is debuggable - can access database"
print_info "Invalid data handling is tested automatically during recovery." print_info "Invalid data handling is tested automatically during recovery."
@@ -507,7 +587,7 @@ main() {
echo " - Database errors (logged, non-fatal)" echo " - Database errors (logged, non-fatal)"
echo "" echo ""
print_info "To manually test invalid data:" print_info "To manually test invalid data:"
echo " 1. Use: adb shell run-as ${PACKAGE} sqlite3 databases/daily_notification_plugin.db" echo " 1. Use: $ADB_BIN shell run-as ${APP_ID} sqlite3 databases/daily_notification_plugin.db"
echo " 2. Insert invalid notification: INSERT INTO notification_content (id, ...) VALUES ('', ...);" echo " 2. Insert invalid notification: INSERT INTO notification_content (id, ...) VALUES ('', ...);"
echo " 3. Launch app and check logs for 'Skipping invalid notification'" echo " 3. Launch app and check logs for 'Skipping invalid notification'"
else else
@@ -518,6 +598,7 @@ main() {
fi fi
wait_for_user wait_for_user
fi
# ============================================ # ============================================
# Summary # Summary
@@ -543,7 +624,7 @@ main() {
print_info "All recovery logs:" print_info "All recovery logs:"
echo "" echo ""
adb logcat -d | grep "DNP-REACTIVATION" | tail -20 $ADB_BIN logcat -d | grep "$REACTIVATION_TAG" | tail -20
echo "" echo ""
print_success "Phase 1 testing script complete!" print_success "Phase 1 testing script complete!"

View File

@@ -6,219 +6,19 @@ set -euo pipefail
# Phase 2 Testing Script Force Stop Recovery # Phase 2 Testing Script Force Stop Recovery
# ======================================== # ========================================
# --- Config ------------------------------------------------------------------- # Source shared library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_ID="com.timesafari.dailynotification" source "${SCRIPT_DIR}/alarm-test-lib.sh"
APK_PATH="./app/build/outputs/apk/debug/app-debug.apk"
ADB_BIN="${ADB_BIN:-adb}"
# Phase 2 specific configuration
# Log tags / patterns (matched to actual ReactivationManager logs) # Log tags / patterns (matched to actual ReactivationManager logs)
REACTIVATION_TAG="DNP-REACTIVATION"
SCENARIO_KEY="Detected scenario: "
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP" FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
COLD_START_SCENARIO_VALUE="COLD_START" COLD_START_SCENARIO_VALUE="COLD_START"
NONE_SCENARIO_VALUE="NONE" NONE_SCENARIO_VALUE="NONE"
BOOT_SCENARIO_VALUE="BOOT" BOOT_SCENARIO_VALUE="BOOT"
# ------------------------------------------------------------------------------ # Allow selecting specific tests on the command line (e.g. ./test-phase2.sh 2 3)
# Helpers SELECTED_TESTS=()
# ------------------------------------------------------------------------------
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 # TEST 1 Force Stop with Cleared Alarms
@@ -276,17 +76,15 @@ test1_force_stop_cleared_alarms() {
logs="$(get_recovery_logs)" logs="$(get_recovery_logs)"
echo "$logs" echo "$logs"
local missed rescheduled verified errors scenario local scenario rescheduled verified errors
missed="$(extract_field_from_logs "$logs" "missed")" scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")" rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
verified="$(extract_field_from_logs "$logs" "verified")" verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")" errors="$(extract_field_from_logs "$logs" "errors")"
scenario="$(extract_scenario_from_logs "$logs")"
echo echo
info "Parsed recovery summary:" info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}" echo " scenario = ${scenario:-<none>}"
echo " missed = ${missed}"
echo " rescheduled= ${rescheduled}" echo " rescheduled= ${rescheduled}"
echo " verified = ${verified}" echo " verified = ${verified}"
echo " errors = ${errors}" echo " errors = ${errors}"
@@ -321,16 +119,13 @@ test1_force_stop_cleared_alarms() {
test2_force_stop_intact_alarms() { test2_force_stop_intact_alarms() {
section "TEST 2: Force Stop / Process Stop Alarms Intact" section "TEST 2: Force Stop / Process Stop Alarms Intact"
echo "Purpose: Ensure we do NOT run heavy force-stop recovery when alarms are intact." echo "Purpose: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
pause pause
substep "Step 1: Launch app & ensure plugin configured" substep "Step 1: Launch app & schedule notifications"
launch_app launch_app
ui_prompt "In the app UI, ensure plugin is configured and schedule at least one future notification.\n\nPress Enter when done."
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" substep "Step 2: Verify alarms are scheduled"
show_alarms show_alarms
@@ -362,7 +157,7 @@ test2_force_stop_intact_alarms() {
pause pause
substep "Step 5: Launch app (triggers recovery) and capture logs" substep "Step 5: Relaunch app and check recovery logs"
clear_logs clear_logs
launch_app launch_app
sleep 5 sleep 5
@@ -372,18 +167,18 @@ test2_force_stop_intact_alarms() {
logs="$(get_recovery_logs)" logs="$(get_recovery_logs)"
echo "$logs" echo "$logs"
local missed rescheduled verified errors scenario local scenario rescheduled missed verified errors
missed="$(extract_field_from_logs "$logs" "missed")" scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")" rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
missed="$(extract_field_from_logs "$logs" "missed")"
verified="$(extract_field_from_logs "$logs" "verified")" verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")" errors="$(extract_field_from_logs "$logs" "errors")"
scenario="$(extract_scenario_from_logs "$logs")"
echo echo
info "Parsed recovery summary:" info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}" echo " scenario = ${scenario:-<none>}"
echo " missed = ${missed}"
echo " rescheduled= ${rescheduled}" echo " rescheduled= ${rescheduled}"
echo " missed = ${missed}"
echo " verified = ${verified}" echo " verified = ${verified}"
echo " errors = ${errors}" echo " errors = ${errors}"
echo echo
@@ -392,12 +187,11 @@ test2_force_stop_intact_alarms() {
error "Recovery reported errors>0 (errors=$errors)" error "Recovery reported errors>0 (errors=$errors)"
fi fi
if [[ "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then if [[ "$after" -gt 0 && "$rescheduled" -eq 0 && "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" ]]; then
ok "TEST 2 PASSED: No heavy force-stop recovery when alarms intact (scenario=$scenario, rescheduled=$rescheduled)." ok "TEST 2 PASSED: Alarms remained intact, and FORCE_STOP scenario did not run (scenario=$scenario, rescheduled=0)."
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" ]]; then
warn "TEST 2: scenario=FORCE_STOP detected but alarms were intact. Check scenario detection logic."
else else
info "TEST 2: Some rescheduling occurred (rescheduled=$rescheduled). This might be OK if alarms were actually missing." 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 fi
} }
@@ -432,43 +226,29 @@ test3_first_launch_no_schedules() {
pause pause
substep "Step 3: Launch app WITHOUT scheduling anything" substep "Step 3: Launch app for the first time"
launch_app launch_app
sleep 5 sleep 5
info "Collecting recovery logs..." substep "Step 4: Collect logs and ensure no force-stop recovery ran"
local logs local logs
logs="$($ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true)" logs="$(get_recovery_logs)"
echo "$logs" echo "$logs"
local scenario rescheduled missed local scenario rescheduled
scenario="$(extract_scenario_from_logs "$logs")" scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")" rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
missed="$(extract_field_from_logs "$logs" "missed")"
echo echo
info "Parsed recovery summary:" info "Parsed summary:"
echo " scenario = ${scenario:-<none>}" echo " scenario = ${scenario:-<none>}"
echo " rescheduled= ${rescheduled}" echo " rescheduled= ${rescheduled}"
echo " missed = ${missed}"
echo 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 if [[ -z "$logs" ]]; then
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)." ok "TEST 3 PASSED: No force-stop recovery logs on first launch."
return elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
fi ok "TEST 3 PASSED: NONE scenario logged with rescheduled=0 on first launch."
# 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 elif [[ "$rescheduled" -gt 0 ]]; then
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring." warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring."
else else
@@ -481,12 +261,26 @@ 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...]"
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 "========================================" echo "========================================"
echo "Phase 2 Testing Script Force Stop Recovery" echo "Phase 2 Testing Script Force Stop Recovery"
echo "========================================" echo "========================================"
echo echo
echo "This script will guide you through all Phase 2 tests." echo "This script will guide you through Phase 2 tests."
echo "You'll be prompted when UI interaction is needed." echo "You'll be prompted when UI interaction is needed."
echo echo
@@ -496,13 +290,19 @@ main() {
build_app build_app
install_app install_app
if should_run_test "1" SELECTED_TESTS; then
test1_force_stop_cleared_alarms test1_force_stop_cleared_alarms
pause pause
fi
if should_run_test "2" SELECTED_TESTS; then
test2_force_stop_intact_alarms test2_force_stop_intact_alarms
pause pause
fi
if should_run_test "3" SELECTED_TESTS; then
test3_first_launch_no_schedules test3_first_launch_no_schedules
fi
section "Testing Complete" section "Testing Complete"
@@ -512,10 +312,10 @@ main() {
echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0" echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0"
echo echo
echo "TEST 2: Force Stop / Process Stop Alarms Intact" 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 " - Verify FORCE_STOP scenario is not incorrectly triggered when alarms are still present"
echo echo
echo "TEST 3: First Launch / No Schedules" 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 " - Confirm that no force-stop recovery runs, or that NONE/FIRST_LAUNCH scenario is logged with rescheduled=0"
echo echo
ok "Phase 2 testing script complete!" ok "Phase 2 testing script complete!"

View File

@@ -6,202 +6,19 @@ set -euo pipefail
# Phase 3 Testing Script Boot Recovery # Phase 3 Testing Script Boot Recovery
# ======================================== # ========================================
# --- Config ------------------------------------------------------------------- # Source shared library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_ID="com.timesafari.dailynotification" source "${SCRIPT_DIR}/alarm-test-lib.sh"
APK_PATH="./app/build/outputs/apk/debug/app-debug.apk"
ADB_BIN="${ADB_BIN:-adb}"
# Phase 3 specific configuration
# Log tags / patterns (matched to actual ReactivationManager logs) # Log tags / patterns (matched to actual ReactivationManager logs)
REACTIVATION_TAG="DNP-REACTIVATION"
SCENARIO_KEY="Detected scenario: "
BOOT_SCENARIO_VALUE="BOOT" BOOT_SCENARIO_VALUE="BOOT"
NONE_SCENARIO_VALUE="NONE" NONE_SCENARIO_VALUE="NONE"
# ------------------------------------------------------------------------------ # Allow selecting specific tests on the command line (e.g. ./test-phase3.sh 1 3)
# Helpers SELECTED_TESTS=()
# ------------------------------------------------------------------------------
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
}
# Phase 3 specific: override extract_scenario_from_logs to handle boot recovery
extract_scenario_from_logs() { extract_scenario_from_logs() {
local logs="$1" local logs="$1"
local scen local scen
@@ -209,6 +26,7 @@ extract_scenario_from_logs() {
if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
echo "$BOOT_SCENARIO_VALUE" echo "$BOOT_SCENARIO_VALUE"
else else
# Use shared library function as fallback
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)" scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
echo "$scen" echo "$scen"
fi fi
@@ -407,7 +225,7 @@ test3_boot_no_schedules() {
info "Collecting recovery logs from boot..." info "Collecting recovery logs from boot..."
sleep 2 sleep 2
local logs local logs
logs="$($ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true)" logs="$(get_recovery_logs)"
echo "$logs" echo "$logs"
local scenario rescheduled missed local scenario rescheduled missed
@@ -519,12 +337,27 @@ test4_silent_boot_recovery() {
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
main() { main() {
# Allow selecting specific tests: e.g. `./test-phase3.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, 4) 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"
echo " $0 4 # run only TEST 4 (silent boot recovery)"
return 0
fi
SELECTED_TESTS=("$@")
echo echo
echo "========================================" echo "========================================"
echo "Phase 3 Testing Script Boot Recovery" echo "Phase 3 Testing Script Boot Recovery"
echo "========================================" echo "========================================"
echo echo
echo "This script will guide you through all Phase 3 tests." echo "This script will guide you through Phase 3 tests."
echo "You'll be prompted when UI interaction is needed." echo "You'll be prompted when UI interaction is needed."
echo echo
echo "⚠️ WARNING: This script will reboot the emulator multiple times." echo "⚠️ WARNING: This script will reboot the emulator multiple times."
@@ -537,16 +370,24 @@ main() {
build_app build_app
install_app install_app
if should_run_test "1" SELECTED_TESTS; then
test1_boot_future_alarms test1_boot_future_alarms
pause pause
fi
if should_run_test "2" SELECTED_TESTS; then
test2_boot_past_alarms test2_boot_past_alarms
pause pause
fi
if should_run_test "3" SELECTED_TESTS; then
test3_boot_no_schedules test3_boot_no_schedules
pause pause
fi
if should_run_test "4" SELECTED_TESTS; then
test4_silent_boot_recovery test4_silent_boot_recovery
fi
section "Testing Complete" section "Testing Complete"
@@ -575,4 +416,3 @@ main() {
} }
main "$@" main "$@"