539 lines
21 KiB
Bash
Executable File
539 lines
21 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# run-emulator-tests.sh — Run all emulator/simulator UI tests for PeriodVault
|
|
# Usage: ./scripts/run-emulator-tests.sh [android|ios|all]
|
|
# Logs to build/emulator-tests.log; script reads the log to detect adb errors (e.g. multiple devices).
|
|
#
|
|
# iOS watchdog env controls:
|
|
# IOS_HEARTBEAT_SECONDS (default: 30)
|
|
# IOS_STARTUP_PROGRESS_TIMEOUT_SECONDS (default: 900)
|
|
# IOS_TEST_STALL_TIMEOUT_SECONDS (default: 480)
|
|
# IOS_UNRESPONSIVE_STALL_TIMEOUT_SECONDS(default: 120)
|
|
# IOS_HARD_TIMEOUT_SECONDS (default: 10800)
|
|
# IOS_ACTIVE_CPU_THRESHOLD (default: 1.0)
|
|
set -euo pipefail
|
|
|
|
PLATFORM="${1:-all}"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# shellcheck source=scripts/lib.sh
|
|
source "$SCRIPT_DIR/lib.sh"
|
|
ensure_log_file "emulator-tests.log"
|
|
|
|
# Start Android emulator headless for test runs (no GUI window needed)
|
|
export EMULATOR_HEADLESS=1
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
ANDROID_PASS=0
|
|
IOS_PASS=0
|
|
ANDROID_FAIL=0
|
|
IOS_FAIL=0
|
|
|
|
run_android() {
|
|
echo -e "${YELLOW}=== Android Emulator Tests ===${NC}"
|
|
|
|
if ! ensure_android_emulator; then
|
|
echo -e "${RED}ERROR: Could not start or connect to Android emulator. See $LOG_FILE${NC}"
|
|
ANDROID_FAIL=1
|
|
return 1
|
|
fi
|
|
|
|
# Disable animations for stable UI tests
|
|
run_and_log "adb_disable_animations" adb shell "settings put global window_animation_scale 0; settings put global transition_animation_scale 0; settings put global animator_duration_scale 0" || true
|
|
|
|
# Pre-flight: verify emulator is responsive via adb shell
|
|
echo "Verifying Android emulator is responsive..."
|
|
if ! adb shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
|
|
echo -e "${RED}ERROR: Android emulator not responsive (sys.boot_completed != 1). Aborting.${NC}"
|
|
ANDROID_FAIL=1
|
|
return 1
|
|
fi
|
|
echo "Android emulator is responsive."
|
|
|
|
# Uninstall the app to ensure a clean database for tests
|
|
echo "Cleaning app data..."
|
|
adb uninstall periodvault.androidApp 2>/dev/null || true
|
|
adb uninstall periodvault.androidApp.test 2>/dev/null || true
|
|
|
|
echo "Running Android instrumented tests..."
|
|
local GRADLE_PID
|
|
local GRADLE_EXIT=0
|
|
local TOTAL_ANDROID_TESTS=0
|
|
TOTAL_ANDROID_TESTS=$(find androidApp/src/androidTest -name '*.kt' -type f -exec grep -hE '@Test' {} + 2>/dev/null | wc -l | tr -d ' ')
|
|
if [[ -z "$TOTAL_ANDROID_TESTS" ]]; then
|
|
TOTAL_ANDROID_TESTS=0
|
|
fi
|
|
./gradlew androidApp:connectedDebugAndroidTest 2>&1 &
|
|
GRADLE_PID=$!
|
|
|
|
# Progress/liveness watchdog:
|
|
# - emits heartbeat every 30s with completed Android test cases and emulator health
|
|
# - kills early only if emulator is unresponsive and test progress is stalled for 10m
|
|
# - retains a generous hard timeout as last-resort safety net
|
|
local HEARTBEAT_SECONDS=30
|
|
local UNRESPONSIVE_STALL_TIMEOUT_SECONDS=600
|
|
local HARD_TIMEOUT_SECONDS=7200 # 2 hours
|
|
(
|
|
local start_ts now_ts elapsed
|
|
local last_progress_ts
|
|
local completed=0
|
|
local last_completed=0
|
|
local stale_seconds=0
|
|
local emu_health=""
|
|
|
|
start_ts=$(date +%s)
|
|
last_progress_ts=$start_ts
|
|
|
|
while kill -0 $GRADLE_PID 2>/dev/null; do
|
|
sleep "$HEARTBEAT_SECONDS"
|
|
now_ts=$(date +%s)
|
|
elapsed=$((now_ts - start_ts))
|
|
|
|
completed=$(find androidApp/build/outputs/androidTest-results/connected -name '*.xml' -type f -exec grep -ho "<testcase " {} + 2>/dev/null | wc -l | tr -d ' ')
|
|
if [[ -z "$completed" ]]; then
|
|
completed=0
|
|
fi
|
|
|
|
if [[ "$completed" -gt "$last_completed" ]]; then
|
|
last_progress_ts=$now_ts
|
|
last_completed=$completed
|
|
fi
|
|
|
|
if adb shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
|
|
emu_health="responsive"
|
|
else
|
|
emu_health="UNRESPONSIVE"
|
|
fi
|
|
|
|
stale_seconds=$((now_ts - last_progress_ts))
|
|
local elapsed_mm elapsed_ss
|
|
elapsed_mm=$((elapsed / 60))
|
|
elapsed_ss=$((elapsed % 60))
|
|
|
|
if [[ "$TOTAL_ANDROID_TESTS" -gt 0 ]]; then
|
|
echo "Android progress: ${completed}/${TOTAL_ANDROID_TESTS} tests complete | elapsed ${elapsed_mm}m${elapsed_ss}s | emulator ${emu_health}"
|
|
else
|
|
echo "Android progress: ${completed} tests complete | elapsed ${elapsed_mm}m${elapsed_ss}s | emulator ${emu_health}"
|
|
fi
|
|
|
|
if [[ "$elapsed" -ge "$HARD_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing Gradle (PID $GRADLE_PID) after hard timeout ${HARD_TIMEOUT_SECONDS}s"
|
|
kill $GRADLE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $GRADLE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
|
|
if [[ "$emu_health" == "UNRESPONSIVE" ]] && [[ "$stale_seconds" -ge "$UNRESPONSIVE_STALL_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing Gradle (PID $GRADLE_PID) - emulator unresponsive and no progress for ${stale_seconds}s"
|
|
kill $GRADLE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $GRADLE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
done
|
|
) &
|
|
local WATCHDOG_PID=$!
|
|
wait $GRADLE_PID 2>/dev/null || GRADLE_EXIT=$?
|
|
kill $WATCHDOG_PID 2>/dev/null || true
|
|
wait $WATCHDOG_PID 2>/dev/null || true
|
|
|
|
if [[ $GRADLE_EXIT -eq 137 ]] || [[ $GRADLE_EXIT -eq 143 ]]; then
|
|
echo -e "${RED}Android emulator tests terminated by watchdog${NC}"
|
|
ANDROID_FAIL=1
|
|
run_and_log "adb_restore_animations" adb shell "settings put global window_animation_scale 1; settings put global transition_animation_scale 1; settings put global animator_duration_scale 1" || true
|
|
return 1
|
|
elif [[ $GRADLE_EXIT -eq 0 ]]; then
|
|
echo -e "${GREEN}Android emulator tests PASSED${NC}"
|
|
ANDROID_PASS=1
|
|
# Emit runtime evidence for CI tracking
|
|
local android_duration_s=""
|
|
local android_test_count=""
|
|
if [[ -f androidApp/build/reports/androidTests/connected/debug/index.html ]]; then
|
|
android_test_count="$(grep -o '<div class="counter">[0-9]*</div>' androidApp/build/reports/androidTests/connected/debug/index.html | head -1 | grep -o '[0-9]*' || echo "")"
|
|
android_duration_s="$(grep -o '<div class="counter">[0-9a-z.]*s</div>' androidApp/build/reports/androidTests/connected/debug/index.html | head -1 | grep -o '[0-9.]*' || echo "")"
|
|
fi
|
|
echo "RUNTIME_EVIDENCE: {\"suite\": \"android_ui\", \"tests\": ${android_test_count:-0}, \"duration\": \"${android_duration_s:-unknown}s\", \"timestamp\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\"}"
|
|
else
|
|
echo -e "${RED}Android emulator tests FAILED${NC}"
|
|
ANDROID_FAIL=1
|
|
echo "Test reports: androidApp/build/reports/androidTests/connected/debug/"
|
|
run_and_log "adb_restore_animations" adb shell "settings put global window_animation_scale 1; settings put global transition_animation_scale 1; settings put global animator_duration_scale 1" || true
|
|
return 1
|
|
fi
|
|
|
|
# Re-enable animations
|
|
run_and_log "adb_restore_animations" adb shell "settings put global window_animation_scale 1; settings put global transition_animation_scale 1; settings put global animator_duration_scale 1" || true
|
|
}
|
|
|
|
run_ios() {
|
|
echo -e "${YELLOW}=== iOS Simulator Tests ===${NC}"
|
|
|
|
# Find an available simulator
|
|
local SIM_ID
|
|
SIM_ID=$(xcrun simctl list devices available -j 2>/dev/null | python3 -c "
|
|
import json, sys
|
|
data = json.load(sys.stdin)
|
|
for runtime, devices in data.get('devices', {}).items():
|
|
if 'iOS' in runtime:
|
|
for d in devices:
|
|
if d.get('isAvailable'):
|
|
print(d['udid'])
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
" 2>/dev/null) || true
|
|
|
|
if [[ -z "$SIM_ID" ]]; then
|
|
echo -e "${RED}ERROR: No available iOS simulator found.${NC}"
|
|
IOS_FAIL=1
|
|
return 1
|
|
fi
|
|
|
|
local SIM_NAME
|
|
SIM_NAME=$(xcrun simctl list devices available | grep "$SIM_ID" | sed 's/ (.*//' | xargs)
|
|
echo "Using simulator: $SIM_NAME ($SIM_ID)"
|
|
|
|
# Boot simulator if needed
|
|
xcrun simctl boot "$SIM_ID" 2>/dev/null || true
|
|
|
|
# Health check: verify simulator is actually responsive (not just "Booted" in simctl)
|
|
echo "Verifying simulator is responsive..."
|
|
local HEALTH_OK=false
|
|
for i in 1 2 3 4 5; do
|
|
if xcrun simctl spawn "$SIM_ID" launchctl print system >/dev/null 2>&1; then
|
|
HEALTH_OK=true
|
|
break
|
|
fi
|
|
echo " Attempt $i/5: simulator not responsive, waiting 5s..."
|
|
sleep 5
|
|
done
|
|
if [[ "$HEALTH_OK" != "true" ]]; then
|
|
echo -e "${RED}ERROR: Simulator $SIM_NAME ($SIM_ID) reports Booted but is not responsive.${NC}"
|
|
echo "Attempting full restart..."
|
|
xcrun simctl shutdown "$SIM_ID" 2>/dev/null || true
|
|
sleep 3
|
|
xcrun simctl boot "$SIM_ID" 2>/dev/null || true
|
|
sleep 10
|
|
if ! xcrun simctl spawn "$SIM_ID" launchctl print system >/dev/null 2>&1; then
|
|
echo -e "${RED}ERROR: Simulator still unresponsive after restart. Aborting.${NC}"
|
|
IOS_FAIL=1
|
|
return 1
|
|
fi
|
|
echo "Simulator recovered after restart."
|
|
fi
|
|
echo "Simulator is responsive."
|
|
|
|
# Generate Xcode project if needed
|
|
if [[ ! -f iosApp/iosApp.xcodeproj/project.pbxproj ]]; then
|
|
echo "Generating Xcode project..."
|
|
(cd iosApp && xcodegen generate)
|
|
fi
|
|
|
|
# --- Phase 1: Build (synchronous, fail-fast) ---
|
|
echo "Building iOS UI tests..."
|
|
local BUILD_DIR
|
|
BUILD_DIR=$(mktemp -d)
|
|
local BUILD_LOG
|
|
BUILD_LOG=$(mktemp)
|
|
local BUILD_START
|
|
BUILD_START=$(date +%s)
|
|
|
|
xcodebuild build-for-testing \
|
|
-project iosApp/iosApp.xcodeproj \
|
|
-scheme iosApp \
|
|
-destination "platform=iOS Simulator,id=$SIM_ID" \
|
|
-derivedDataPath "$BUILD_DIR" \
|
|
> "$BUILD_LOG" 2>&1
|
|
|
|
local BUILD_EXIT=$?
|
|
local BUILD_END
|
|
BUILD_END=$(date +%s)
|
|
echo "iOS build phase: $((BUILD_END - BUILD_START))s (exit=$BUILD_EXIT)"
|
|
|
|
if [[ $BUILD_EXIT -ne 0 ]]; then
|
|
echo -e "${RED}BUILD FAILED — last 30 lines:${NC}"
|
|
tail -30 "$BUILD_LOG"
|
|
rm -f "$BUILD_LOG"
|
|
rm -rf "$BUILD_DIR"
|
|
IOS_FAIL=1
|
|
return 1
|
|
fi
|
|
rm -f "$BUILD_LOG"
|
|
|
|
# Disable animations for stable, faster UI tests
|
|
echo "Disabling simulator animations..."
|
|
xcrun simctl spawn "$SIM_ID" defaults write com.apple.Accessibility ReduceMotionEnabled -bool YES 2>/dev/null || true
|
|
|
|
# Uninstall the app to ensure a clean database for tests
|
|
echo "Cleaning app data..."
|
|
xcrun simctl uninstall "$SIM_ID" com.periodvault.app 2>/dev/null || true
|
|
|
|
# --- Phase 2: Test (background with watchdog, parallel execution) ---
|
|
echo "Running iOS UI tests (parallel enabled)..."
|
|
local TEST_EXIT=0
|
|
local TEST_LOG
|
|
TEST_LOG=$(mktemp)
|
|
local RESULT_BUNDLE_DIR
|
|
RESULT_BUNDLE_DIR=$(mktemp -d)
|
|
local RESULT_BUNDLE_PATH="$RESULT_BUNDLE_DIR/ios-ui-tests.xcresult"
|
|
local TOTAL_IOS_TESTS=0
|
|
TOTAL_IOS_TESTS=$(find iosApp/iosAppUITests -name '*.swift' -print0 2>/dev/null | xargs -0 grep -hE '^[[:space:]]*func[[:space:]]+test' 2>/dev/null | wc -l | tr -d ' ')
|
|
if [[ -z "$TOTAL_IOS_TESTS" ]]; then
|
|
TOTAL_IOS_TESTS=0
|
|
fi
|
|
local TEST_START
|
|
TEST_START=$(date +%s)
|
|
|
|
xcodebuild test-without-building \
|
|
-project iosApp/iosApp.xcodeproj \
|
|
-scheme iosApp \
|
|
-destination "platform=iOS Simulator,id=$SIM_ID" \
|
|
-only-testing:iosAppUITests \
|
|
-derivedDataPath "$BUILD_DIR" \
|
|
-resultBundlePath "$RESULT_BUNDLE_PATH" \
|
|
-parallel-testing-enabled YES \
|
|
> "$TEST_LOG" 2>&1 &
|
|
local XCODE_PID=$!
|
|
|
|
# Progress/liveness watchdog:
|
|
# - emits heartbeat with completed test count and simulator health
|
|
# - fails fast when CoreSimulatorService is unhealthy
|
|
# - treats test completion, xcodebuild CPU, and log growth as activity
|
|
# - fails when startup/test activity stalls beyond configured thresholds
|
|
# - keeps a hard cap as a final safety net
|
|
local HEARTBEAT_SECONDS="${IOS_HEARTBEAT_SECONDS:-30}"
|
|
local STARTUP_PROGRESS_TIMEOUT_SECONDS="${IOS_STARTUP_PROGRESS_TIMEOUT_SECONDS:-900}"
|
|
local TEST_STALL_TIMEOUT_SECONDS="${IOS_TEST_STALL_TIMEOUT_SECONDS:-480}"
|
|
local UNRESPONSIVE_STALL_TIMEOUT_SECONDS="${IOS_UNRESPONSIVE_STALL_TIMEOUT_SECONDS:-120}"
|
|
local HARD_TIMEOUT_SECONDS="${IOS_HARD_TIMEOUT_SECONDS:-10800}" # 3 hours
|
|
local ACTIVE_CPU_THRESHOLD="${IOS_ACTIVE_CPU_THRESHOLD:-1.0}"
|
|
|
|
echo "iOS watchdog: heartbeat=${HEARTBEAT_SECONDS}s startup_timeout=${STARTUP_PROGRESS_TIMEOUT_SECONDS}s test_stall_timeout=${TEST_STALL_TIMEOUT_SECONDS}s unresponsive_timeout=${UNRESPONSIVE_STALL_TIMEOUT_SECONDS}s hard_timeout=${HARD_TIMEOUT_SECONDS}s cpu_active_threshold=${ACTIVE_CPU_THRESHOLD}%"
|
|
(
|
|
local start_ts now_ts elapsed
|
|
local last_test_progress_ts
|
|
local last_activity_ts
|
|
local completed=0
|
|
local last_completed=0
|
|
local stale_seconds=0
|
|
local sim_health=""
|
|
local first_test_seen=false
|
|
local simctl_health_output=""
|
|
local log_size=0
|
|
local last_log_size=0
|
|
local xcode_cpu="0.0"
|
|
local xcode_cpu_raw=""
|
|
|
|
start_ts=$(date +%s)
|
|
last_test_progress_ts=$start_ts
|
|
last_activity_ts=$start_ts
|
|
|
|
while kill -0 $XCODE_PID 2>/dev/null; do
|
|
sleep "$HEARTBEAT_SECONDS"
|
|
now_ts=$(date +%s)
|
|
elapsed=$((now_ts - start_ts))
|
|
|
|
# Keep watchdog alive before first completed test appears; do not fail on zero matches.
|
|
completed=$(grep -E -c "Test [Cc]ase .* (passed|failed)" "$TEST_LOG" 2>/dev/null || true)
|
|
if [[ -z "$completed" ]]; then
|
|
completed=0
|
|
fi
|
|
|
|
if [[ "$completed" -gt "$last_completed" ]]; then
|
|
last_test_progress_ts=$now_ts
|
|
last_activity_ts=$now_ts
|
|
last_completed=$completed
|
|
first_test_seen=true
|
|
fi
|
|
|
|
# xcodebuild output growth indicates ongoing work even when a test has not completed yet.
|
|
log_size=$(wc -c < "$TEST_LOG" 2>/dev/null || echo 0)
|
|
if [[ -n "$log_size" ]] && [[ "$log_size" -gt "$last_log_size" ]]; then
|
|
last_log_size=$log_size
|
|
last_activity_ts=$now_ts
|
|
fi
|
|
|
|
# CPU usage provides another liveness signal during long-running UI tests.
|
|
xcode_cpu_raw=$(ps -p "$XCODE_PID" -o %cpu= 2>/dev/null | tr -d ' ' || true)
|
|
if [[ -n "$xcode_cpu_raw" ]]; then
|
|
xcode_cpu="$xcode_cpu_raw"
|
|
else
|
|
xcode_cpu="0.0"
|
|
fi
|
|
if awk "BEGIN { exit !($xcode_cpu >= $ACTIVE_CPU_THRESHOLD) }"; then
|
|
last_activity_ts=$now_ts
|
|
fi
|
|
|
|
if simctl_health_output=$(xcrun simctl spawn "$SIM_ID" launchctl print system 2>&1); then
|
|
sim_health="responsive"
|
|
else
|
|
sim_health="UNRESPONSIVE"
|
|
|
|
# Fail fast when the simulator service itself is down. Waiting longer does not recover this state.
|
|
if echo "$simctl_health_output" | grep -Eiq "CoreSimulatorService connection became invalid|not connected to CoreSimulatorService|Unable to locate device set|Connection refused|simdiskimaged.*(crashed|not responding)|Unable to discover any Simulator runtimes"; then
|
|
echo "WATCHDOG: CoreSimulatorService unhealthy; killing xcodebuild (PID $XCODE_PID) immediately"
|
|
echo "$simctl_health_output" | head -5 | sed 's/^/ simctl: /'
|
|
kill $XCODE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $XCODE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
fi
|
|
|
|
stale_seconds=$((now_ts - last_activity_ts))
|
|
local elapsed_mm elapsed_ss
|
|
elapsed_mm=$((elapsed / 60))
|
|
elapsed_ss=$((elapsed % 60))
|
|
|
|
if [[ "$TOTAL_IOS_TESTS" -gt 0 ]]; then
|
|
echo "iOS progress: ${completed}/${TOTAL_IOS_TESTS} tests complete | elapsed ${elapsed_mm}m${elapsed_ss}s | simulator ${sim_health} | xcodebuild cpu ${xcode_cpu}%"
|
|
else
|
|
echo "iOS progress: ${completed} tests complete | elapsed ${elapsed_mm}m${elapsed_ss}s | simulator ${sim_health} | xcodebuild cpu ${xcode_cpu}%"
|
|
fi
|
|
|
|
if [[ "$elapsed" -ge "$HARD_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing xcodebuild (PID $XCODE_PID) after hard timeout ${HARD_TIMEOUT_SECONDS}s"
|
|
kill $XCODE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $XCODE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
|
|
if [[ "$first_test_seen" != "true" ]] && [[ "$elapsed" -ge "$STARTUP_PROGRESS_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing xcodebuild (PID $XCODE_PID) - no completed iOS test observed within startup timeout (${STARTUP_PROGRESS_TIMEOUT_SECONDS}s)"
|
|
kill $XCODE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $XCODE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
|
|
if [[ "$first_test_seen" == "true" ]] && [[ "$stale_seconds" -ge "$TEST_STALL_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing xcodebuild (PID $XCODE_PID) - no iOS test activity for ${stale_seconds}s"
|
|
kill $XCODE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $XCODE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
|
|
if [[ "$sim_health" == "UNRESPONSIVE" ]] && [[ "$stale_seconds" -ge "$UNRESPONSIVE_STALL_TIMEOUT_SECONDS" ]]; then
|
|
echo "WATCHDOG: killing xcodebuild (PID $XCODE_PID) - simulator unresponsive and no test activity for ${stale_seconds}s"
|
|
kill $XCODE_PID 2>/dev/null || true
|
|
sleep 5
|
|
kill -9 $XCODE_PID 2>/dev/null || true
|
|
break
|
|
fi
|
|
done
|
|
) &
|
|
local WATCHDOG_PID=$!
|
|
|
|
wait $XCODE_PID 2>/dev/null || TEST_EXIT=$?
|
|
kill $WATCHDOG_PID 2>/dev/null || true
|
|
wait $WATCHDOG_PID 2>/dev/null || true
|
|
|
|
local TEST_END
|
|
TEST_END=$(date +%s)
|
|
echo "iOS test phase: $((TEST_END - TEST_START))s (exit=$TEST_EXIT)"
|
|
echo "iOS total (build+test): $((TEST_END - BUILD_START))s"
|
|
|
|
# Show test summary (passed/failed counts and any failures)
|
|
echo "--- Test Results ---"
|
|
grep -E "Test [Cc]ase .* (passed|failed)" "$TEST_LOG" || true
|
|
echo ""
|
|
echo "--- Failures ---"
|
|
grep -E "(FAIL|error:|\*\* TEST FAILED)" "$TEST_LOG" || echo " (none)"
|
|
echo ""
|
|
echo "--- Last 20 lines ---"
|
|
tail -20 "$TEST_LOG"
|
|
rm -f "$TEST_LOG"
|
|
|
|
if [[ $TEST_EXIT -eq 0 ]]; then
|
|
local SKIP_ALLOWLIST="${IOS_SKIPPED_TESTS_ALLOWLIST:-audit/ios-skipped-tests-allowlist.txt}"
|
|
if ! bash "$SCRIPT_DIR/validate-ios-skipped-tests.sh" "$RESULT_BUNDLE_PATH" "$SKIP_ALLOWLIST"; then
|
|
echo -e "${RED}iOS skipped-test gate FAILED${NC}"
|
|
TEST_EXIT=1
|
|
fi
|
|
fi
|
|
|
|
rm -rf "$RESULT_BUNDLE_DIR"
|
|
rm -rf "$BUILD_DIR"
|
|
|
|
# Re-enable animations
|
|
xcrun simctl spawn "$SIM_ID" defaults write com.apple.Accessibility ReduceMotionEnabled -bool NO 2>/dev/null || true
|
|
|
|
if [[ $TEST_EXIT -eq 137 ]] || [[ $TEST_EXIT -eq 143 ]]; then
|
|
echo -e "${RED}iOS simulator tests terminated by watchdog${NC}"
|
|
IOS_FAIL=1
|
|
return 1
|
|
elif [[ $TEST_EXIT -eq 0 ]]; then
|
|
echo -e "${GREEN}iOS simulator tests PASSED${NC}"
|
|
IOS_PASS=1
|
|
# Emit runtime evidence for CI tracking
|
|
local ios_test_count=""
|
|
ios_test_count="$TOTAL_IOS_TESTS"
|
|
local ios_elapsed_s=""
|
|
ios_elapsed_s="$(($(date +%s) - $(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" +%s 2>/dev/null || echo 0)))"
|
|
echo "RUNTIME_EVIDENCE: {\"suite\": \"ios_ui\", \"tests\": ${ios_test_count:-0}, \"timestamp\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\"}"
|
|
else
|
|
echo -e "${RED}iOS simulator tests FAILED${NC}"
|
|
IOS_FAIL=1
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
echo "================================================"
|
|
echo " PeriodVault Emulator/Simulator Test Runner"
|
|
echo "================================================"
|
|
echo ""
|
|
|
|
case "$PLATFORM" in
|
|
android)
|
|
run_android
|
|
;;
|
|
ios)
|
|
run_ios
|
|
;;
|
|
all)
|
|
run_android || true
|
|
echo ""
|
|
run_ios || true
|
|
;;
|
|
*)
|
|
echo "Usage: $0 [android|ios|all]"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "================================================"
|
|
echo " Results Summary"
|
|
echo "================================================"
|
|
if [[ "$PLATFORM" == "all" || "$PLATFORM" == "android" ]]; then
|
|
if [[ $ANDROID_PASS -eq 1 ]]; then
|
|
echo -e " Android: ${GREEN}PASSED${NC}"
|
|
elif [[ $ANDROID_FAIL -eq 1 ]]; then
|
|
echo -e " Android: ${RED}FAILED${NC}"
|
|
else
|
|
echo -e " Android: ${YELLOW}SKIPPED${NC}"
|
|
fi
|
|
fi
|
|
if [[ "$PLATFORM" == "all" || "$PLATFORM" == "ios" ]]; then
|
|
if [[ $IOS_PASS -eq 1 ]]; then
|
|
echo -e " iOS: ${GREEN}PASSED${NC}"
|
|
elif [[ $IOS_FAIL -eq 1 ]]; then
|
|
echo -e " iOS: ${RED}FAILED${NC}"
|
|
else
|
|
echo -e " iOS: ${YELLOW}SKIPPED${NC}"
|
|
fi
|
|
fi
|
|
echo "================================================"
|
|
|
|
# Exit with failure if any platform failed
|
|
if [[ $ANDROID_FAIL -eq 1 ]] || [[ $IOS_FAIL -eq 1 ]]; then
|
|
exit 1
|
|
fi
|