9740bc64c9
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
Continuous Deployment / Deploy to Production (push) Has been cancelled
Continuous Deployment / Rollback Deployment (push) Has been cancelled
Continuous Deployment / Post-deployment Monitoring (push) Has been cancelled
Continuous Deployment / Notify Deployment Status (push) Has been cancelled
Continuous Deployment / Deploy to Staging (push) Has been cancelled
Security Scanning / Security Report (push) Has been cancelled
374 lines
14 KiB
Bash
Executable File
374 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# QEMU Snapshot-Based Test Runner — ADR-061 Layer 8
|
|
#
|
|
# Uses QEMU VM snapshots to accelerate repeated test runs.
|
|
# Instead of rebooting and re-initializing for each test scenario,
|
|
# we snapshot the VM state after boot and after the first CSI frame,
|
|
# then restore from the snapshot for each individual test.
|
|
#
|
|
# This dramatically reduces per-test wall time from ~15s (full boot)
|
|
# to ~2s (snapshot restore + execution).
|
|
#
|
|
# Environment variables:
|
|
# QEMU_PATH - Path to qemu-system-xtensa (default: qemu-system-xtensa)
|
|
# QEMU_TIMEOUT - Per-test timeout in seconds (default: 10)
|
|
# FLASH_IMAGE - Path to merged flash image (default: build/qemu_flash.bin)
|
|
# SKIP_SNAPSHOT - Set to "1" to run without snapshots (baseline timing)
|
|
#
|
|
# Exit codes:
|
|
# 0 PASS — all checks passed
|
|
# 1 WARN — non-critical checks failed
|
|
# 2 FAIL — critical checks failed
|
|
# 3 FATAL — build error, crash, or infrastructure failure
|
|
|
|
# ── Help ──────────────────────────────────────────────────────────────
|
|
usage() {
|
|
cat <<'HELP'
|
|
Usage: qemu-snapshot-test.sh [OPTIONS]
|
|
|
|
Use QEMU VM snapshots to accelerate repeated test runs. Snapshots the VM
|
|
state after boot and after the first CSI frame, then restores from the
|
|
snapshot for each individual test (~2s vs ~15s per test).
|
|
|
|
Options:
|
|
-h, --help Show this help message and exit
|
|
|
|
Environment variables:
|
|
QEMU_PATH Path to qemu-system-xtensa (default: qemu-system-xtensa)
|
|
QEMU_TIMEOUT Per-test timeout in seconds (default: 10)
|
|
FLASH_IMAGE Path to merged flash image (default: build/qemu_flash.bin)
|
|
SKIP_SNAPSHOT Set to "1" to run without snapshots (baseline timing)
|
|
|
|
Examples:
|
|
./qemu-snapshot-test.sh
|
|
QEMU_TIMEOUT=20 ./qemu-snapshot-test.sh
|
|
FLASH_IMAGE=/path/to/image.bin ./qemu-snapshot-test.sh
|
|
|
|
Exit codes:
|
|
0 PASS — all checks passed
|
|
1 WARN — non-critical checks failed
|
|
2 FAIL — critical checks failed
|
|
3 FATAL — build error, crash, or infrastructure failure
|
|
HELP
|
|
exit 0
|
|
}
|
|
|
|
case "${1:-}" in -h|--help) usage ;; esac
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
FIRMWARE_DIR="$PROJECT_ROOT/firmware/esp32-csi-node"
|
|
BUILD_DIR="$FIRMWARE_DIR/build"
|
|
QEMU_BIN="${QEMU_PATH:-qemu-system-xtensa}"
|
|
FLASH_IMAGE="${FLASH_IMAGE:-$BUILD_DIR/qemu_flash.bin}"
|
|
TIMEOUT_SEC="${QEMU_TIMEOUT:-10}"
|
|
MONITOR_SOCK="$BUILD_DIR/qemu-monitor.sock"
|
|
LOG_DIR="$BUILD_DIR/snapshot-tests"
|
|
QEMU_PID=""
|
|
|
|
# Timing accumulators
|
|
SNAPSHOT_TOTAL_MS=0
|
|
BASELINE_TOTAL_MS=0
|
|
|
|
# Track test results: array of "test_name:exit_code"
|
|
declare -a TEST_RESULTS=()
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Cleanup
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
cleanup() {
|
|
echo ""
|
|
echo "[cleanup] Shutting down QEMU and removing socket..."
|
|
if [ -n "$QEMU_PID" ] && kill -0 "$QEMU_PID" 2>/dev/null; then
|
|
kill "$QEMU_PID" 2>/dev/null || true
|
|
wait "$QEMU_PID" 2>/dev/null || true
|
|
fi
|
|
rm -f "$MONITOR_SOCK"
|
|
echo "[cleanup] Done."
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Helpers
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
now_ms() {
|
|
# Millisecond timestamp (portable: Linux date +%s%N, macOS perl fallback)
|
|
local ns
|
|
ns=$(date +%s%N 2>/dev/null)
|
|
if [[ "$ns" =~ ^[0-9]+$ ]]; then
|
|
echo $(( ns / 1000000 ))
|
|
else
|
|
perl -MTime::HiRes=time -e 'printf "%d\n", time()*1000' 2>/dev/null || \
|
|
echo $(( $(date +%s) * 1000 ))
|
|
fi
|
|
}
|
|
|
|
monitor_cmd() {
|
|
# Send a command to QEMU monitor via socat and capture response
|
|
local cmd="$1"
|
|
local timeout="${2:-5}"
|
|
if ! command -v socat &>/dev/null; then
|
|
echo "ERROR: socat not found (required for QEMU monitor)" >&2
|
|
return 1
|
|
fi
|
|
echo "$cmd" | socat - "UNIX-CONNECT:$MONITOR_SOCK,connect-timeout=$timeout" 2>/dev/null
|
|
}
|
|
|
|
wait_for_pattern() {
|
|
# Wait until a pattern appears in the log file, or timeout
|
|
local log_file="$1"
|
|
local pattern="$2"
|
|
local timeout="$3"
|
|
local elapsed=0
|
|
while [ "$elapsed" -lt "$timeout" ]; do
|
|
if [ -f "$log_file" ] && grep -q "$pattern" "$log_file" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
elapsed=$((elapsed + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
start_qemu() {
|
|
# Launch QEMU in background with monitor socket
|
|
echo "[qemu] Launching QEMU with monitor socket..."
|
|
|
|
rm -f "$MONITOR_SOCK"
|
|
|
|
local qemu_args=(
|
|
-machine esp32s3
|
|
-nographic
|
|
-drive "file=$FLASH_IMAGE,if=mtd,format=raw"
|
|
-serial "file:$LOG_DIR/qemu_uart.log"
|
|
-no-reboot
|
|
-monitor "unix:$MONITOR_SOCK,server,nowait"
|
|
)
|
|
|
|
"$QEMU_BIN" "${qemu_args[@]}" &
|
|
QEMU_PID=$!
|
|
echo "[qemu] PID=$QEMU_PID"
|
|
|
|
# Wait for monitor socket to appear
|
|
local waited=0
|
|
while [ ! -S "$MONITOR_SOCK" ] && [ "$waited" -lt 10 ]; do
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
done
|
|
|
|
if [ ! -S "$MONITOR_SOCK" ]; then
|
|
echo "ERROR: QEMU monitor socket did not appear after 10s"
|
|
return 1
|
|
fi
|
|
|
|
# Verify QEMU is still running
|
|
if ! kill -0 "$QEMU_PID" 2>/dev/null; then
|
|
echo "ERROR: QEMU process exited prematurely"
|
|
return 1
|
|
fi
|
|
|
|
echo "[qemu] Monitor socket ready: $MONITOR_SOCK"
|
|
}
|
|
|
|
save_snapshot() {
|
|
local name="$1"
|
|
echo "[snapshot] Saving snapshot: $name"
|
|
monitor_cmd "savevm $name" 5
|
|
echo "[snapshot] Saved: $name"
|
|
}
|
|
|
|
restore_snapshot() {
|
|
local name="$1"
|
|
echo "[snapshot] Restoring snapshot: $name"
|
|
monitor_cmd "loadvm $name" 5
|
|
echo "[snapshot] Restored: $name"
|
|
}
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Pre-flight checks
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo "=== QEMU Snapshot Test Runner — ADR-061 Layer 8 ==="
|
|
echo "QEMU binary: $QEMU_BIN"
|
|
echo "Flash image: $FLASH_IMAGE"
|
|
echo "Timeout/test: ${TIMEOUT_SEC}s"
|
|
echo ""
|
|
|
|
if ! command -v "$QEMU_BIN" &>/dev/null; then
|
|
echo "ERROR: QEMU binary not found: $QEMU_BIN"
|
|
echo " Install: sudo apt install qemu-system-misc # Debian/Ubuntu"
|
|
echo " Install: brew install qemu # macOS"
|
|
echo " Or set QEMU_PATH to the qemu-system-xtensa binary."
|
|
exit 3
|
|
fi
|
|
|
|
if ! command -v qemu-img &>/dev/null; then
|
|
echo "ERROR: qemu-img not found (needed for snapshot disk management)."
|
|
echo " Install: sudo apt install qemu-utils # Debian/Ubuntu"
|
|
echo " Install: brew install qemu # macOS"
|
|
exit 3
|
|
fi
|
|
|
|
if ! command -v socat &>/dev/null; then
|
|
echo "ERROR: socat not found (needed for QEMU monitor communication)."
|
|
echo " Install: sudo apt install socat # Debian/Ubuntu"
|
|
echo " Install: brew install socat # macOS"
|
|
exit 3
|
|
fi
|
|
|
|
if [ ! -f "$FLASH_IMAGE" ]; then
|
|
echo "ERROR: Flash image not found: $FLASH_IMAGE"
|
|
echo "Run qemu-esp32s3-test.sh first to build the flash image."
|
|
exit 3
|
|
fi
|
|
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Phase 1: Boot and create snapshots
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo "── Phase 1: Boot and snapshot creation ──"
|
|
echo ""
|
|
|
|
# Clear any previous UART log
|
|
> "$LOG_DIR/qemu_uart.log"
|
|
|
|
start_qemu
|
|
|
|
# Wait for boot (look for boot indicators, max 5s)
|
|
echo "[boot] Waiting for firmware boot (up to 5s)..."
|
|
if wait_for_pattern "$LOG_DIR/qemu_uart.log" "app_main\|main_task\|ESP32-S3" 5; then
|
|
echo "[boot] Firmware booted successfully."
|
|
else
|
|
echo "[boot] No boot indicator found after 5s (continuing anyway)."
|
|
fi
|
|
|
|
# Save post-boot snapshot
|
|
save_snapshot "post_boot"
|
|
echo ""
|
|
|
|
# Wait for first mock CSI frame (additional 5s)
|
|
echo "[frame] Waiting for first CSI frame (up to 5s)..."
|
|
if wait_for_pattern "$LOG_DIR/qemu_uart.log" "frame\|CSI\|mock_csi\|iq_data\|subcarrier" 5; then
|
|
echo "[frame] First CSI frame detected."
|
|
else
|
|
echo "[frame] No frame indicator found after 5s (continuing anyway)."
|
|
fi
|
|
|
|
# Save post-first-frame snapshot
|
|
save_snapshot "post_first_frame"
|
|
echo ""
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Phase 2: Run tests from snapshot
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo "── Phase 2: Running tests from snapshot ──"
|
|
echo ""
|
|
|
|
TESTS=("test_presence" "test_fall" "test_multi_person")
|
|
MAX_EXIT=0
|
|
|
|
for test_name in "${TESTS[@]}"; do
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " Test: $test_name"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
test_log="$LOG_DIR/${test_name}.log"
|
|
t_start=$(now_ms)
|
|
|
|
# Restore to post_first_frame state
|
|
restore_snapshot "post_first_frame"
|
|
|
|
# Record current log length so we can extract only new lines
|
|
pre_lines=$(wc -l < "$LOG_DIR/qemu_uart.log" 2>/dev/null || echo 0)
|
|
|
|
# Let execution continue for TIMEOUT_SEC seconds
|
|
echo "[test] Running for ${TIMEOUT_SEC}s..."
|
|
sleep "$TIMEOUT_SEC"
|
|
|
|
# Capture only the new log lines produced during this test
|
|
tail -n +$((pre_lines + 1)) "$LOG_DIR/qemu_uart.log" > "$test_log"
|
|
|
|
t_end=$(now_ms)
|
|
elapsed_ms=$((t_end - t_start))
|
|
SNAPSHOT_TOTAL_MS=$((SNAPSHOT_TOTAL_MS + elapsed_ms))
|
|
|
|
echo "[test] Captured $(wc -l < "$test_log") lines in ${elapsed_ms}ms"
|
|
|
|
# Validate
|
|
echo "[test] Validating..."
|
|
test_exit=0
|
|
python3 "$SCRIPT_DIR/validate_qemu_output.py" "$test_log" || test_exit=$?
|
|
|
|
TEST_RESULTS+=("${test_name}:${test_exit}")
|
|
if [ "$test_exit" -gt "$MAX_EXIT" ]; then
|
|
MAX_EXIT=$test_exit
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Phase 3: Baseline timing (without snapshots) for comparison
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo "── Phase 3: Timing comparison ──"
|
|
echo ""
|
|
|
|
# Estimate baseline: full boot (5s) + frame wait (5s) + test run per test
|
|
BASELINE_PER_TEST=$((5 + 5 + TIMEOUT_SEC))
|
|
BASELINE_TOTAL_MS=$((BASELINE_PER_TEST * ${#TESTS[@]} * 1000))
|
|
SNAPSHOT_PER_TEST=$((SNAPSHOT_TOTAL_MS / ${#TESTS[@]}))
|
|
|
|
echo "Timing Summary:"
|
|
echo " Tests run: ${#TESTS[@]}"
|
|
echo " With snapshots:"
|
|
echo " Total wall time: ${SNAPSHOT_TOTAL_MS}ms"
|
|
echo " Per-test average: ${SNAPSHOT_PER_TEST}ms"
|
|
echo " Without snapshots (estimated):"
|
|
echo " Total wall time: ${BASELINE_TOTAL_MS}ms"
|
|
echo " Per-test average: $((BASELINE_PER_TEST * 1000))ms"
|
|
echo ""
|
|
|
|
if [ "$SNAPSHOT_TOTAL_MS" -gt 0 ] && [ "$BASELINE_TOTAL_MS" -gt 0 ]; then
|
|
SPEEDUP=$((BASELINE_TOTAL_MS * 100 / SNAPSHOT_TOTAL_MS))
|
|
echo " Speedup: ${SPEEDUP}% (${SPEEDUP}x/100)"
|
|
else
|
|
echo " Speedup: N/A (insufficient data)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Summary
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo "── Test Results Summary ──"
|
|
echo ""
|
|
PASS_COUNT=0
|
|
FAIL_COUNT=0
|
|
for result in "${TEST_RESULTS[@]}"; do
|
|
name="${result%%:*}"
|
|
code="${result##*:}"
|
|
if [ "$code" -le 1 ]; then
|
|
echo " [PASS] $name (exit=$code)"
|
|
PASS_COUNT=$((PASS_COUNT + 1))
|
|
else
|
|
echo " [FAIL] $name (exit=$code)"
|
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo " $PASS_COUNT passed, $FAIL_COUNT failed out of ${#TESTS[@]} tests"
|
|
echo ""
|
|
echo "=== Snapshot Test Complete (exit code: $MAX_EXIT) ==="
|
|
exit "$MAX_EXIT"
|