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:
364
test-apps/android-test-app/alarm-test-lib.sh
Normal file
364
test-apps/android-test-app/alarm-test-lib.sh
Normal 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
|
||||
}
|
||||
|
||||
@@ -5,155 +5,17 @@
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
# Source shared library
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/alarm-test-lib.sh"
|
||||
|
||||
# Configuration
|
||||
PACKAGE="com.timesafari.dailynotification"
|
||||
ACTIVITY="${PACKAGE}/.MainActivity"
|
||||
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Phase 1 specific configuration
|
||||
VERIFY_FIRE=${VERIFY_FIRE:-false} # Set VERIFY_FIRE=true to enable alarm fire verification
|
||||
APP_DIR="${SCRIPT_DIR}"
|
||||
PROJECT_ROOT="$(cd "${APP_DIR}/../.." && pwd)"
|
||||
|
||||
# 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}"
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
# Allow selecting specific tests on the command line (e.g. ./test-phase1.sh 1 2)
|
||||
SELECTED_TESTS=()
|
||||
|
||||
check_plugin_configured() {
|
||||
print_info "Checking if plugin is already configured..."
|
||||
@@ -162,14 +24,14 @@ check_plugin_configured() {
|
||||
sleep 3
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
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
|
||||
print_success "Plugin appears to be configured (database or preferences exist)"
|
||||
@@ -191,6 +53,64 @@ check_plugin_configured() {
|
||||
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() {
|
||||
if check_plugin_configured; then
|
||||
# Plugin might be configured, but let user verify
|
||||
@@ -199,11 +119,14 @@ ensure_plugin_configured() {
|
||||
If you see:
|
||||
- ⚙️ Plugin Settings: ✅ Configured
|
||||
- 🔌 Native Fetcher: ✅ Configured
|
||||
- 🔔 Notifications: ✅ Granted (or similar)
|
||||
|
||||
Then the plugin is already configured - just press Enter to continue.
|
||||
|
||||
If either shows ❌ or 'Not configured', click 'Configure Plugin' button first,
|
||||
wait for both to show ✅, then press Enter."
|
||||
If any show ❌ or 'Not configured':
|
||||
- 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
|
||||
sleep 2
|
||||
@@ -212,6 +135,10 @@ ensure_plugin_configured() {
|
||||
else
|
||||
# Plugin definitely 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 the status to update:
|
||||
@@ -226,48 +153,34 @@ ensure_plugin_configured() {
|
||||
fi
|
||||
}
|
||||
|
||||
kill_app() {
|
||||
print_info "Killing app process..."
|
||||
adb shell am kill "${PACKAGE}"
|
||||
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 ""
|
||||
}
|
||||
# kill_app is now provided by the library, but Phase 1 uses it with PACKAGE variable
|
||||
# The library version uses APP_ID, which is set to PACKAGE, so it should work
|
||||
# But we keep this as a compatibility wrapper if needed
|
||||
|
||||
# Phase 1 specific helper: get_current_time (used for fire verification)
|
||||
get_current_time() {
|
||||
adb shell date +%s
|
||||
$ADB_BIN shell date +%s
|
||||
}
|
||||
|
||||
# Main test execution
|
||||
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"
|
||||
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 ""
|
||||
wait_for_user
|
||||
@@ -285,112 +198,259 @@ main() {
|
||||
clear_logs
|
||||
|
||||
# ============================================
|
||||
# TEST 1: Cold Start Missed Detection
|
||||
# TEST 1: Force-Stop Recovery - Database Restoration
|
||||
# ============================================
|
||||
print_header "TEST 1: Cold Start Missed Detection"
|
||||
echo "Purpose: Verify missed notifications are detected and marked."
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
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 ""
|
||||
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
|
||||
ensure_plugin_configured
|
||||
|
||||
wait_for_ui_action "In the app UI, click the 'Test Notification' button.
|
||||
|
||||
This will schedule a notification for 4 minutes in the future.
|
||||
(The test app automatically schedules for 4 minutes from now)"
|
||||
This will schedule ONE notification for 4 minutes in the future.
|
||||
(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
|
||||
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..."
|
||||
adb logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5
|
||||
|
||||
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."
|
||||
$ADB_BIN logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5
|
||||
echo ""
|
||||
|
||||
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..."
|
||||
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
|
||||
# Verify app is stopped (force_stop_app already handles this)
|
||||
|
||||
if [ ${WAIT_SECONDS} -gt 0 ] && [ ${WAIT_SECONDS} -lt 600 ]; then
|
||||
ALARM_READABLE=$(date -d "@${ALARM_TIME_SEC}" 2>/dev/null || echo "${ALARM_TIME_SEC}")
|
||||
CURRENT_READABLE=$(date -d "@${CURRENT_TIME}" 2>/dev/null || echo "${CURRENT_TIME}")
|
||||
print_info "Alarm scheduled for: ${ALARM_READABLE}"
|
||||
print_info "Current time: ${CURRENT_READABLE}"
|
||||
print_wait "Waiting ${WAIT_SECONDS} seconds for alarm time to pass..."
|
||||
sleep ${WAIT_SECONDS}
|
||||
elif [ ${WAIT_SECONDS} -le 0 ]; then
|
||||
print_info "Alarm time has already passed"
|
||||
print_wait "Waiting 2 minutes to ensure we're well past alarm time..."
|
||||
sleep 120
|
||||
else
|
||||
print_wait "Alarm is more than 10 minutes away. Waiting 5 minutes (you can adjust this)..."
|
||||
sleep 300
|
||||
fi
|
||||
else
|
||||
print_wait "Could not parse alarm time. Waiting 5 minutes..."
|
||||
sleep 300
|
||||
fi
|
||||
# ============================================
|
||||
# Step 4: Verify alarms are MISSING (cleared by OS)
|
||||
# ============================================
|
||||
print_step "5" "Verifying alarms are MISSING from AlarmManager (AFTER force-stop)..."
|
||||
sleep 1
|
||||
ALARM_COUNT_AFTER=$(count_alarms)
|
||||
print_info "Alarm count in AlarmManager after force-stop: ${ALARM_COUNT_AFTER}"
|
||||
|
||||
if [ "${ALARM_COUNT_AFTER}" -eq "0" ] 2>/dev/null; then
|
||||
print_success "✅ Alarms cleared by force-stop (count: ${ALARM_COUNT_AFTER})"
|
||||
print_info "This confirms: Force-stop cleared alarms from AlarmManager"
|
||||
else
|
||||
print_wait "Could not find alarm in AlarmManager. Waiting 5 minutes..."
|
||||
sleep 300
|
||||
print_warn "⚠️ Alarms still present after force-stop (count: ${ALARM_COUNT_AFTER})"
|
||||
print_info "Some devices/OS versions may not clear alarms on force-stop"
|
||||
print_info "Continuing test anyway - recovery should still work"
|
||||
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
|
||||
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
|
||||
|
||||
print_info "Expected log output:"
|
||||
echo " DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)"
|
||||
echo " DNP-REACTIVATION: Cold start recovery: checking for missed notifications"
|
||||
echo " DNP-REACTIVATION: Marked missed notification: <id>"
|
||||
echo " DNP-REACTIVATION: Cold start recovery complete: missed=1, ..."
|
||||
echo " DNP-REACTIVATION: Starting app launch recovery"
|
||||
echo " DNP-REACTIVATION: Rescheduled alarm: <id> for <time>"
|
||||
echo " DNP-REACTIVATION: Cold start recovery complete: ... rescheduled>=1, ..."
|
||||
echo ""
|
||||
|
||||
RECOVERY_RESULT=$(adb logcat -d | grep "Cold start recovery complete" | tail -1)
|
||||
if echo "${RECOVERY_RESULT}" | grep -q "missed=[1-9]"; then
|
||||
print_success "TEST 1 PASSED: Missed notification detected!"
|
||||
elif echo "${RECOVERY_RESULT}" | grep -q "missed=0"; then
|
||||
print_error "TEST 1 FAILED: No missed notifications detected (missed=0)"
|
||||
print_info "This might mean:"
|
||||
echo " - Notification was already delivered"
|
||||
echo " - NotificationContentEntity was not created"
|
||||
echo " - Alarm fired before app was killed"
|
||||
# Check recovery logs for rescheduling and recovery source
|
||||
RECOVERY_RESULT=$($ADB_BIN logcat -d | grep "Cold start recovery complete\|Boot recovery complete" | tail -1)
|
||||
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")
|
||||
|
||||
# Check for explicit recovery source indication (if logged)
|
||||
RECOVERY_SOURCE=$($ADB_BIN logcat -d | grep -E "recovery source|from database|DATABASE" | tail -1 || echo "")
|
||||
|
||||
# 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
|
||||
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 "TEST 1 INCONCLUSIVE: Could not find recovery result"
|
||||
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
|
||||
|
||||
wait_for_user
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# TEST 2: Future Alarm Verification
|
||||
# ============================================
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
print_header "TEST 2: Future Alarm Verification"
|
||||
echo "Purpose: Verify future alarms are verified/rescheduled if missing."
|
||||
echo ""
|
||||
echo "Note: The test app doesn't have a cancel button, so we'll test"
|
||||
echo " verification of existing alarms instead."
|
||||
echo "Note: This test verifies that recovery correctly handles multiple alarms."
|
||||
echo " We'll schedule a second alarm to test recovery with multiple schedules."
|
||||
echo ""
|
||||
wait_for_user
|
||||
|
||||
@@ -398,18 +458,21 @@ main() {
|
||||
launch_app
|
||||
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..."
|
||||
sleep 2
|
||||
check_alarm_status
|
||||
|
||||
ALARM_COUNT=$(adb shell dumpsys alarm | grep -c "timesafari" || echo "0")
|
||||
print_info "Found ${ALARM_COUNT} scheduled alarm(s)"
|
||||
ALARM_COUNT=$(count_alarms)
|
||||
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"
|
||||
else
|
||||
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 ""
|
||||
|
||||
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
|
||||
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")
|
||||
|
||||
if [ "${RESCHEDULED_COUNT}" -gt "0" ]; then
|
||||
print_success "TEST 2 PASSED: Missing future alarms were detected and rescheduled (rescheduled=${RESCHEDULED_COUNT})!"
|
||||
elif [ "${VERIFIED_COUNT}" -gt "0" ]; then
|
||||
print_success "TEST 2 PASSED: Future alarms verified in AlarmManager (verified=${VERIFIED_COUNT})!"
|
||||
elif [ "${RESCHEDULED_COUNT}" -eq "0" ] && [ "${VERIFIED_COUNT}" -eq "0" ]; then
|
||||
print_info "TEST 2: No verification/rescheduling needed"
|
||||
print_info "This is OK if:"
|
||||
echo " - All alarms were in the past (marked as missed)"
|
||||
echo " - All future alarms were already correctly scheduled"
|
||||
# Verify alarm count after recovery
|
||||
ALARM_COUNT_AFTER_RECOVERY=$(count_alarms)
|
||||
print_info "Alarm count after recovery: ${ALARM_COUNT_AFTER_RECOVERY} (expected: 1)"
|
||||
|
||||
if [ "${RESCHEDULED_COUNT}" -gt "0" ] 2>/dev/null; then
|
||||
print_success "✅ TEST 2 PASSED: Missing future alarm was detected and rescheduled (rescheduled=${RESCHEDULED_COUNT})!"
|
||||
if [ "${ALARM_COUNT_AFTER_RECOVERY}" -eq "1" ] 2>/dev/null; then
|
||||
print_success "✅ Single alarm confirmed in AlarmManager after recovery"
|
||||
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
|
||||
print_error "TEST 2 INCONCLUSIVE: Could not find recovery result"
|
||||
print_info "Recovery result: ${RECOVERY_RESULT}"
|
||||
@@ -456,10 +532,12 @@ main() {
|
||||
check_alarm_status
|
||||
|
||||
wait_for_user
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# TEST 3: Recovery Timeout
|
||||
# ============================================
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
print_header "TEST 3: Recovery Timeout"
|
||||
echo "Purpose: Verify recovery times out gracefully."
|
||||
echo ""
|
||||
@@ -469,7 +547,7 @@ main() {
|
||||
wait_for_user
|
||||
|
||||
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"
|
||||
else
|
||||
print_error "Timeout not found in code"
|
||||
@@ -485,10 +563,12 @@ main() {
|
||||
print_info "Full test (100+ schedules) can be done manually if needed"
|
||||
|
||||
wait_for_user
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# TEST 4: Invalid Data Handling
|
||||
# ============================================
|
||||
if should_run_test "4" SELECTED_TESTS; then
|
||||
print_header "TEST 4: Invalid Data Handling"
|
||||
echo "Purpose: Verify invalid data doesn't crash recovery."
|
||||
echo ""
|
||||
@@ -497,7 +577,7 @@ main() {
|
||||
wait_for_user
|
||||
|
||||
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_info "Invalid data handling is tested automatically during recovery."
|
||||
@@ -507,7 +587,7 @@ main() {
|
||||
echo " - Database errors (logged, non-fatal)"
|
||||
echo ""
|
||||
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 " 3. Launch app and check logs for 'Skipping invalid notification'"
|
||||
else
|
||||
@@ -518,6 +598,7 @@ main() {
|
||||
fi
|
||||
|
||||
wait_for_user
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Summary
|
||||
@@ -543,7 +624,7 @@ main() {
|
||||
|
||||
print_info "All recovery logs:"
|
||||
echo ""
|
||||
adb logcat -d | grep "DNP-REACTIVATION" | tail -20
|
||||
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" | tail -20
|
||||
echo ""
|
||||
|
||||
print_success "Phase 1 testing script complete!"
|
||||
|
||||
@@ -6,219 +6,19 @@ set -euo pipefail
|
||||
# Phase 2 Testing Script – Force Stop Recovery
|
||||
# ========================================
|
||||
|
||||
# --- Config -------------------------------------------------------------------
|
||||
|
||||
APP_ID="com.timesafari.dailynotification"
|
||||
APK_PATH="./app/build/outputs/apk/debug/app-debug.apk"
|
||||
ADB_BIN="${ADB_BIN:-adb}"
|
||||
# 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)
|
||||
REACTIVATION_TAG="DNP-REACTIVATION"
|
||||
SCENARIO_KEY="Detected scenario: "
|
||||
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
|
||||
COLD_START_SCENARIO_VALUE="COLD_START"
|
||||
NONE_SCENARIO_VALUE="NONE"
|
||||
BOOT_SCENARIO_VALUE="BOOT"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "$1"
|
||||
echo "========================================"
|
||||
echo
|
||||
}
|
||||
|
||||
substep() {
|
||||
echo "→ $1"
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "ℹ️ $1"
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo -e "✅ $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "⚠️ $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "❌ $1"
|
||||
}
|
||||
|
||||
pause() {
|
||||
echo
|
||||
read -rp "Press Enter when ready to continue..."
|
||||
echo
|
||||
}
|
||||
|
||||
ui_prompt() {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "👆 UI ACTION REQUIRED"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo -e "$1"
|
||||
echo
|
||||
read -rp "Press Enter after completing the action above..."
|
||||
echo
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
# Wrapper so output is visible but failures stop the script
|
||||
# Usage: run_cmd "description" cmd...
|
||||
local desc="$1"; shift
|
||||
substep "$desc"
|
||||
echo " $*"
|
||||
if "$@"; then
|
||||
ok "$desc"
|
||||
else
|
||||
error "$desc failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_adb_device() {
|
||||
section "Pre-Flight Checks"
|
||||
|
||||
if ! $ADB_BIN devices | awk 'NR>1 && $2=="device"{found=1} END{exit !found}'; then
|
||||
error "No emulator/device in 'device' state. Start your emulator first."
|
||||
exit 1
|
||||
fi
|
||||
ok "ADB device connected"
|
||||
|
||||
info "Checking emulator status..."
|
||||
if ! $ADB_BIN shell getprop sys.boot_completed | grep -q "1"; then
|
||||
info "Waiting for emulator to boot..."
|
||||
$ADB_BIN wait-for-device
|
||||
while [ "$($ADB_BIN shell getprop sys.boot_completed)" != "1" ]; do
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
ok "Emulator is ready"
|
||||
}
|
||||
|
||||
build_app() {
|
||||
section "Building Test App"
|
||||
|
||||
substep "Step 1: Building debug APK..."
|
||||
if ./gradlew :app:assembleDebug; then
|
||||
ok "Build successful"
|
||||
else
|
||||
error "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$APK_PATH" ]]; then
|
||||
ok "APK ready: $APK_PATH"
|
||||
else
|
||||
error "APK not found at $APK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_app() {
|
||||
section "Installing App"
|
||||
|
||||
substep "Step 1: Uninstalling existing app (if present)..."
|
||||
set +e
|
||||
uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)"
|
||||
uninstall_status=$?
|
||||
set -e
|
||||
|
||||
if [[ $uninstall_status -ne 0 ]]; then
|
||||
if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then
|
||||
info "No existing app to uninstall (continuing)"
|
||||
else
|
||||
warn "Uninstall returned non-zero status: $uninstall_output (continuing anyway)"
|
||||
fi
|
||||
else
|
||||
ok "Previous app uninstall succeeded"
|
||||
fi
|
||||
|
||||
substep "Step 2: Installing new APK..."
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed successfully"
|
||||
else
|
||||
error "App installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
substep "Step 3: Verifying installation..."
|
||||
if $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then
|
||||
ok "App verified in package list"
|
||||
else
|
||||
error "App not found in package list"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
launch_app() {
|
||||
info "Launching app..."
|
||||
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" >/dev/null 2>&1
|
||||
sleep 3 # Give app time to load
|
||||
ok "App launched"
|
||||
}
|
||||
|
||||
clear_logs() {
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
show_alarms() {
|
||||
info "Checking AlarmManager status..."
|
||||
echo
|
||||
$ADB_BIN shell dumpsys alarm | grep -A3 "$APP_ID" || true
|
||||
echo
|
||||
}
|
||||
|
||||
count_alarms() {
|
||||
# Returns count of alarms for our app
|
||||
$ADB_BIN shell dumpsys alarm | grep -c "$APP_ID" || echo "0"
|
||||
}
|
||||
|
||||
force_stop_app() {
|
||||
info "Force-stopping app..."
|
||||
$ADB_BIN shell am force-stop "$APP_ID"
|
||||
sleep 2
|
||||
ok "Force stop requested"
|
||||
}
|
||||
|
||||
get_recovery_logs() {
|
||||
# Collect recent reactivation logs
|
||||
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true
|
||||
}
|
||||
|
||||
extract_field_from_logs() {
|
||||
# Usage: extract_field_from_logs "<logs>" "<field_name>"
|
||||
local logs="$1"
|
||||
local field="$2"
|
||||
# Looks for patterns like "field=NUMBER" and returns NUMBER (or 0)
|
||||
local value
|
||||
value="$(grep -oE "${field}=[0-9]+" <<<"$logs" | tail -n1 | sed "s/${field}=//" || true)"
|
||||
if [[ -z "$value" ]]; then
|
||||
echo "0"
|
||||
else
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_scenario_from_logs() {
|
||||
local logs="$1"
|
||||
local scen
|
||||
# Looks for "Detected scenario: FORCE_STOP" format
|
||||
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
|
||||
echo "$scen"
|
||||
}
|
||||
# Allow selecting specific tests on the command line (e.g. ./test-phase2.sh 2 3)
|
||||
SELECTED_TESTS=()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 1 – Force Stop with Cleared Alarms
|
||||
@@ -276,17 +76,15 @@ test1_force_stop_cleared_alarms() {
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
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")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
@@ -321,16 +119,13 @@ test1_force_stop_cleared_alarms() {
|
||||
test2_force_stop_intact_alarms() {
|
||||
section "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
|
||||
echo "Purpose: Ensure we do NOT run heavy force-stop recovery when alarms are intact."
|
||||
echo "Purpose: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & ensure plugin configured"
|
||||
substep "Step 1: Launch app & schedule notifications"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
|
||||
|
||||
ui_prompt "Click 'Test Notification' to schedule another notification a few minutes in the future."
|
||||
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
|
||||
@@ -362,7 +157,7 @@ test2_force_stop_intact_alarms() {
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 5: Launch app (triggers recovery) and capture logs"
|
||||
substep "Step 5: Relaunch app and check recovery logs"
|
||||
clear_logs
|
||||
launch_app
|
||||
sleep 5
|
||||
@@ -372,18 +167,18 @@ test2_force_stop_intact_alarms() {
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
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")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " missed = ${missed}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
@@ -392,12 +187,11 @@ test2_force_stop_intact_alarms() {
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 2 PASSED: No heavy force-stop recovery when alarms intact (scenario=$scenario, rescheduled=$rescheduled)."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||||
warn "TEST 2: scenario=FORCE_STOP detected but alarms were intact. Check scenario detection logic."
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@@ -432,43 +226,29 @@ test3_first_launch_no_schedules() {
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Launch app WITHOUT scheduling anything"
|
||||
substep "Step 3: Launch app for the first time"
|
||||
launch_app
|
||||
sleep 5
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
substep "Step 4: Collect logs and ensure no force-stop recovery ran"
|
||||
local logs
|
||||
logs="$($ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true)"
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled missed
|
||||
local scenario rescheduled
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
info "Parsed summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " missed = ${missed}"
|
||||
echo
|
||||
|
||||
# Check for explicit "No schedules" or "skipping recovery" messages
|
||||
if echo "$logs" | grep -qiE "No schedules|skipping recovery|first launch"; then
|
||||
ok "TEST 3 PASSED: No recovery logs or explicit skip message when there are no schedules (safe behavior)."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "$logs" ]]; then
|
||||
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)."
|
||||
return
|
||||
fi
|
||||
|
||||
# If you explicitly log a NONE scenario, check for that:
|
||||
if [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 3 PASSED: NONE scenario detected with no rescheduling."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||||
error "TEST 3 FAILED: FORCE_STOP scenario detected on first launch / empty DB. This should not happen."
|
||||
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
|
||||
@@ -481,12 +261,26 @@ test3_first_launch_no_schedules() {
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
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 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
|
||||
|
||||
@@ -496,13 +290,19 @@ main() {
|
||||
build_app
|
||||
install_app
|
||||
|
||||
test1_force_stop_cleared_alarms
|
||||
pause
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
test1_force_stop_cleared_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
test2_force_stop_intact_alarms
|
||||
pause
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
test2_force_stop_intact_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
test3_first_launch_no_schedules
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
test3_first_launch_no_schedules
|
||||
fi
|
||||
|
||||
section "Testing Complete"
|
||||
|
||||
@@ -512,10 +312,10 @@ main() {
|
||||
echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0"
|
||||
echo
|
||||
echo "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
echo " - Check that heavy FORCE_STOP recovery did NOT run when alarms are still present"
|
||||
echo " - Verify FORCE_STOP scenario is not incorrectly triggered when alarms are still present"
|
||||
echo
|
||||
echo "TEST 3: First Launch / No Schedules"
|
||||
echo " - Check that no force-stop recovery runs, or that NONE scenario is logged with rescheduled=0"
|
||||
echo " - 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!"
|
||||
|
||||
@@ -6,202 +6,19 @@ set -euo pipefail
|
||||
# Phase 3 Testing Script – Boot Recovery
|
||||
# ========================================
|
||||
|
||||
# --- Config -------------------------------------------------------------------
|
||||
|
||||
APP_ID="com.timesafari.dailynotification"
|
||||
APK_PATH="./app/build/outputs/apk/debug/app-debug.apk"
|
||||
ADB_BIN="${ADB_BIN:-adb}"
|
||||
# Source shared library
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/alarm-test-lib.sh"
|
||||
|
||||
# Phase 3 specific configuration
|
||||
# Log tags / patterns (matched to actual ReactivationManager logs)
|
||||
REACTIVATION_TAG="DNP-REACTIVATION"
|
||||
SCENARIO_KEY="Detected scenario: "
|
||||
BOOT_SCENARIO_VALUE="BOOT"
|
||||
NONE_SCENARIO_VALUE="NONE"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "$1"
|
||||
echo "========================================"
|
||||
echo
|
||||
}
|
||||
|
||||
substep() {
|
||||
echo "→ $1"
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "ℹ️ $1"
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo -e "✅ $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "⚠️ $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "❌ $1"
|
||||
}
|
||||
|
||||
pause() {
|
||||
echo
|
||||
read -rp "Press Enter when ready to continue..."
|
||||
echo
|
||||
}
|
||||
|
||||
ui_prompt() {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "👆 UI ACTION REQUIRED"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo -e "$1"
|
||||
echo
|
||||
read -rp "Press Enter after completing the action above..."
|
||||
echo
|
||||
}
|
||||
|
||||
require_adb_device() {
|
||||
section "Pre-Flight Checks"
|
||||
|
||||
if ! $ADB_BIN devices | awk 'NR>1 && $2=="device"{found=1} END{exit !found}'; then
|
||||
error "No emulator/device in 'device' state. Start your emulator first."
|
||||
exit 1
|
||||
fi
|
||||
ok "ADB device connected"
|
||||
|
||||
info "Checking emulator status..."
|
||||
if ! $ADB_BIN shell getprop sys.boot_completed | grep -q "1"; then
|
||||
info "Waiting for emulator to boot..."
|
||||
$ADB_BIN wait-for-device
|
||||
while [ "$($ADB_BIN shell getprop sys.boot_completed)" != "1" ]; do
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
ok "Emulator is ready"
|
||||
}
|
||||
|
||||
build_app() {
|
||||
section "Building Test App"
|
||||
|
||||
substep "Step 1: Building debug APK..."
|
||||
if ./gradlew :app:assembleDebug; then
|
||||
ok "Build successful"
|
||||
else
|
||||
error "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$APK_PATH" ]]; then
|
||||
ok "APK ready: $APK_PATH"
|
||||
else
|
||||
error "APK not found at $APK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_app() {
|
||||
section "Installing App"
|
||||
|
||||
substep "Step 1: Uninstalling existing app (if present)..."
|
||||
set +e
|
||||
uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)"
|
||||
uninstall_status=$?
|
||||
set -e
|
||||
|
||||
if [[ $uninstall_status -ne 0 ]]; then
|
||||
if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then
|
||||
info "No existing app to uninstall (continuing)"
|
||||
else
|
||||
warn "Uninstall returned non-zero status: $uninstall_output (continuing anyway)"
|
||||
fi
|
||||
else
|
||||
ok "Previous app uninstall succeeded"
|
||||
fi
|
||||
|
||||
substep "Step 2: Installing new APK..."
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed successfully"
|
||||
else
|
||||
error "App installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
substep "Step 3: Verifying installation..."
|
||||
if $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then
|
||||
ok "App verified in package list"
|
||||
else
|
||||
error "App not found in package list"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
launch_app() {
|
||||
info "Launching app..."
|
||||
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" >/dev/null 2>&1
|
||||
sleep 3 # Give app time to load
|
||||
ok "App launched"
|
||||
}
|
||||
|
||||
clear_logs() {
|
||||
info "Clearing logcat buffer..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
}
|
||||
|
||||
show_alarms() {
|
||||
info "Checking AlarmManager status..."
|
||||
echo
|
||||
$ADB_BIN shell dumpsys alarm | grep -A3 "$APP_ID" || true
|
||||
echo
|
||||
}
|
||||
|
||||
count_alarms() {
|
||||
# Returns count of alarms for our app
|
||||
$ADB_BIN shell dumpsys alarm | grep -c "$APP_ID" || echo "0"
|
||||
}
|
||||
|
||||
reboot_emulator() {
|
||||
info "Rebooting emulator..."
|
||||
$ADB_BIN reboot
|
||||
ok "Reboot initiated"
|
||||
|
||||
info "Waiting for emulator to come back online..."
|
||||
$ADB_BIN wait-for-device
|
||||
while [ "$($ADB_BIN shell getprop sys.boot_completed)" != "1" ]; do
|
||||
sleep 2
|
||||
done
|
||||
ok "Emulator boot completed"
|
||||
}
|
||||
|
||||
get_recovery_logs() {
|
||||
# Collect recent reactivation logs
|
||||
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true
|
||||
}
|
||||
|
||||
extract_field_from_logs() {
|
||||
# Usage: extract_field_from_logs "<logs>" "<field_name>"
|
||||
local logs="$1"
|
||||
local field="$2"
|
||||
# Looks for patterns like "field=NUMBER" and returns NUMBER (or 0)
|
||||
local value
|
||||
value="$(grep -oE "${field}=[0-9]+" <<<"$logs" | tail -n1 | sed "s/${field}=//" || true)"
|
||||
if [[ -z "$value" ]]; then
|
||||
echo "0"
|
||||
else
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
# Allow selecting specific tests on the command line (e.g. ./test-phase3.sh 1 3)
|
||||
SELECTED_TESTS=()
|
||||
|
||||
# Phase 3 specific: override extract_scenario_from_logs to handle boot recovery
|
||||
extract_scenario_from_logs() {
|
||||
local logs="$1"
|
||||
local scen
|
||||
@@ -209,6 +26,7 @@ extract_scenario_from_logs() {
|
||||
if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
|
||||
echo "$BOOT_SCENARIO_VALUE"
|
||||
else
|
||||
# Use shared library function as fallback
|
||||
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
|
||||
echo "$scen"
|
||||
fi
|
||||
@@ -407,7 +225,7 @@ test3_boot_no_schedules() {
|
||||
info "Collecting recovery logs from boot..."
|
||||
sleep 2
|
||||
local logs
|
||||
logs="$($ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true)"
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled missed
|
||||
@@ -519,12 +337,27 @@ test4_silent_boot_recovery() {
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
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 "Phase 3 Testing Script – Boot Recovery"
|
||||
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
|
||||
echo "⚠️ WARNING: This script will reboot the emulator multiple times."
|
||||
@@ -537,16 +370,24 @@ main() {
|
||||
build_app
|
||||
install_app
|
||||
|
||||
test1_boot_future_alarms
|
||||
pause
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
test1_boot_future_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
test2_boot_past_alarms
|
||||
pause
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
test2_boot_past_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
test3_boot_no_schedules
|
||||
pause
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
test3_boot_no_schedules
|
||||
pause
|
||||
fi
|
||||
|
||||
test4_silent_boot_recovery
|
||||
if should_run_test "4" SELECTED_TESTS; then
|
||||
test4_silent_boot_recovery
|
||||
fi
|
||||
|
||||
section "Testing Complete"
|
||||
|
||||
@@ -575,4 +416,3 @@ main() {
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user