#!/bin/bash # Round01 repeated-session snapshot reset QA. It proves a second Quick Switch # session starts with an empty snapshot state instead of exposing stale data from # the previous session while the new snapshot is still loading. 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-quick-switch-session-reset-report.json" SNAPSHOT_DELAY="${ALIGNER_ROUND1_SESSION_RESET_SNAPSHOT_DELAY:-2.0}" START_WAIT="${ALIGNER_ROUND1_SESSION_RESET_START_WAIT:-2.5}" OPEN_WAIT="${ALIGNER_ROUND1_SESSION_RESET_OPEN_WAIT:-1.2}" REPORT_WAIT="${ALIGNER_ROUND1_SESSION_RESET_REPORT_WAIT:-8.0}" fail() { echo "Round01 session reset QA failed: $*" >&2 exit 1 } 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_no_quick_switch() { /usr/bin/python3 - "$SCRIPT_DIR" <<'PY' import subprocess import sys import time script_dir = sys.argv[1] deadline = time.monotonic() + 5.0 last_output = "" while time.monotonic() < deadline: result = subprocess.run( ["swift", f"{script_dir}/window-logic-qa.swift", "--expect-no-quick-switch"], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) last_output = result.stdout if result.returncode == 0: print(last_output, end="") sys.exit(0) time.sleep(0.2) print(last_output, end="") sys.exit(1) PY } wait_for_loaded_report() { /usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY' import json import sys import time path = sys.argv[1] timeout = float(sys.argv[2]) deadline = time.monotonic() + timeout while time.monotonic() < deadline: try: with open(path, "r", encoding="utf-8") as file: report = json.load(file) if report.get("snapshotLoaded") is True: print(json.dumps(report, indent=2, ensure_ascii=False)) sys.exit(0) except FileNotFoundError: pass except json.JSONDecodeError: pass time.sleep(0.2) print(f"snapshot report did not become loaded within {timeout:.1f}s", file=sys.stderr) sys.exit(1) PY } assert_report_reset() { /usr/bin/python3 - "$REPORT" <<'PY' import json import sys path = sys.argv[1] with open(path, "r", encoding="utf-8") as file: report = json.load(file) if report.get("snapshotLoaded") is not False: print("second session report must start with snapshotLoaded=false", file=sys.stderr) print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr) sys.exit(1) if report.get("windowCount") != 0 or report.get("appCount") != 0: print("second session report must not expose previous snapshot counts", file=sys.stderr) print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr) sys.exit(1) print(json.dumps(report, indent=2, ensure_ascii=False)) PY } stop_current_aligner "$SCRIPT_DIR/package-app.sh" >&2 rm -f "$REPORT" "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-quick-switch-report="$REPORT" \ --round01-snapshot-load-delay="$SNAPSHOT_DELAY" & 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 "$START_WAIT" swift "$SCRIPT_DIR/post-round1-key.swift" --option-tab sleep "$OPEN_WAIT" swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch --expect-single-aligner-overlay wait_for_loaded_report swift "$SCRIPT_DIR/post-round1-key.swift" --escape wait_for_no_quick_switch swift "$SCRIPT_DIR/post-round1-key.swift" --option-tab sleep "$OPEN_WAIT" swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch --expect-single-aligner-overlay assert_report_reset cleanup trap - EXIT swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch