Merge branch 'master' into ios-2
This commit is contained in:
613
test-apps/android-test-app/alarm-test-lib.sh
Normal file
613
test-apps/android-test-app/alarm-test-lib.sh
Normal file
@@ -0,0 +1,613 @@
|
||||
#!/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: }"
|
||||
|
||||
# Screenshot configuration
|
||||
: "${SCREENSHOT_ROOT:=screenshots}"
|
||||
: "${ENABLE_SCREENSHOTS:=1}"
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# Plugin-specific alarm action (must match AndroidManifest.xml)
|
||||
PLUGIN_ALARM_ACTION="com.timesafari.daily.NOTIFICATION"
|
||||
|
||||
get_plugin_alarm_count() {
|
||||
# Returns count of ONLY the plugin's NOTIFICATION alarms (not prefetch - that uses WorkManager)
|
||||
# Expected: 1 notification alarm per daily schedule
|
||||
#
|
||||
# This function counts ALARM_CLOCK wake alarms (RTC_WAKEUP) tagged as:
|
||||
# tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
#
|
||||
# Uses deduplicating parser to avoid double-counting the same alarm that appears in both:
|
||||
# - Main alarm list
|
||||
# - "Next wake from idle" section (ignored - only counts RTC_WAKEUP blocks)
|
||||
# - Alarm Stats section (ignored - only counts actual alarm blocks)
|
||||
#
|
||||
# Tracks unique Alarm handles to ensure each alarm is counted only once.
|
||||
# Checks for app package AND action string anywhere in the block (they appear on different lines).
|
||||
local count app_id action
|
||||
app_id="$APP_ID"
|
||||
action="com.timesafari.daily.NOTIFICATION"
|
||||
count="$($ADB_BIN shell dumpsys alarm 2>/dev/null | awk -v app="$app_id" -v action="$action" '
|
||||
BEGIN {
|
||||
in_block = 0
|
||||
alarmId = ""
|
||||
hasAppLine = 0
|
||||
hasActionLine = 0
|
||||
}
|
||||
# Start of a new RTC_WAKEUP alarm block
|
||||
/^[[:space:]]*RTC_WAKEUP/ {
|
||||
# Flush previous block if it was a plugin notification
|
||||
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
|
||||
seen[alarmId] = 1
|
||||
}
|
||||
in_block = 1
|
||||
alarmId = ""
|
||||
hasAppLine = 0
|
||||
hasActionLine = 0
|
||||
# Extract alarmId from "Alarm{11245c ..."
|
||||
if (match($0, /Alarm\{[0-9a-f]+/)) {
|
||||
# match is like "Alarm{11245c", extract just the hex part
|
||||
alarmId = substr($0, RSTART + 6, RLENGTH - 6)
|
||||
}
|
||||
}
|
||||
# Blank line = end of block
|
||||
/^[[:space:]]*$/ {
|
||||
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
|
||||
seen[alarmId] = 1
|
||||
}
|
||||
in_block = 0
|
||||
alarmId = ""
|
||||
hasAppLine = 0
|
||||
hasActionLine = 0
|
||||
}
|
||||
# Lines inside an alarm block
|
||||
in_block == 1 {
|
||||
if ($0 ~ app) {
|
||||
hasAppLine = 1
|
||||
}
|
||||
if ($0 ~ action) {
|
||||
hasActionLine = 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
# Flush final block if it was a plugin notification
|
||||
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
|
||||
seen[alarmId] = 1
|
||||
}
|
||||
count = 0
|
||||
for (id in seen) {
|
||||
count++
|
||||
}
|
||||
print count + 0
|
||||
}
|
||||
' 2>/dev/null || echo "0")"
|
||||
echo "$count" | tr -d '\n\r' | head -1
|
||||
}
|
||||
|
||||
# Alias for backward compatibility
|
||||
get_notification_alarm_count() {
|
||||
get_plugin_alarm_count
|
||||
}
|
||||
|
||||
get_prefetch_work_count() {
|
||||
# Returns count of prefetch WorkManager jobs (not AlarmManager alarms)
|
||||
# Note: Prefetch uses WorkManager, not AlarmManager, so it won't appear in dumpsys alarm
|
||||
# This is a placeholder - actual WorkManager job counting would require different approach
|
||||
local count
|
||||
count="$($ADB_BIN shell dumpsys jobscheduler | grep -c "prefetch" 2>/dev/null || echo "0")"
|
||||
echo "$count" | tr -d '\n\r' | head -1
|
||||
}
|
||||
|
||||
get_system_alarm_count() {
|
||||
# Returns total RTC_WAKEUP alarms on system (for debugging/context)
|
||||
local count
|
||||
count="$($ADB_BIN shell dumpsys alarm 2>/dev/null | grep -c "RTC_WAKEUP" || echo "0")"
|
||||
echo "$count" | tr -d '\n\r' | head -1
|
||||
}
|
||||
|
||||
count_alarms() {
|
||||
# Legacy function: now returns plugin-specific alarm count
|
||||
# Use get_plugin_alarm_count() for clarity, or get_system_alarm_count() for total
|
||||
get_plugin_alarm_count
|
||||
}
|
||||
|
||||
show_plugin_alarms_compact() {
|
||||
# Prints only the plugin's alarm block(s) for debugging
|
||||
# Shows complete RTC_WAKEUP alarm blocks that contain the app ID
|
||||
# This makes it visually obvious why the AWK matcher should pick up alarms
|
||||
# (app ID on header line, action on tag line within the same block)
|
||||
$ADB_BIN shell dumpsys alarm 2>/dev/null \
|
||||
| awk -v app="$APP_ID" '
|
||||
BEGIN {
|
||||
in_block = 0
|
||||
block = ""
|
||||
found_app = 0
|
||||
}
|
||||
/^[[:space:]]*RTC_WAKEUP/ {
|
||||
# Print previous block if it contained app ID
|
||||
if (in_block && found_app) {
|
||||
print block ORS
|
||||
}
|
||||
# Start new block
|
||||
in_block = 1
|
||||
block = $0 ORS
|
||||
found_app = ($0 ~ app) ? 1 : 0
|
||||
next
|
||||
}
|
||||
/^[[:space:]]*$/ {
|
||||
# End of block
|
||||
if (in_block && found_app) {
|
||||
print block ORS
|
||||
}
|
||||
in_block = 0
|
||||
block = ""
|
||||
found_app = 0
|
||||
next
|
||||
}
|
||||
{
|
||||
if (in_block) {
|
||||
block = block $0 ORS
|
||||
if ($0 ~ app) {
|
||||
found_app = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (in_block && found_app) {
|
||||
print block
|
||||
}
|
||||
}
|
||||
' \
|
||||
| sed -n '1,80p' || true
|
||||
}
|
||||
|
||||
wait_for_stable_plugin_alarm_count() {
|
||||
# Polls for plugin alarm count to stabilize (reduces race condition false negatives)
|
||||
# Usage: wait_for_stable_plugin_alarm_count [attempts] [delay_seconds]
|
||||
# Default: 5 attempts, 2 second delay (total ~10 seconds)
|
||||
# Returns: alarm count (0 if none found after all attempts)
|
||||
local attempts=${1:-5}
|
||||
local delay=${2:-2}
|
||||
local count=0
|
||||
local i
|
||||
|
||||
for i in $(seq 1 "$attempts"); do
|
||||
count="$(get_plugin_alarm_count)"
|
||||
if [ "$count" -ge 1 ] 2>/dev/null; then
|
||||
echo "$count"
|
||||
return 0
|
||||
fi
|
||||
if [ "$i" -lt "$attempts" ]; then
|
||||
sleep "$delay"
|
||||
fi
|
||||
done
|
||||
echo "$count"
|
||||
}
|
||||
|
||||
# --- Screenshot Helpers ---
|
||||
|
||||
take_screenshot() {
|
||||
# Captures a device screenshot and saves it with test name, step, and timestamp
|
||||
# Usage: take_screenshot "test_name" "step_name"
|
||||
# Example: take_screenshot "phase1_test0_daily_rollover" "before_scheduling"
|
||||
local test_name="$1"
|
||||
local step_name="$2"
|
||||
|
||||
# Do nothing if screenshots are disabled
|
||||
if [ "$ENABLE_SCREENSHOTS" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "$ADB_BIN" ]; then
|
||||
echo "⚠️ ADB_BIN is not set; cannot take screenshot." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Timestamp for uniqueness
|
||||
local ts
|
||||
ts="$(date '+%Y%m%d-%H%M%S' 2>/dev/null)" || ts="unknown"
|
||||
|
||||
# Directory: screenshots/<test_name>/
|
||||
# Use absolute path relative to script directory if SCREENSHOT_ROOT is relative
|
||||
local dir
|
||||
if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR" ]; then
|
||||
dir="${SCRIPT_DIR}/${SCREENSHOT_ROOT}/${test_name}"
|
||||
elif [ -n "${BASH_SOURCE[0]}" ]; then
|
||||
# Fallback: derive from this script's location
|
||||
local lib_dir
|
||||
lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)" || lib_dir=""
|
||||
if [ -n "$lib_dir" ]; then
|
||||
dir="${lib_dir}/${SCREENSHOT_ROOT}/${test_name}"
|
||||
else
|
||||
dir="${SCREENSHOT_ROOT}/${test_name}"
|
||||
fi
|
||||
else
|
||||
dir="${SCREENSHOT_ROOT}/${test_name}"
|
||||
fi
|
||||
mkdir -p "$dir" 2>/dev/null || {
|
||||
echo "⚠️ Failed to create screenshot directory: $dir" >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
# File name: <test>_<step>_<timestamp>.png, with spaces converted to dashes
|
||||
local safe_step="${step_name// /-}"
|
||||
local file="${dir}/${test_name}_${safe_step}_${ts}.png"
|
||||
|
||||
echo "📸 Capturing screenshot: ${file}" >&2
|
||||
# Use exec-out to avoid newline mangling
|
||||
if ! "$ADB_BIN" exec-out screencap -p > "$file" 2>/dev/null; then
|
||||
echo "⚠️ Failed to capture screenshot via adb." >&2
|
||||
# Clean up empty file if created
|
||||
[ -s "$file" ] || rm -f "$file" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Verify file was created and has content
|
||||
if [ ! -s "$file" ]; then
|
||||
echo "⚠️ Screenshot file is empty or missing: $file" >&2
|
||||
rm -f "$file" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
take_failure_screenshot() {
|
||||
# Convenience wrapper for failure cases
|
||||
# Usage: take_failure_screenshot "test_name" "reason"
|
||||
# Example: take_failure_screenshot "phase1_test0_daily_rollover" "no_alarm_after_rollover"
|
||||
local test_name="$1"
|
||||
local reason="$2"
|
||||
take_screenshot "$test_name" "FAIL_${reason}"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ android {
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true // Enable debugging for test app
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
<div id="pluginStatusContent" style="margin-top: 8px;">
|
||||
Loading plugin status...
|
||||
</div>
|
||||
<div id="notificationReceivedIndicator" style="margin-top: 8px; padding: 8px; background: rgba(0, 255, 0, 0.2); border-radius: 5px; display: none;">
|
||||
<strong>🔔 Notification Received!</strong><br>
|
||||
<span id="notificationReceivedTime"></span><br>
|
||||
<small>Check the top of your screen for the notification banner</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,7 +237,8 @@
|
||||
const notificationTimeReadable = notificationTime.toLocaleTimeString();
|
||||
status.innerHTML = '✅ Notification scheduled!<br>' +
|
||||
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
|
||||
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
|
||||
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')<br><br>' +
|
||||
'<small>💡 When the notification fires, look for a banner at the <strong>top of your screen</strong>.</small>';
|
||||
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||
// Refresh plugin status display
|
||||
setTimeout(() => loadPluginStatus(), 500);
|
||||
@@ -411,6 +417,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Check for notification delivery periodically
|
||||
function checkNotificationDelivery() {
|
||||
if (!window.DailyNotification) return;
|
||||
|
||||
window.DailyNotification.getNotificationStatus()
|
||||
.then(result => {
|
||||
if (result.lastNotificationTime) {
|
||||
const lastTime = new Date(result.lastNotificationTime);
|
||||
const now = new Date();
|
||||
const timeDiff = now - lastTime;
|
||||
|
||||
// If notification was received in the last 2 minutes, show indicator
|
||||
if (timeDiff > 0 && timeDiff < 120000) {
|
||||
const indicator = document.getElementById('notificationReceivedIndicator');
|
||||
const timeSpan = document.getElementById('notificationReceivedTime');
|
||||
|
||||
if (indicator && timeSpan) {
|
||||
indicator.style.display = 'block';
|
||||
timeSpan.textContent = `Received at ${lastTime.toLocaleTimeString()}`;
|
||||
|
||||
// Hide after 30 seconds
|
||||
setTimeout(() => {
|
||||
indicator.style.display = 'none';
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Silently fail - this is just for visual feedback
|
||||
});
|
||||
}
|
||||
|
||||
// Load plugin status automatically on page load
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Page loaded, loading plugin status...');
|
||||
@@ -419,6 +458,9 @@
|
||||
loadPluginStatus();
|
||||
loadPermissionStatus();
|
||||
loadChannelStatus();
|
||||
|
||||
// Check for notification delivery every 5 seconds
|
||||
setInterval(checkNotificationDelivery, 5000);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
|
||||
234
test-apps/android-test-app/docs/PHASE1_TEST0_GOLDEN.md
Normal file
234
test-apps/android-test-app/docs/PHASE1_TEST0_GOLDEN.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Phase 1 — TEST 0 Golden Run (Daily Rollover Verification)
|
||||
|
||||
**Last Updated:** 2025-12-04
|
||||
**Status:** ✅ PASS (Golden Baseline)
|
||||
|
||||
**Related Docs:**
|
||||
- [PHASE1_TEST0_GOLDEN.md](./PHASE1_TEST0_GOLDEN.md) - Daily Rollover Verification (this document)
|
||||
- [PHASE1_TEST1_GOLDEN.md](./PHASE1_TEST1_GOLDEN.md) - Force-Stop Recovery
|
||||
|
||||
---
|
||||
|
||||
## 1. Test Overview
|
||||
|
||||
This document captures a **golden baseline** for **Phase 1 – TEST 0: Daily Rollover Verification**.
|
||||
|
||||
**Purpose:** Verify that after a notification fires, the plugin:
|
||||
- Computes **next day's time** (T + 24h)
|
||||
- Schedules **exactly one** `AlarmManager` notification alarm for tomorrow
|
||||
- Does **not** create duplicates
|
||||
- Leaves prefetch in **WorkManager** (not visible in `dumpsys alarm`)
|
||||
|
||||
> **Golden Rule:** If a future run looks like this doc, TEST 0 should be considered a PASS.
|
||||
|
||||
---
|
||||
|
||||
## 2. Environment & Build Info
|
||||
|
||||
### Emulator / Device
|
||||
- **API Level:** 35
|
||||
- **Android Version:** 15
|
||||
|
||||
### Build
|
||||
- **Gradle Version:** 8.13
|
||||
- **Build Warnings:**
|
||||
- `WARNING: Using flatDir should be avoided because it doesn't support any meta-data formats.`
|
||||
- `Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.`
|
||||
|
||||
### Command Used
|
||||
```bash
|
||||
./test-phase1.sh
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
- `ENABLE_SCREENSHOTS=1` (screenshots enabled)
|
||||
- `ADB_BIN=adb` (default)
|
||||
|
||||
---
|
||||
|
||||
## 3. Step-by-Step Execution (Golden Run)
|
||||
|
||||
1. Ran `./test-phase1.sh`.
|
||||
2. Confirmed pre-flight checks (ADB device + emulator ready).
|
||||
3. Allowed script to rebuild and reinstall the app.
|
||||
4. Confirmed plugin status in the UI:
|
||||
- ⚙️ Plugin Settings: ✅ Configured
|
||||
- 🔌 Native Fetcher: ✅ Configured
|
||||
- 🔔 Notifications: ✅ Granted
|
||||
- ⏰ Exact Alarms: ✅ Granted
|
||||
- 📢 Channel: ✅ Enabled (High)
|
||||
5. From the UI, scheduled a **daily notification** for ~1–2 minutes in the future (scheduled for `09:23:00` on 2025-12-04).
|
||||
6. Waited for the notification banner to fire.
|
||||
7. Pressed Enter to continue when prompted.
|
||||
8. Let the script perform the post-rollover alarm check.
|
||||
|
||||
---
|
||||
|
||||
## 4. Expected Script Output (Key Excerpts)
|
||||
|
||||
### 4.1. Pre-Schedule Check
|
||||
```text
|
||||
✅ Found 1 notification alarm (expected: 1) – preliminary check passed.
|
||||
ℹ️ This is preliminary check only; final verdict after rollover.
|
||||
```
|
||||
|
||||
### 4.2. Notification Alarm Details (After Scheduling)
|
||||
```text
|
||||
ℹ️ Notification alarm details:
|
||||
tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
type=RTC_WAKEUP origWhen=2025-12-04 09:23:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
|
||||
policyWhenElapsed: requester=+3m34s315ms app_standby=-10s456ms device_idle=-- battery_saver=--
|
||||
```
|
||||
|
||||
### 4.3. Post-Rollover Check
|
||||
```text
|
||||
ℹ️ Polling for stable alarm count (allowing up to ~10 seconds for Android to settle)...
|
||||
ℹ️ Notification alarms after rollover: 1 (expected: 1)
|
||||
ℹ️ System/other alarms: <N> (for context)
|
||||
ℹ️ Note: Prefetch is scheduled via WorkManager (not AlarmManager), so it won't appear in alarm count
|
||||
```
|
||||
|
||||
### 4.4. Final Verdict
|
||||
```text
|
||||
✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow.
|
||||
Expected state after rollover:
|
||||
✅ 1 notification alarm (AlarmManager) for tomorrow
|
||||
✅ 1 prefetch job (WorkManager) for 2 minutes before tomorrow's notification
|
||||
```
|
||||
|
||||
**Note:** The `origWhen` for tomorrow will be the next day at the same time (e.g., `2025-12-05 09:23:00.000` if scheduled for `2025-12-04 09:23:00.000`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Expected UI State (Screenshots)
|
||||
|
||||
### 5.1 Screenshot Files (Golden Run)
|
||||
|
||||
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_before_scheduling_20251204-091910.png`
|
||||
- **Status:** Active Schedules: **No**; Next Notification: **None scheduled**.
|
||||
|
||||
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_after_scheduling_20251204-091925.png`
|
||||
- **Status:** Active Schedules: **Yes**; Next Notification: **today at 09:23:00 AM**; Pending: **1**.
|
||||
|
||||
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_after_rollover_check_20251204-092307.png`
|
||||
- **Status:** Active Schedules: **Yes**; Next Notification: **tomorrow at 09:23:00 AM** (24 hours later); Pending: **1**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Expected `dumpsys alarm` Shape
|
||||
|
||||
### 6.1. Representative Snippet
|
||||
|
||||
```text
|
||||
RTC_WAKEUP #<N>: Alarm{<handle> type 0 origWhen <timestamp> whenElapsed ... com.timesafari.dailynotification}
|
||||
tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
type=RTC_WAKEUP origWhen=2025-12-05 09:23:00.000 ...
|
||||
...
|
||||
|
||||
Next wake from idle: Alarm{<handle> type 0 origWhen <timestamp> ... com.timesafari.dailynotification}
|
||||
tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
```
|
||||
|
||||
### 6.2. Key Observations
|
||||
|
||||
- There should be **exactly one unique alarm handle** for the plugin (the handle will differ between runs).
|
||||
- It can appear both in the main list and in **"Next wake from idle"**, but counted as **one** alarm (deduplication by alarm handle).
|
||||
- `tag` must be `*walarm*:com.timesafari.daily.NOTIFICATION`.
|
||||
- `type` must be `RTC_WAKEUP`.
|
||||
- `origWhen` should be **tomorrow** at the same time-of-day as the scheduled notification (e.g., `2025-12-05 09:23:00.000` if scheduled for `2025-12-04 09:23:00.000`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Expected `logcat` Patterns
|
||||
|
||||
### 7.1. Scheduling Test Notification
|
||||
|
||||
```text
|
||||
DNP-SCHEDULE: Scheduling next daily alarm: id=daily_..., nextRun=2025-12-04 09:23:00, source=TEST_NOTIFICATION
|
||||
DNP-NOTIFY: Stored notification content in database: id=daily_...
|
||||
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:23:00, ...
|
||||
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ...
|
||||
```
|
||||
|
||||
### 7.2. Rollover on Fire
|
||||
|
||||
```text
|
||||
DNP-SCHEDULE: Scheduling next daily alarm: id=daily_rollover_..., nextRun=2025-12-05 09:23:00, source=ROLLOVER_ON_FIRE
|
||||
DNP-NOTIFY: Stored notification content in database: id=notify_...
|
||||
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-05 09:23:00, ...
|
||||
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ...
|
||||
```
|
||||
|
||||
### 7.3. Critical Requirements
|
||||
|
||||
**Both sequences must be present** for a true PASS:
|
||||
- `source=TEST_NOTIFICATION` sequence when scheduling the initial test notification
|
||||
- `source=ROLLOVER_ON_FIRE` sequence when the notification fires and schedules tomorrow's alarm
|
||||
- Times must match: initial schedule time → tomorrow's time (T + 24h)
|
||||
- Example: `2025-12-04 09:23:00` → `2025-12-05 09:23:00`
|
||||
|
||||
---
|
||||
|
||||
## 8. Quick Pass/Fail Checklist
|
||||
|
||||
A run of TEST 0 is a **PASS** if all of the following are true:
|
||||
|
||||
### Script Output
|
||||
- [ ] Shows "Found 1 notification alarm (expected: 1) – preliminary check passed."
|
||||
- [ ] Shows "Notification alarms after rollover: 1 (expected: 1)".
|
||||
- [ ] Ends with "✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow."
|
||||
|
||||
### UI State
|
||||
- [ ] **Before scheduling:** Active Schedules: No; Next Notification: None scheduled.
|
||||
- [ ] **After scheduling:** Active Schedules: Yes; Next Notification: *today* at the chosen time.
|
||||
- [ ] **After rollover:** Active Schedules: Yes; Next Notification: *tomorrow* at the same time.
|
||||
|
||||
### `dumpsys alarm`
|
||||
- [ ] Exactly one `RTC_WAKEUP` alarm with `tag=*walarm*:com.timesafari.daily.NOTIFICATION` for **tomorrow**.
|
||||
- [ ] Same alarm handle may appear under "Next wake from idle", but no second distinct handle.
|
||||
- [ ] `origWhen` timestamp is exactly 24 hours after the initial scheduled time.
|
||||
|
||||
### `logcat`
|
||||
- [ ] Shows both `source=TEST_NOTIFICATION` and `source=ROLLOVER_ON_FIRE` sequences with matching times.
|
||||
- [ ] No duplicate `DNP-SCHEDULE` entries for the same `nextRun` time.
|
||||
- [ ] No errors or warnings related to alarm scheduling.
|
||||
|
||||
---
|
||||
|
||||
## 9. Notes / Deviations
|
||||
|
||||
### Failure Conditions
|
||||
- If there is exactly **0** alarms after rollover, treat as **INCONCLUSIVE** and investigate:
|
||||
- Check `logcat` for `ROLLOVER_ON_FIRE` sequence
|
||||
- Verify `dumpsys alarm` manually
|
||||
- Check for scheduling errors in logs
|
||||
- If there are **>1** alarms after rollover, treat as **FAIL** (duplicate alarm bug):
|
||||
- Check for multiple `DNP-SCHEDULE` entries with same `nextRun`
|
||||
- Verify idempotence checks are working
|
||||
- Check for race conditions between rollover and recovery paths
|
||||
|
||||
### Time-of-Day Variations
|
||||
- Time-of-day may differ in future golden runs; **structure and relationships must remain the same**.
|
||||
- The key is: initial time → tomorrow's time (T + 24h), not the specific hour/minute.
|
||||
|
||||
### Screenshot Timestamps
|
||||
- Screenshot filenames include timestamps (`YYYYMMDD-HHMMSS`), so exact filenames will differ between runs.
|
||||
- Focus on the **content** of screenshots (UI state) rather than exact filenames.
|
||||
|
||||
### Alarm Handle Variations
|
||||
- The alarm handle (e.g., `Alarm{1f00a1b}`) will differ between runs; this is expected.
|
||||
- The important thing is that there is **exactly one unique handle** per scheduled alarm.
|
||||
|
||||
---
|
||||
|
||||
## 10. Updating This Document
|
||||
|
||||
When updating this golden run document:
|
||||
1. Update timestamps and IDs with actual values from your successful run
|
||||
2. Replace placeholder values (marked with "Update...") with real data
|
||||
3. Update screenshot filenames with actual timestamps
|
||||
4. Add any environment-specific notes that might affect future runs
|
||||
5. Document any deviations or edge cases encountered
|
||||
|
||||
**Last Golden Run Date:** 2025-12-04 (09:23:00 scheduled time)
|
||||
|
||||
327
test-apps/android-test-app/docs/PHASE1_TEST1_GOLDEN.md
Normal file
327
test-apps/android-test-app/docs/PHASE1_TEST1_GOLDEN.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Phase 1 — TEST 1 Golden Run (Force-Stop Recovery - Database Restoration)
|
||||
|
||||
**Related Docs:**
|
||||
- [PHASE1_TEST0_GOLDEN.md](./PHASE1_TEST0_GOLDEN.md)
|
||||
- [PHASE1_TEST1_GOLDEN.md](./PHASE1_TEST1_GOLDEN.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Test Overview
|
||||
|
||||
**Test Name:** TEST 1 — Force-Stop Recovery: Database Restoration
|
||||
**Purpose:** Verify that after an Android **force-stop** (which clears all scheduled alarms), the plugin:
|
||||
|
||||
1. **Persists** the alarm schedule in its database.
|
||||
2. **Detects** that alarms are missing on app launch.
|
||||
3. **Rebuilds** the missing alarm(s) from the database.
|
||||
4. Restores the **one-notification-per-day** contract:
|
||||
- Before force-stop: 1 alarm
|
||||
- After force-stop: 0 alarms
|
||||
- After recovery: 1 alarm (same trigger time as before force-stop)
|
||||
|
||||
This golden run documents a **known-good execution** on 2025-12-04.
|
||||
|
||||
---
|
||||
|
||||
## 2. Environment & Build Info
|
||||
|
||||
- **Date/Time of Run:** 2025-12-04 (around 09:51–09:55 UTC)
|
||||
- **Command Used:**
|
||||
|
||||
```bash
|
||||
./test-phase1.sh
|
||||
```
|
||||
|
||||
* **Project:** `daily-notification-plugin` — `test-apps/android-test-app`
|
||||
|
||||
* **Gradle:**
|
||||
|
||||
* Build invoked via `./gradlew assembleDebug` (through the script)
|
||||
* Gradle output included:
|
||||
|
||||
```text
|
||||
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
|
||||
```
|
||||
|
||||
* **APK Built:**
|
||||
|
||||
```text
|
||||
./app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
* **App ID:** `com.timesafari.dailynotification`
|
||||
|
||||
> **Note:** Device/emulator model & API level can be added here later if desired.
|
||||
|
||||
---
|
||||
|
||||
## 3. Step-by-Step Execution (Golden Run)
|
||||
|
||||
This section captures the **actual** sequence for the golden run.
|
||||
|
||||
1. Ran `./test-phase1.sh`.
|
||||
2. Pre-flight checks:
|
||||
* ✅ ADB device connected
|
||||
* ✅ Emulator ready
|
||||
3. Build phase:
|
||||
* ✅ Debug APK built successfully.
|
||||
* ✅ APK path: `./app/build/outputs/apk/debug/app-debug.apk`
|
||||
4. Install phase:
|
||||
* ✅ Previous app uninstall succeeded
|
||||
* ✅ APK installed successfully
|
||||
* ✅ App verified in package list
|
||||
* ✅ Logcat cleared (twice)
|
||||
5. **TEST 0** executed:
|
||||
* Configured plugin in UI (if needed).
|
||||
* Scheduled daily notification.
|
||||
* Verified:
|
||||
* Before scheduling: 0 plugin alarms.
|
||||
* After scheduling: 1 plugin alarm for **today**.
|
||||
* After fire/rollover: 1 plugin alarm for **tomorrow**.
|
||||
* **TEST 0 PASSED.**
|
||||
6. **TEST 1** started:
|
||||
* Step 1: Detected 1 lingering plugin alarm (tomorrow's alarm from TEST 0).
|
||||
* Auto-reset:
|
||||
* Uninstalled app
|
||||
* Reinstalled APK
|
||||
* Cleared logcat
|
||||
* Verified plugin alarms = 0
|
||||
* Confirmed **clean starting state** for TEST 1.
|
||||
7. Step 2: Launched app and confirmed plugin configured.
|
||||
8. In UI: tapped **"Test Notification"** button (schedules a notification ~4 minutes in the future).
|
||||
9. Step 3 (pre-FS verify):
|
||||
* Verified **1** plugin alarm exists in AlarmManager.
|
||||
* Confirmed alarm details (time, tag, type).
|
||||
* Confirmed scheduling logs with `source=TEST_NOTIFICATION`.
|
||||
10. Step 4: Performed **force-stop** via `adb shell am force-stop com.timesafari.dailynotification`.
|
||||
11. Step 5 (post-FS verify):
|
||||
* Verified plugin alarms = **0** (force-stop cleared alarms).
|
||||
12. Step 6: Relaunched app (cold start).
|
||||
13. Step 7 (recovery verify):
|
||||
* Verified plugin alarms = **1** after recovery.
|
||||
* Confirmed recovery logs under `DNP-REACTIVATION`:
|
||||
* App launch recovery
|
||||
* Boot recovery
|
||||
* `rescheduled=1`
|
||||
* Confirmed rescheduled alarm uses the **same scheduleId and triggerTime** as pre-force-stop.
|
||||
14. Fire verification was **skipped** (`VERIFY_FIRE=false`).
|
||||
15. TEST 1 summary:
|
||||
* ✅ Before FS: 1 alarm
|
||||
* ✅ After FS: 0 alarms
|
||||
* ✅ After recovery: 1 alarm
|
||||
* ✅ `rescheduled=1`, `errors=0`
|
||||
* ✅ TEST 1 PASSED.
|
||||
|
||||
---
|
||||
|
||||
## 4. Expected Script Output (Key Excerpts)
|
||||
|
||||
These are the **critical excerpts** from the test harness output for a passing TEST 1.
|
||||
|
||||
### 4.1 Clean Start & Auto-Reset
|
||||
|
||||
```text
|
||||
→ Step 1: Clean start - checking for lingering alarms...
|
||||
ℹ️ Current plugin notification alarms: 1
|
||||
ℹ️ System/other alarms: 17 (for context)
|
||||
⚠️ Found 1 lingering plugin alarm(s) - these will interfere with TEST 1.
|
||||
ℹ️ TEST 1 needs a clean state (no existing plugin alarms).
|
||||
ℹ️ Resetting app state via uninstall + reinstall...
|
||||
ℹ️ Uninstalling existing app...
|
||||
✅ App uninstall succeeded (clean slate).
|
||||
ℹ️ Reinstalling APK...
|
||||
✅ App reinstall succeeded.
|
||||
ℹ️ Clearing logcat buffer after reinstall...
|
||||
ℹ️ Rechecking plugin alarms after reset...
|
||||
ℹ️ Plugin alarms after reset: 0 (expected: 0)
|
||||
✅ App state reset complete. TEST 1 starting from clean state.
|
||||
```
|
||||
|
||||
### 4.2 Alarm Scheduled Before Force-Stop
|
||||
|
||||
```text
|
||||
→ Step 3: Verifying alarm exists in AlarmManager (BEFORE force-stop)...
|
||||
ℹ️ Plugin alarms: 1 (expected: 1)
|
||||
ℹ️ System/other alarms: 19 (for context)
|
||||
✅ ✅ Single plugin alarm confirmed in AlarmManager (one per day)
|
||||
ℹ️ Alarm details:
|
||||
RTC_WAKEUP #4: Alarm{161cd2b type 0 origWhen 1764842100000 whenElapsed 13009441 com.timesafari.dailynotification}
|
||||
tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
type=RTC_WAKEUP origWhen=2025-12-04 09:55:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
|
||||
policyWhenElapsed: requester=+3m4s281ms app_standby=-6s566ms device_idle=-- battery_saver=--
|
||||
--
|
||||
ℹ️ Alarm scheduled for: Thu Dec 4 09:55:00 AM UTC 2025 (1764842100000 ms)
|
||||
ℹ️ Checking logs for scheduling confirmation...
|
||||
12-04 09:51:49.150 6803 6867 W DNP-SCHEDULE: Cancelling existing alarm before rescheduling: requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION
|
||||
12-04 09:51:49.151 6803 6867 I DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:55:00, delayMs=190849, requestCode=3454, scheduleId=daily_1764841909137
|
||||
12-04 09:51:49.152 6803 6867 I DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, triggerTime=1764842100000, requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION, pendingIntentHash=267839060, showIntentHash=256236029
|
||||
12-04 09:51:49.153 6803 6867 I DNP-NOTIFY: Alarm clock scheduled (setAlarmClock): triggerAt=1764842100000, requestCode=3454
|
||||
```
|
||||
|
||||
### 4.3 Force-Stop & Alarm Clearance
|
||||
|
||||
```text
|
||||
→ Step 4: Force-stopping app (clears all alarms)...
|
||||
⚠️ Force-stop will clear ALL alarms from AlarmManager
|
||||
ℹ️ Executing: adb shell am force-stop com.timesafari.dailynotification
|
||||
ℹ️ Forcing stop of app process...
|
||||
✅ Force stop issued
|
||||
→ Step 5: Verifying alarms are MISSING from AlarmManager (AFTER force-stop)...
|
||||
ℹ️ Plugin alarms after force-stop: 0 (expected: 0)
|
||||
ℹ️ System/other alarms: 17 (for context)
|
||||
✅ ✅ Plugin alarms cleared by force-stop (count: 0)
|
||||
ℹ️ This confirms: Force-stop cleared alarms from AlarmManager
|
||||
```
|
||||
|
||||
### 4.4 Recovery & Rescheduling
|
||||
|
||||
```text
|
||||
→ Step 6: Relaunching app (triggers recovery from database)...
|
||||
ℹ️ Clearing logcat buffer...
|
||||
✅ Logs cleared
|
||||
ℹ️ Launching app...
|
||||
Starting: Intent { cmp=com.timesafari.dailynotification/.MainActivity }
|
||||
✅ App launched
|
||||
→ Step 7: Verifying recovery rebuilt alarms from database...
|
||||
ℹ️ Plugin alarms after recovery: 1 (expected: 1)
|
||||
ℹ️ System/other alarms: 21 (for context)
|
||||
ℹ️ Checking recovery logs...
|
||||
|
||||
12-04 09:52:21.919 6954 7015 I DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)
|
||||
12-04 09:52:21.926 6954 7015 I DNP-REACTIVATION: Cold start recovery: checking for missed notifications
|
||||
12-04 09:52:21.937 6954 7015 I DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
|
||||
12-04 09:52:21.937 6954 7015 I DNP-REACTIVATION: Rescheduled missing alarm: daily_1764841909137 at 1764842100000
|
||||
12-04 09:52:21.952 6954 7015 I DNP-REACTIVATION: Cold start recovery complete: missed=0, rescheduled=1, verified=0, errors=0
|
||||
12-04 09:52:21.952 6954 7015 I DNP-REACTIVATION: App launch recovery completed: missed=0, rescheduled=1, verified=0, errors=0
|
||||
12-04 09:52:22.077 6954 7015 I DNP-REACTIVATION: Starting boot recovery
|
||||
12-04 09:52:22.135 6954 7017 I DNP-REACTIVATION: Loaded 1 schedules from DB
|
||||
12-04 09:52:22.138 6954 7017 I DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
|
||||
12-04 09:52:22.143 6954 7017 I DNP-REACTIVATION: Boot recovery complete: missed=0, rescheduled=1, verified=0, errors=0
|
||||
```
|
||||
|
||||
Final summary:
|
||||
|
||||
```text
|
||||
✅ ✅ Alarms restored in AlarmManager (count: 1)
|
||||
✅ ✅ Recovery logs confirm rescheduling (rescheduled=1)
|
||||
✅ TEST 1 PASSED: Recovery successfully rebuilt alarms from database!
|
||||
ℹ️ Summary:
|
||||
- Before force-stop: 1 alarm(s)
|
||||
- After force-stop: 0 alarm(s) (cleared)
|
||||
- After recovery: 1 alarm(s) (rebuilt)
|
||||
- Rescheduled: 1 alarm(s)
|
||||
- Verified: 0 alarm(s)
|
||||
ℹ️ Skipping fire verification (VERIFY_FIRE=false, set VERIFY_FIRE=true to enable)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Expected `dumpsys alarm` Shapes
|
||||
|
||||
### 5.1 Before Force-Stop (Scheduled)
|
||||
|
||||
* **Plugin alarm count:** 1
|
||||
* Example block (shape, not necessarily exact handle):
|
||||
|
||||
```text
|
||||
RTC_WAKEUP #4: Alarm{161cd2b type 0 origWhen 1764842100000 whenElapsed 13009441 com.timesafari.dailynotification}
|
||||
tag=*walarm*:com.timesafari.daily.NOTIFICATION
|
||||
type=RTC_WAKEUP origWhen=2025-12-04 09:55:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
|
||||
policyWhenElapsed: requester=+3m4s281ms app_standby=-6s566ms device_idle=-- battery_saver=--
|
||||
```
|
||||
|
||||
### 5.2 After Force-Stop
|
||||
|
||||
* **Plugin alarm count:** 0
|
||||
* No `*walarm*:com.timesafari.daily.NOTIFICATION` entries should appear.
|
||||
|
||||
### 5.3 After Recovery
|
||||
|
||||
* **Plugin alarm count:** 1
|
||||
* Alarm should be restored with:
|
||||
* Same `scheduleId`: `daily_1764841909137`
|
||||
* Same `triggerTime`: `1764842100000` → `2025-12-04 09:55:00`
|
||||
|
||||
---
|
||||
|
||||
## 6. Expected `logcat` Patterns
|
||||
|
||||
For a passing run, look for:
|
||||
|
||||
* **Scheduling (before FS):**
|
||||
|
||||
```text
|
||||
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:55:00, delayMs=..., requestCode=3454, scheduleId=daily_1764841909137
|
||||
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, triggerTime=1764842100000, requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION, ...
|
||||
```
|
||||
|
||||
* **Recovery (after FS + relaunch):**
|
||||
|
||||
```text
|
||||
DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)
|
||||
DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
|
||||
DNP-REACTIVATION: Cold start recovery complete: missed=0, rescheduled=1, verified=0, errors=0
|
||||
DNP-REACTIVATION: App launch recovery completed: missed=0, rescheduled=1, verified=0, errors=0
|
||||
|
||||
DNP-REACTIVATION: Starting boot recovery
|
||||
DNP-REACTIVATION: Loaded 1 schedules from DB
|
||||
DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
|
||||
DNP-REACTIVATION: Boot recovery complete: missed=0, rescheduled=1, verified=0, errors=0
|
||||
```
|
||||
|
||||
Key invariants:
|
||||
|
||||
* `rescheduled=1`
|
||||
* `errors=0`
|
||||
* `scheduleId` and `triggerTime` match pre-FS values.
|
||||
|
||||
---
|
||||
|
||||
## 7. Quick Pass/Fail Checklist
|
||||
|
||||
A TEST 1 run is a **PASS** if all of the following are true:
|
||||
|
||||
* **Starting state:**
|
||||
* Script auto-resets if lingering alarms exist from TEST 0.
|
||||
* After reset: plugin alarm count = **0**.
|
||||
|
||||
* **Pre-force-stop:**
|
||||
* Plugin alarm count = **1**.
|
||||
* Alarm is tagged `*walarm*:com.timesafari.daily.NOTIFICATION`.
|
||||
* `triggerTime` and `origWhen` are consistent (e.g. `2025-12-04 09:55:00`, `1764842100000`).
|
||||
* `scheduleId` looks like `daily_<timestamp>` (here: `daily_1764841909137`).
|
||||
* Logs show `source=TEST_NOTIFICATION`.
|
||||
|
||||
* **After force-stop:**
|
||||
* Plugin alarm count = **0**.
|
||||
* No NOTIFICATION alarms remain in `dumpsys`.
|
||||
|
||||
* **After recovery (relaunch):**
|
||||
* Plugin alarm count = **1**.
|
||||
* Logs show `DNP-REACTIVATION` with:
|
||||
* `rescheduled=1`
|
||||
* `errors=0`
|
||||
* Same `scheduleId` and `triggerTime` as pre-FS.
|
||||
|
||||
* **Script summary:**
|
||||
* States:
|
||||
* Before force-stop: 1 alarm
|
||||
* After force-stop: 0 alarms
|
||||
* After recovery: 1 alarm
|
||||
* Ends with: `✅ TEST 1 PASSED: Recovery successfully rebuilt alarms from database!`
|
||||
|
||||
If any of these conditions fail, the run is **NOT GOLDEN** and should not overwrite this reference.
|
||||
|
||||
---
|
||||
|
||||
## 8. Notes / Deviations
|
||||
|
||||
* This golden run **skips fire verification** (`VERIFY_FIRE=false`).
|
||||
Future runs may enable fire verification; if that becomes standard, update this doc.
|
||||
* It is acceptable for both:
|
||||
* **Cold start recovery** and
|
||||
* **Boot recovery**
|
||||
to run on app launch, as long as:
|
||||
* `rescheduled=1`
|
||||
* No duplicate alarms are scheduled.
|
||||
* If OS behavior changes (e.g., force-stop no longer clears alarms on a future Android version), this test's expectations may need to be revised; document any such deviations explicitly here before changing the checklist.
|
||||
1171
test-apps/android-test-app/test-phase1.sh
Executable file
1171
test-apps/android-test-app/test-phase1.sh
Executable file
File diff suppressed because it is too large
Load Diff
352
test-apps/android-test-app/test-phase2.sh
Executable file
352
test-apps/android-test-app/test-phase2.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ========================================
|
||||
# Phase 2 Testing Script – Force Stop Recovery
|
||||
# ========================================
|
||||
|
||||
# Source shared library
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/alarm-test-lib.sh"
|
||||
|
||||
# Phase 2 specific configuration
|
||||
# Log tags / patterns (matched to actual ReactivationManager logs)
|
||||
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
|
||||
COLD_START_SCENARIO_VALUE="COLD_START"
|
||||
NONE_SCENARIO_VALUE="NONE"
|
||||
BOOT_SCENARIO_VALUE="BOOT"
|
||||
|
||||
# Allow selecting specific tests on the command line (e.g. ./test-phase2.sh 2 3)
|
||||
SELECTED_TESTS=()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 1 – Force Stop with Cleared Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test1_force_stop_cleared_alarms() {
|
||||
section "TEST 1: Force Stop – Alarms Cleared"
|
||||
|
||||
echo "Purpose: Verify force stop detection and alarm rescheduling when alarms are cleared."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & check plugin status"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
|
||||
|
||||
ui_prompt "Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before_count system_count
|
||||
before_count="$(get_plugin_alarm_count)"
|
||||
system_count="$(get_system_alarm_count)"
|
||||
info "Plugin alarms before force stop: $before_count (expected: 1)"
|
||||
info "System/other alarms: $system_count (for context)"
|
||||
|
||||
if [[ "$before_count" -eq 0 ]]; then
|
||||
warn "No plugin alarms found before force stop; TEST 1 may not be meaningful."
|
||||
elif [[ "$before_count" -eq 1 ]]; then
|
||||
ok "Single plugin alarm confirmed (one per day)"
|
||||
else
|
||||
warn "Found $before_count plugin alarms (expected: 1)"
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Force stop app (should clear alarms on many devices)"
|
||||
force_stop_app
|
||||
|
||||
substep "Step 4: Check alarms after force stop"
|
||||
local after_count system_after
|
||||
after_count="$(get_plugin_alarm_count)"
|
||||
system_after="$(get_system_alarm_count)"
|
||||
info "Plugin alarms after force stop: $after_count (expected: 0)"
|
||||
info "System/other alarms: $system_after (for context)"
|
||||
show_alarms
|
||||
|
||||
if [[ "$after_count" -gt 0 ]]; then
|
||||
warn "Plugin alarms still present after force stop. This device/OS may not clear alarms on force stop."
|
||||
warn "TEST 1 will continue but may not fully validate FORCE_STOP scenario."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 4.5: Clear boot flag (prevent false BOOT detection)"
|
||||
# Clear boot flag to ensure force stop detection works correctly
|
||||
# Boot flag might be set from previous runs or emulator quirks
|
||||
adb shell "run-as ${APP_ID} rm -f shared_prefs/dailynotification_recovery.xml 2>/dev/null || true"
|
||||
info "Boot flag cleared (if it existed)"
|
||||
|
||||
substep "Step 5: Launch app (triggers recovery) and capture logs"
|
||||
clear_logs
|
||||
launch_app
|
||||
sleep 5 # give recovery a moment to run
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled verified errors
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 1 PASSED: Force stop detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
|
||||
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
warn "TEST 1: scenario=FORCE_STOP but rescheduled=0. Check implementation or logs."
|
||||
elif [[ "$after_count" -gt 0 ]]; then
|
||||
info "TEST 1: Device/emulator kept alarms after force stop; FORCE_STOP scenario may not trigger here."
|
||||
if [[ "$rescheduled" -gt 0 ]]; then
|
||||
info "Recovery still worked (rescheduled=$rescheduled), but scenario was ${scenario:-COLD_START} instead of FORCE_STOP"
|
||||
fi
|
||||
else
|
||||
warn "TEST 1: Expected FORCE_STOP scenario not clearly detected. Review logs and scenario detection logic."
|
||||
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
|
||||
fi
|
||||
|
||||
substep "Step 6: Verify alarms are rescheduled in AlarmManager"
|
||||
show_alarms
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 2 – Force Stop / Process Stop with Intact Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test2_force_stop_intact_alarms() {
|
||||
section "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
|
||||
echo "Purpose: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & schedule notifications"
|
||||
launch_app
|
||||
ui_prompt "In the app UI, ensure plugin is configured and schedule at least one future notification.\n\nPress Enter when done."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before system_before
|
||||
before="$(get_plugin_alarm_count)"
|
||||
system_before="$(get_system_alarm_count)"
|
||||
info "Plugin alarms before stop: $before (expected: 1)"
|
||||
info "System/other alarms: $system_before (for context)"
|
||||
|
||||
if [[ "$before" -eq 0 ]]; then
|
||||
warn "No plugin alarms found; TEST 2 may not be meaningful."
|
||||
elif [[ "$before" -eq 1 ]]; then
|
||||
ok "Single plugin alarm confirmed (one per day)"
|
||||
else
|
||||
warn "Found $before plugin alarms (expected: 1)"
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Simulate a 'soft' stop or process kill that does NOT clear alarms"
|
||||
info "Killing app process (non-destructive - may not clear alarms)..."
|
||||
$ADB_BIN shell am kill "$APP_ID" || true
|
||||
sleep 2
|
||||
ok "Kill signal sent (soft stop)"
|
||||
|
||||
substep "Step 4: Verify alarms are still scheduled"
|
||||
local after system_after
|
||||
after="$(get_plugin_alarm_count)"
|
||||
system_after="$(get_system_alarm_count)"
|
||||
info "Plugin alarms after soft stop: $after (expected: 1)"
|
||||
info "System/other alarms: $system_after (for context)"
|
||||
show_alarms
|
||||
|
||||
if [[ "$after" -eq 0 ]]; then
|
||||
warn "Alarms appear cleared after soft stop; this environment may not distinguish TEST 2 well."
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 5: Relaunch app and check recovery logs"
|
||||
clear_logs
|
||||
launch_app
|
||||
sleep 5
|
||||
|
||||
info "Collecting recovery logs..."
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled missed verified errors
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " missed = ${missed}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$after" -gt 0 && "$rescheduled" -eq 0 && "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" ]]; then
|
||||
ok "TEST 2 PASSED: Alarms remained intact, and FORCE_STOP scenario did not run (scenario=$scenario, rescheduled=0)."
|
||||
else
|
||||
warn "TEST 2: Verify that FORCE_STOP recovery didn't misfire when alarms were intact."
|
||||
info "Scenario=${scenario:-<none>}, rescheduled=$rescheduled, after_count=$after"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 3 – First Launch / Empty DB Safeguard
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test3_first_launch_no_schedules() {
|
||||
section "TEST 3: First Launch / No Schedules Safeguard"
|
||||
|
||||
echo "Purpose: Ensure force-stop recovery is NOT triggered when DB is empty or plugin isn't configured."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Uninstall app to clear DB/state"
|
||||
set +e
|
||||
$ADB_BIN uninstall "$APP_ID" >/dev/null 2>&1
|
||||
set -e
|
||||
ok "App uninstalled (state cleared)"
|
||||
|
||||
substep "Step 2: Reinstall app"
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed"
|
||||
else
|
||||
error "Reinstall failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Launch app for the first time"
|
||||
launch_app
|
||||
sleep 5
|
||||
|
||||
substep "Step 4: Collect logs and ensure no force-stop recovery ran"
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
|
||||
echo
|
||||
info "Parsed summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo
|
||||
|
||||
if [[ -z "$logs" ]]; then
|
||||
ok "TEST 3 PASSED: No force-stop recovery logs on first launch."
|
||||
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 3 PASSED: NONE scenario logged with rescheduled=0 on first launch."
|
||||
elif [[ "$rescheduled" -gt 0 ]]; then
|
||||
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring."
|
||||
else
|
||||
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / FIRST_LAUNCH."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
# Allow selecting specific tests: e.g. `./test-phase2.sh 1 3`
|
||||
if [[ "$#" -gt 0 && ( "$1" == "-h" || "$1" == "--help" ) ]]; then
|
||||
echo "Usage: $0 [TEST_IDS...]"
|
||||
echo
|
||||
echo "If no TEST_IDS are given, all tests (1, 2, 3) will run."
|
||||
echo "Examples:"
|
||||
echo " $0 # run all tests"
|
||||
echo " $0 1 # run only TEST 1"
|
||||
echo " $0 2 3 # run only TEST 2 and TEST 3"
|
||||
return 0
|
||||
fi
|
||||
|
||||
SELECTED_TESTS=("$@")
|
||||
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "Phase 2 Testing Script – Force Stop Recovery"
|
||||
echo "========================================"
|
||||
echo
|
||||
echo "This script will guide you through Phase 2 tests."
|
||||
echo "You'll be prompted when UI interaction is needed."
|
||||
echo
|
||||
|
||||
pause
|
||||
|
||||
require_adb_device
|
||||
build_app
|
||||
install_app
|
||||
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
test1_force_stop_cleared_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
test2_force_stop_intact_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
test3_first_launch_no_schedules
|
||||
fi
|
||||
|
||||
section "Testing Complete"
|
||||
|
||||
echo "Test Results Summary (see logs above for details):"
|
||||
echo
|
||||
echo "TEST 1: Force Stop – Alarms Cleared"
|
||||
echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0"
|
||||
echo
|
||||
echo "TEST 2: Force Stop / Process Stop – Alarms Intact"
|
||||
echo " - Verify FORCE_STOP scenario is not incorrectly triggered when alarms are still present"
|
||||
echo
|
||||
echo "TEST 3: First Launch / No Schedules"
|
||||
echo " - Confirm that no force-stop recovery runs, or that NONE/FIRST_LAUNCH scenario is logged with rescheduled=0"
|
||||
echo
|
||||
|
||||
ok "Phase 2 testing script complete!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo " - Review logs above"
|
||||
echo " - Capture snippets into PHASE2-EMULATOR-TESTING.md"
|
||||
echo " - Update PHASE2-VERIFICATION.md and unified directive status matrix"
|
||||
echo
|
||||
}
|
||||
|
||||
main "$@"
|
||||
437
test-apps/android-test-app/test-phase3.sh
Executable file
437
test-apps/android-test-app/test-phase3.sh
Executable file
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ========================================
|
||||
# Phase 3 Testing Script – Boot Recovery
|
||||
# ========================================
|
||||
|
||||
# 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)
|
||||
BOOT_SCENARIO_VALUE="BOOT"
|
||||
NONE_SCENARIO_VALUE="NONE"
|
||||
|
||||
# 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
|
||||
# Looks for "Detected scenario: BOOT" or "Starting boot recovery" format
|
||||
if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
|
||||
echo "$BOOT_SCENARIO_VALUE"
|
||||
else
|
||||
# Use shared library function as fallback
|
||||
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
|
||||
echo "$scen"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 1 – Boot with Future Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test1_boot_future_alarms() {
|
||||
section "TEST 1: Boot with Future Alarms"
|
||||
|
||||
echo "Purpose: Verify alarms are recreated on boot when schedules have future run times."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & check plugin status"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
|
||||
|
||||
ui_prompt "Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before_count system_before
|
||||
before_count="$(get_plugin_alarm_count)"
|
||||
system_before="$(get_system_alarm_count)"
|
||||
info "Plugin alarms before reboot: $before_count (expected: 1)"
|
||||
info "System/other alarms: $system_before (for context)"
|
||||
|
||||
if [[ "$before_count" -eq 0 ]]; then
|
||||
warn "No plugin alarms found before reboot; TEST 1 may not be meaningful."
|
||||
elif [[ "$before_count" -eq 1 ]]; then
|
||||
ok "Single plugin alarm confirmed (one per day)"
|
||||
else
|
||||
warn "Found $before_count plugin alarms (expected: 1)"
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Reboot emulator"
|
||||
warn "The emulator will reboot now. This will take 30-60 seconds."
|
||||
pause
|
||||
reboot_emulator
|
||||
|
||||
substep "Step 4: Collect boot recovery logs"
|
||||
info "Collecting recovery logs from boot..."
|
||||
sleep 2 # Give recovery a moment to complete
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
substep "Step 5: Verify alarms were recreated"
|
||||
show_alarms
|
||||
local after_count system_after
|
||||
after_count="$(get_plugin_alarm_count)"
|
||||
system_after="$(get_system_alarm_count)"
|
||||
info "Plugin alarms after boot: $after_count (expected: 1)"
|
||||
info "System/other alarms: $system_after (for context)"
|
||||
|
||||
if [[ "$scenario" == "$BOOT_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 1 PASSED: Boot recovery detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
|
||||
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
|
||||
if [[ "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 1 PASSED: Boot recovery ran and alarms rescheduled (rescheduled=$rescheduled)."
|
||||
else
|
||||
warn "TEST 1: Boot recovery ran but rescheduled=0. Check implementation or logs."
|
||||
fi
|
||||
else
|
||||
warn "TEST 1: Boot recovery not clearly detected. Review logs and boot receiver implementation."
|
||||
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 2 – Boot with Past Alarms
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test2_boot_past_alarms() {
|
||||
section "TEST 2: Boot with Past Alarms"
|
||||
|
||||
echo "Purpose: Verify missed alarms are detected and next occurrence is scheduled on boot."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & ensure plugin configured"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
|
||||
|
||||
ui_prompt "Click 'Test Notification' to schedule a notification for 2 minutes in the future.\n\nAfter scheduling, we'll wait for the alarm time to pass, then reboot."
|
||||
|
||||
substep "Step 2: Wait for alarm time to pass"
|
||||
info "Waiting 3 minutes for scheduled alarm time to pass..."
|
||||
warn "You can manually advance system time if needed (requires root/emulator)"
|
||||
sleep 180 # Wait 3 minutes
|
||||
|
||||
substep "Step 3: Verify alarm time has passed"
|
||||
info "Alarm time should now be in the past"
|
||||
show_alarms
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 4: Reboot emulator"
|
||||
warn "The emulator will reboot now. This will take 30-60 seconds."
|
||||
pause
|
||||
reboot_emulator
|
||||
|
||||
substep "Step 5: Collect boot recovery logs"
|
||||
info "Collecting recovery logs from boot..."
|
||||
sleep 2
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
# For TEST 2, we need the FIRST entry (which has missed count) not the last
|
||||
# Boot recovery runs twice (LOCKED_BOOT_COMPLETED and BOOT_COMPLETED)
|
||||
# First run marks missed alarms, second run only reschedules (missed=0)
|
||||
missed="$(echo "$logs" | grep -E "missed=" | sed -E "s/.*missed=([0-9]+).*/\1/" | head -n1 || echo "0")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
error "Recovery reported errors>0 (errors=$errors)"
|
||||
fi
|
||||
|
||||
if [[ "$missed" -ge 1 && "$rescheduled" -ge 1 ]]; then
|
||||
ok "TEST 2 PASSED: Past alarms detected and next occurrence scheduled (missed=$missed, rescheduled=$rescheduled)."
|
||||
elif [[ "$missed" -ge 1 ]]; then
|
||||
warn "TEST 2: Past alarms detected (missed=$missed) but rescheduled=$rescheduled. Check reschedule logic."
|
||||
else
|
||||
warn "TEST 2: No missed alarms detected. Verify alarm time actually passed before reboot."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 3 – Boot with No Schedules
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test3_boot_no_schedules() {
|
||||
section "TEST 3: Boot with No Schedules"
|
||||
|
||||
echo "Purpose: Verify boot recovery handles empty database gracefully."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Uninstall app to clear DB/state"
|
||||
set +e
|
||||
$ADB_BIN uninstall "$APP_ID" >/dev/null 2>&1
|
||||
set -e
|
||||
ok "App uninstalled (state cleared)"
|
||||
|
||||
substep "Step 2: Reinstall app"
|
||||
if $ADB_BIN install -r "$APK_PATH"; then
|
||||
ok "App installed"
|
||||
else
|
||||
error "Reinstall failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Clearing logcat..."
|
||||
$ADB_BIN logcat -c
|
||||
ok "Logs cleared"
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Reboot emulator WITHOUT scheduling anything"
|
||||
warn "Do NOT schedule any notifications. The app should have no schedules in the database."
|
||||
warn "The emulator will reboot now. This will take 30-60 seconds."
|
||||
pause
|
||||
reboot_emulator
|
||||
|
||||
substep "Step 4: Collect boot recovery logs"
|
||||
info "Collecting recovery logs from boot..."
|
||||
sleep 2
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local scenario rescheduled missed
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " missed = ${missed}"
|
||||
echo
|
||||
|
||||
if [[ -z "$logs" ]]; then
|
||||
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)."
|
||||
return
|
||||
fi
|
||||
|
||||
if echo "$logs" | grep -qiE "No schedules found|No schedules present"; then
|
||||
ok "TEST 3 PASSED: Explicit 'No schedules found' message logged with no rescheduling."
|
||||
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
|
||||
ok "TEST 3 PASSED: NONE scenario detected with no rescheduling."
|
||||
elif [[ "$rescheduled" -gt 0 ]]; then
|
||||
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that boot recovery isn't misfiring."
|
||||
else
|
||||
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / NO_SCHEDULES."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# TEST 4 – Silent Boot Recovery (App Never Opened)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
test4_silent_boot_recovery() {
|
||||
section "TEST 4: Silent Boot Recovery (App Never Opened)"
|
||||
|
||||
echo "Purpose: Verify boot recovery occurs even when the app is never opened after reboot."
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 1: Launch app & ensure plugin configured"
|
||||
launch_app
|
||||
|
||||
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
|
||||
|
||||
ui_prompt "Click 'Test Notification' to schedule a notification for a few minutes in the future."
|
||||
|
||||
substep "Step 2: Verify alarms are scheduled"
|
||||
show_alarms
|
||||
local before_count system_before
|
||||
before_count="$(get_plugin_alarm_count)"
|
||||
system_before="$(get_system_alarm_count)"
|
||||
info "Plugin alarms before reboot: $before_count (expected: 1)"
|
||||
info "System/other alarms: $system_before (for context)"
|
||||
|
||||
if [[ "$before_count" -eq 0 ]]; then
|
||||
warn "No plugin alarms found; TEST 4 may not be meaningful."
|
||||
elif [[ "$before_count" -eq 1 ]]; then
|
||||
ok "Single plugin alarm confirmed (one per day)"
|
||||
else
|
||||
warn "Found $before_count plugin alarms (expected: 1)"
|
||||
fi
|
||||
|
||||
pause
|
||||
|
||||
substep "Step 3: Reboot emulator (DO NOT open app after reboot)"
|
||||
warn "IMPORTANT: After reboot, DO NOT open the app. Boot recovery should run silently."
|
||||
warn "The emulator will reboot now. This will take 30-60 seconds."
|
||||
pause
|
||||
reboot_emulator
|
||||
|
||||
substep "Step 4: Collect boot recovery logs (without opening app)"
|
||||
info "Collecting recovery logs from boot (app was NOT opened)..."
|
||||
sleep 2
|
||||
local logs
|
||||
logs="$(get_recovery_logs)"
|
||||
echo "$logs"
|
||||
|
||||
local missed rescheduled verified errors scenario
|
||||
missed="$(extract_field_from_logs "$logs" "missed")"
|
||||
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
|
||||
verified="$(extract_field_from_logs "$logs" "verified")"
|
||||
errors="$(extract_field_from_logs "$logs" "errors")"
|
||||
scenario="$(extract_scenario_from_logs "$logs")"
|
||||
|
||||
echo
|
||||
info "Parsed recovery summary:"
|
||||
echo " scenario = ${scenario:-<none>}"
|
||||
echo " missed = ${missed}"
|
||||
echo " rescheduled= ${rescheduled}"
|
||||
echo " verified = ${verified}"
|
||||
echo " errors = ${errors}"
|
||||
echo
|
||||
|
||||
substep "Step 5: Verify alarms were recreated (without opening app)"
|
||||
show_alarms
|
||||
local after_count system_after
|
||||
after_count="$(get_plugin_alarm_count)"
|
||||
system_after="$(get_system_alarm_count)"
|
||||
info "Plugin alarms after boot (app never opened): $after_count (expected: 1)"
|
||||
info "System/other alarms: $system_after (for context)"
|
||||
|
||||
if [[ "$after_count" -gt 0 && "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 4 PASSED: Boot recovery occurred silently and alarms were recreated (rescheduled=$rescheduled) without app launch."
|
||||
elif [[ "$rescheduled" -gt 0 ]]; then
|
||||
ok "TEST 4 PASSED: Boot recovery occurred silently (rescheduled=$rescheduled), but alarm count check unclear."
|
||||
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
|
||||
warn "TEST 4: Boot recovery ran but alarms may not have been recreated. Check logs and implementation."
|
||||
else
|
||||
warn "TEST 4: Boot recovery not detected. Verify boot receiver is registered and has BOOT_COMPLETED permission."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
# 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 Phase 3 tests."
|
||||
echo "You'll be prompted when UI interaction is needed."
|
||||
echo
|
||||
echo "⚠️ WARNING: This script will reboot the emulator multiple times."
|
||||
echo " Each reboot takes 30-60 seconds."
|
||||
echo
|
||||
|
||||
pause
|
||||
|
||||
require_adb_device
|
||||
build_app
|
||||
install_app
|
||||
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
test1_boot_future_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
test2_boot_past_alarms
|
||||
pause
|
||||
fi
|
||||
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
test3_boot_no_schedules
|
||||
pause
|
||||
fi
|
||||
|
||||
if should_run_test "4" SELECTED_TESTS; then
|
||||
test4_silent_boot_recovery
|
||||
fi
|
||||
|
||||
section "Testing Complete"
|
||||
|
||||
echo "Test Results Summary (see logs above for details):"
|
||||
echo
|
||||
echo "TEST 1: Boot with Future Alarms"
|
||||
echo " - Check logs for scenario=$BOOT_SCENARIO_VALUE and rescheduled>0"
|
||||
echo
|
||||
echo "TEST 2: Boot with Past Alarms"
|
||||
echo " - Check that missed>=1 and rescheduled>=1"
|
||||
echo
|
||||
echo "TEST 3: Boot with No Schedules"
|
||||
echo " - Check that no recovery runs, or NONE scenario is logged with rescheduled=0"
|
||||
echo
|
||||
echo "TEST 4: Silent Boot Recovery"
|
||||
echo " - Check that boot recovery occurred and alarms were recreated without app launch"
|
||||
echo
|
||||
|
||||
ok "Phase 3 testing script complete!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo " - Review logs above"
|
||||
echo " - Capture snippets into PHASE3-EMULATOR-TESTING.md"
|
||||
echo " - Update PHASE3-VERIFICATION.md and unified directive status matrix"
|
||||
echo
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -103,3 +103,91 @@ npm run build
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## ADB Commands for Android Testing
|
||||
|
||||
**Package Name**: `com.timesafari.dailynotification.test`
|
||||
|
||||
### Check Device Connection
|
||||
|
||||
```sh
|
||||
adb devices
|
||||
```
|
||||
|
||||
### Install App
|
||||
|
||||
Install the debug APK to a connected device or emulator:
|
||||
|
||||
```sh
|
||||
# From project root
|
||||
cd android
|
||||
./gradlew installDebug
|
||||
|
||||
# Or from test-apps/daily-notification-test directory
|
||||
cd android && ./gradlew installDebug
|
||||
```
|
||||
|
||||
### List Installed Packages
|
||||
|
||||
Check if the app is installed:
|
||||
|
||||
```sh
|
||||
# List all packages (filter for this app)
|
||||
adb shell pm list packages | grep timesafari
|
||||
|
||||
# List only this app's package
|
||||
adb shell pm list packages com.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
### Launch/Raise the App
|
||||
|
||||
Launch the app or bring it to foreground:
|
||||
|
||||
```sh
|
||||
# Launch the main activity
|
||||
adb shell am start -n com.timesafari.dailynotification.test/.MainActivity
|
||||
|
||||
# Launch with explicit intent
|
||||
adb shell am start -a android.intent.action.MAIN -n com.timesafari.dailynotification.test/.MainActivity
|
||||
```
|
||||
|
||||
### Uninstall App
|
||||
|
||||
Remove the app from the device:
|
||||
|
||||
```sh
|
||||
# Uninstall by package name
|
||||
adb uninstall com.timesafari.dailynotification.test
|
||||
|
||||
# Force uninstall (if regular uninstall fails)
|
||||
adb shell pm uninstall -k --user 0 com.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
### Additional Useful ADB Commands
|
||||
|
||||
**View App Logs**:
|
||||
```sh
|
||||
# Filter logs for this app
|
||||
adb logcat | grep -E "timesafari|DailyNotification|DNP"
|
||||
|
||||
# Clear logs before testing
|
||||
adb logcat -c
|
||||
```
|
||||
|
||||
**Check App Info**:
|
||||
```sh
|
||||
# Get app version and info
|
||||
adb shell dumpsys package com.timesafari.dailynotification.test | grep -A 5 "versionName\|versionCode"
|
||||
```
|
||||
|
||||
**Force Stop App**:
|
||||
```sh
|
||||
# Force stop the app (useful for testing recovery scenarios)
|
||||
adb shell am force-stop com.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
**Clear App Data**:
|
||||
```sh
|
||||
# Clear app data (resets to fresh install state)
|
||||
adb shell pm clear com.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
14
test-apps/daily-notification-test/package-lock.json
generated
14
test-apps/daily-notification-test/package-lock.json
generated
@@ -117,7 +117,6 @@
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -417,7 +416,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
@@ -636,7 +634,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
|
||||
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -2068,7 +2065,6 @@
|
||||
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
@@ -2667,7 +2663,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2916,7 +2911,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
@@ -3379,7 +3373,6 @@
|
||||
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3441,7 +3434,6 @@
|
||||
"integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -4301,7 +4293,6 @@
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
@@ -5730,7 +5721,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -5808,7 +5798,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -6042,7 +6031,6 @@
|
||||
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -6313,7 +6301,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6333,7 +6320,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
|
||||
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.22",
|
||||
"@vue/compiler-sfc": "3.5.22",
|
||||
|
||||
Reference in New Issue
Block a user