#!/bin/bash # Round01 overlay lifecycle QA. It verifies one visible overlay while the PoC is # open, then verifies that no Round01 overlay remains after auto-close. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" OUTPUT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # shellcheck source=build-output-paths.sh source "$SCRIPT_DIR/build-output-paths.sh" APP="$BUILD_CURRENT_APP" REPORT="$BUILD_REPORT_ROOT/round01-overlay-qa-report.json" OPEN_WAIT="${ALIGNER_ROUND1_OVERLAY_QA_OPEN_WAIT:-2.0}" HIDE_AFTER="${ALIGNER_ROUND1_OVERLAY_QA_HIDE_AFTER:-8.0}" QUIT_AFTER="${ALIGNER_ROUND1_OVERLAY_QA_QUIT_AFTER:-10.0}" AFTER_HIDE_WAIT="${ALIGNER_ROUND1_OVERLAY_QA_AFTER_HIDE_WAIT:-0.8}" QUIT_TIMEOUT="${ALIGNER_ROUND1_OVERLAY_QA_QUIT_TIMEOUT:-5.0}" fail() { echo "Round01 overlay QA failed: $*" >&2 exit 1 } python_float_check() { /usr/bin/python3 - "$@" <<'PY' import sys hide_after, quit_after, after_hide_wait = map(float, sys.argv[1:4]) if quit_after <= hide_after + after_hide_wait: print( f"QUIT_AFTER must be greater than HIDE_AFTER + AFTER_HIDE_WAIT: " f"{quit_after} <= {hide_after} + {after_hide_wait}", file=sys.stderr, ) sys.exit(1) PY } seconds_after_open_until_hidden() { /usr/bin/python3 - "$OPEN_WAIT" "$HIDE_AFTER" "$AFTER_HIDE_WAIT" <<'PY' import sys open_wait, hide_after, after_hide_wait = map(float, sys.argv[1:4]) print(max(0.0, hide_after + after_hide_wait - open_wait)) PY } aligner_pids_for_current_app() { ps -axo pid=,args= | while read -r pid command; do case "$command" in "$APP/Contents/MacOS/Aligner"*) echo "$pid" ;; esac done } stop_current_aligner() { for pid in $(aligner_pids_for_current_app); do kill "$pid" 2>/dev/null || true done for _ in {1..30}; do [ -z "$(aligner_pids_for_current_app)" ] && return sleep 0.1 done fail "current Aligner app did not exit before QA" } wait_for_app_exit() { local timeout="$1" /usr/bin/python3 - "$APP_PID" "$timeout" <<'PY' import os import signal import sys import time pid = int(sys.argv[1]) timeout = float(sys.argv[2]) deadline = time.monotonic() + timeout while time.monotonic() < deadline: stat = os.popen(f"ps -p {pid} -o stat=").read().strip() if not stat or "Z" in stat: sys.exit(0) try: os.kill(pid, 0) except ProcessLookupError: sys.exit(0) time.sleep(0.1) print(f"Aligner app did not quit within {timeout:.1f}s", file=sys.stderr) sys.exit(1) PY } python_float_check "$HIDE_AFTER" "$QUIT_AFTER" "$AFTER_HIDE_WAIT" POST_OPEN_HIDE_WAIT="$(seconds_after_open_until_hidden)" stop_current_aligner "$SCRIPT_DIR/package-app.sh" >&2 rm -f "$REPORT" "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-performance-poc \ --round01-poc-auto-cycle \ --round01-poc-auto-hide-after="$HIDE_AFTER" \ --round01-poc-quit-after="$QUIT_AFTER" \ --round01-poc-report="$REPORT" & APP_PID=$! cleanup() { if kill -0 "$APP_PID" 2>/dev/null; then kill "$APP_PID" 2>/dev/null || true wait "$APP_PID" 2>/dev/null || true fi } trap cleanup EXIT sleep "$OPEN_WAIT" swift "$SCRIPT_DIR/window-logic-qa.swift" \ --expect-round01-performance-poc \ --expect-single-aligner-overlay \ --expect-overlay-on-mouse-screen \ --expect-overlay-uses-screen-frame sleep "$POST_OPEN_HIDE_WAIT" swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-round01-performance-poc wait_for_app_exit "$QUIT_TIMEOUT" || fail "app did not quit after overlay hide" wait "$APP_PID" trap - EXIT swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-round01-performance-poc if [[ ! -f "$REPORT" ]]; then echo "Round01 overlay QA report was not created: $REPORT" >&2 exit 1 fi cat "$REPORT"