#!/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 }