test(android): fix alarm counting logic and add screenshot capture
Fix alarm counting to correctly parse dumpsys output where app ID and action appear on different lines. Add screenshot capture for test diagnostics and create golden run documentation. Test Harness Improvements: - Fix get_plugin_alarm_count() to track app ID and action separately across alarm block lines (fixes false 0-count bug) - Add show_plugin_alarms_compact() to display complete alarm blocks - Add wait_for_stable_plugin_alarm_count() polling helper to reduce race condition false negatives - Add take_screenshot() and take_failure_screenshot() helpers for automatic test state capture - Integrate screenshots into TEST 0 at key checkpoints - Update TEST 0 messaging to handle race conditions gracefully - Add screenshots/ to .gitignore Documentation: - Create PHASE1_TEST0_GOLDEN.md with actual values from successful run - Document expected script output, UI state, dumpsys shape, and logcat patterns - Include pass/fail checklist for future test runs This fixes the issue where alarm counting always returned 0 because the AWK logic required app ID and action on the same line, but dumpsys output has them on separate lines (header line has app ID, tag line has action).
This commit is contained in:
@@ -23,6 +23,10 @@
|
||||
: "${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"
|
||||
@@ -262,46 +266,64 @@ 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
|
||||
local count app_id
|
||||
# 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"
|
||||
count="$($ADB_BIN shell dumpsys alarm 2>/dev/null | awk -v app="$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 = ""
|
||||
isPluginNotification = 0
|
||||
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 = ""
|
||||
isPluginNotification = 0
|
||||
if (match($0, /Alarm\{/)) {
|
||||
rest = substr($0, RSTART + RLENGTH)
|
||||
if (match(rest, /^[0-9a-f]+/)) {
|
||||
alarmId = substr(rest, RSTART, RLENGTH)
|
||||
}
|
||||
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 && isPluginNotification == 1 && alarmId != "") {
|
||||
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
|
||||
seen[alarmId] = 1
|
||||
}
|
||||
in_block = 0
|
||||
alarmId = ""
|
||||
isPluginNotification = 0
|
||||
hasAppLine = 0
|
||||
hasActionLine = 0
|
||||
}
|
||||
# Lines inside an alarm block
|
||||
in_block == 1 {
|
||||
if ($0 ~ app && $0 ~ /com\.timesafari\.daily\.NOTIFICATION/) {
|
||||
isPluginNotification = 1
|
||||
if ($0 ~ app) {
|
||||
hasAppLine = 1
|
||||
}
|
||||
if ($0 ~ action) {
|
||||
hasActionLine = 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (in_block == 1 && isPluginNotification == 1 && alarmId != "") {
|
||||
# Flush final block if it was a plugin notification
|
||||
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
|
||||
seen[alarmId] = 1
|
||||
}
|
||||
count = 0
|
||||
@@ -341,6 +363,154 @@ count_alarms() {
|
||||
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
|
||||
|
||||
230
test-apps/android-test-app/docs/PHASE1_TEST0_GOLDEN.md
Normal file
230
test-apps/android-test-app/docs/PHASE1_TEST0_GOLDEN.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# Phase 1 — TEST 0 Golden Run (Daily Rollover Verification)
|
||||
|
||||
**Last Updated:** 2025-12-04
|
||||
**Status:** ✅ PASS (Golden Baseline)
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -226,6 +226,9 @@ main() {
|
||||
launch_app
|
||||
ensure_plugin_configured
|
||||
|
||||
# Capture pre-schedule UI state
|
||||
take_screenshot "phase1_test0_daily_rollover" "before_scheduling"
|
||||
|
||||
INITIAL_COUNT=$(get_plugin_alarm_count)
|
||||
SYSTEM_COUNT=$(get_system_alarm_count)
|
||||
print_info "Current notification alarms: ${INITIAL_COUNT} (expected before scheduling: 0)"
|
||||
@@ -243,22 +246,30 @@ main() {
|
||||
- 1 notification alarm (AlarmManager) for the specified time
|
||||
- 1 prefetch job (WorkManager) for 2 minutes before that time"
|
||||
sleep 3 # Give alarm time to be registered in AlarmManager
|
||||
|
||||
# Capture post-schedule UI state
|
||||
take_screenshot "phase1_test0_daily_rollover" "after_scheduling"
|
||||
|
||||
POST_SCHEDULE_COUNT=$(get_plugin_alarm_count)
|
||||
|
||||
# Check alarm count after scheduling
|
||||
# Note: 0 alarms is likely a race condition (alarm may not be visible yet in dumpsys)
|
||||
# Note: This is a PRELIMINARY sanity check only - final verdict comes after rollover
|
||||
# 0 alarms is likely a race condition (alarm may not be visible yet in dumpsys)
|
||||
# Only treat >1 alarms as a real failure (duplicates)
|
||||
if [ "${POST_SCHEDULE_COUNT}" -eq "0" ] 2>/dev/null; then
|
||||
print_warn "⚠️ Found 0 plugin alarms right after scheduling."
|
||||
print_info "ℹ️ This is likely a race condition – treating as inconclusive, not a failure."
|
||||
print_info "ℹ️ The alarm may not be visible in dumpsys yet. We'll rely on the rollover check."
|
||||
print_info "ℹ️ Pre-schedule check not reliable; will rely on rollover assertions."
|
||||
print_info "ℹ️ The alarm may not be visible in dumpsys yet. Continuing to verify after rollover."
|
||||
elif [ "${POST_SCHEDULE_COUNT}" -eq "1" ] 2>/dev/null; then
|
||||
print_success "✅ Found 1 notification alarm (expected: 1) – immediate post-schedule check passed."
|
||||
print_success "✅ Found 1 notification alarm (expected: 1) – preliminary check passed."
|
||||
print_info "ℹ️ This is preliminary check only; final verdict after rollover."
|
||||
else
|
||||
# count > 1 - this is a real duplicate bug
|
||||
print_warn "⚠️ ⚠️ Found ${POST_SCHEDULE_COUNT} notification alarms (expected: 1) – DUPLICATES DETECTED right after scheduling!"
|
||||
print_warn "This indicates duplicate NOTIFICATION alarms were created (BUG DETECTED)"
|
||||
print_info "For debugging, run:"
|
||||
print_info "Showing alarm blocks for debugging:"
|
||||
show_plugin_alarms_compact
|
||||
print_info "For more details, run:"
|
||||
print_info " adb shell dumpsys alarm | grep -A 5 'com.timesafari.dailynotification' | sed -n '1,80p'"
|
||||
fi
|
||||
INITIAL_COUNT="${POST_SCHEDULE_COUNT}"
|
||||
@@ -316,12 +327,19 @@ main() {
|
||||
echo " - Prefetch job scheduled in WorkManager"
|
||||
echo ""
|
||||
echo "Expected log patterns:"
|
||||
echo " DNP-PLUGIN: Calculated next run time: cron=<time>"
|
||||
echo " DNP-SCHEDULE: Scheduling next daily alarm: ... source=ROLLOVER_ON_FIRE"
|
||||
echo " DNP-NOTIFY: Scheduling alarm: triggerTime=<tomorrow's time>"
|
||||
echo " DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ..."
|
||||
echo ""
|
||||
echo "When scheduling the test notification, expect:"
|
||||
echo " DNP-SCHEDULE: ... source=TEST_NOTIFICATION"
|
||||
echo ""
|
||||
echo "Alarm shape in dumpsys:"
|
||||
echo " RTC_WAKEUP, ALARM_CLOCK, tag=*walarm*:com.timesafari.daily.NOTIFICATION"
|
||||
echo ""
|
||||
echo "After notification fires, run:"
|
||||
echo " adb shell dumpsys alarm | grep -A 3 'com.timesafari.dailynotification'"
|
||||
echo " adb logcat -d | grep -E 'DNP-PLUGIN|DNP-NOTIFY' | tail -20"
|
||||
echo " adb logcat -d | grep -E 'DNP-SCHEDULE|DNP-NOTIFY' | tail -20"
|
||||
echo ""
|
||||
|
||||
wait_for_ui_action "After the notification fires (or you advance the clock),
|
||||
@@ -333,12 +351,16 @@ main() {
|
||||
|
||||
Press Enter when you've verified this (or to skip this test)."
|
||||
|
||||
POST_ROLLOVER_COUNT=$(get_plugin_alarm_count)
|
||||
print_info "Polling for stable alarm count (allowing up to ~10 seconds for Android to settle)..."
|
||||
POST_ROLLOVER_COUNT=$(wait_for_stable_plugin_alarm_count 5 2)
|
||||
SYSTEM_FINAL=$(get_system_alarm_count)
|
||||
print_info "Notification alarms after rollover: ${POST_ROLLOVER_COUNT} (expected: 1)"
|
||||
print_info "System/other alarms: ${SYSTEM_FINAL} (for context)"
|
||||
print_info "Note: Prefetch is scheduled via WorkManager (not AlarmManager), so it won't appear in alarm count"
|
||||
|
||||
# Capture post-rollover UI state
|
||||
take_screenshot "phase1_test0_daily_rollover" "after_rollover_check"
|
||||
|
||||
# After rollover, the state should be stable - this is the real assertion point
|
||||
if [ "${POST_ROLLOVER_COUNT}" -eq "1" ] 2>/dev/null; then
|
||||
print_success "✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow."
|
||||
@@ -348,15 +370,40 @@ main() {
|
||||
elif [ "${POST_ROLLOVER_COUNT}" -gt "1" ] 2>/dev/null; then
|
||||
print_warn "⚠️ ⚠️ TEST 0 FAILED: Daily rollover created ${POST_ROLLOVER_COUNT} NOTIFICATION alarms (duplicates)."
|
||||
print_warn "This indicates duplicate NOTIFICATION alarms were created (BUG DETECTED)"
|
||||
print_info "For debugging, run:"
|
||||
print_info " adb shell dumpsys alarm | grep -A 5 'com.timesafari.dailynotification' | sed -n '1,80p'"
|
||||
take_failure_screenshot "phase1_test0_daily_rollover" "duplicate_alarms"
|
||||
echo ""
|
||||
print_info "Showing alarm blocks for debugging:"
|
||||
show_plugin_alarms_compact
|
||||
echo ""
|
||||
print_info "Check scheduling logs for multiple DNP-SCHEDULE entries:"
|
||||
print_info " adb logcat -d | grep 'DNP-SCHEDULE\|DNP-NOTIFY' | tail -20"
|
||||
echo ""
|
||||
print_info "Possible causes:"
|
||||
echo " - Multiple scheduling paths racing (rollover + recovery)"
|
||||
echo " - Old alarm wasn't canceled before scheduling new one"
|
||||
echo " - Different scheduleIds used for same trigger time"
|
||||
echo " - Idempotence check not working correctly"
|
||||
else
|
||||
# count is 0 or invalid - rollover may have failed
|
||||
print_warn "⚠️ TEST 0 INCONCLUSIVE: No NOTIFICATION alarm found after rollover – check logs/dumpsys manually."
|
||||
print_warn "⚠️ TEST 0 INCONCLUSIVE: No NOTIFICATION alarm found after rollover (expected 1, got 0)"
|
||||
print_warn "This indicates the rollover did not schedule tomorrow's alarm correctly"
|
||||
take_failure_screenshot "phase1_test0_daily_rollover" "no_alarm_after_rollover"
|
||||
echo ""
|
||||
print_info "Showing alarm blocks for debugging:"
|
||||
show_plugin_alarms_compact
|
||||
echo ""
|
||||
print_info "Expected log pattern after fire:"
|
||||
echo " DNP-SCHEDULE: Scheduling next daily alarm: ... source=ROLLOVER_ON_FIRE"
|
||||
echo " DNP-NOTIFY: Scheduling alarm: triggerTime=<tomorrow>"
|
||||
echo " DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ..."
|
||||
echo ""
|
||||
print_info "Check logs for rollover scheduling errors:"
|
||||
print_info " adb logcat -d | grep 'DNP-SCHEDULE\|DNP-NOTIFY' | tail -20"
|
||||
print_info " adb shell dumpsys alarm | grep -A 3 'com.timesafari.dailynotification'"
|
||||
echo ""
|
||||
print_info "Manual verification:"
|
||||
echo " If dumpsys shows a single RTC_WAKEUP alarm with"
|
||||
echo " tag=*walarm*:com.timesafari.daily.NOTIFICATION for tomorrow,"
|
||||
echo " treat TEST 0 as manually PASSED and update the script expectations later."
|
||||
fi
|
||||
|
||||
wait_for_user
|
||||
|
||||
Reference in New Issue
Block a user