#!/bin/bash
|
# Round01 dual-screen placement QA. It does not rebuild/package; it verifies
|
# the current internal Aligner.app so candidate DMG hashes stay stable.
|
|
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-dual-screen-placement-report.json"
|
WORK_DIR="${ALIGNER_ROUND1_DUAL_SCREEN_WORK_DIR:-$(mktemp -d /tmp/aligner-round01-dual-screen-placement.XXXXXX)}"
|
ALIGNER_LOG="$WORK_DIR/aligner.log"
|
START_WAIT="${ALIGNER_ROUND1_DUAL_SCREEN_START_WAIT:-1.6}"
|
OPEN_WAIT="${ALIGNER_ROUND1_DUAL_SCREEN_OPEN_WAIT:-0.8}"
|
CLOSE_WAIT="${ALIGNER_ROUND1_DUAL_SCREEN_CLOSE_WAIT:-0.8}"
|
RAPID_RETRIGGER_COUNT="${ALIGNER_ROUND1_DUAL_SCREEN_RAPID_RETRIGGER_COUNT:-5}"
|
RAPID_RETRIGGER_WAIT="${ALIGNER_ROUND1_DUAL_SCREEN_RAPID_RETRIGGER_WAIT:-0.18}"
|
APP_PID=""
|
|
fail() {
|
echo "Round01 dual-screen placement QA failed: $*" >&2
|
exit 1
|
}
|
|
screen_count() {
|
swift -e 'import AppKit; print(NSScreen.screens.count)'
|
}
|
|
post_key_with_system_events() {
|
local shortcut="$1"
|
case "$shortcut" in
|
option-tab)
|
osascript -e 'tell application "System Events" to key code 48 using {option down}'
|
;;
|
escape)
|
osascript -e 'tell application "System Events" to key code 53'
|
;;
|
*)
|
fail "unsupported System Events shortcut: $shortcut"
|
;;
|
esac
|
}
|
|
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() {
|
local timeout="$1"
|
/usr/bin/python3 - "$SCRIPT_DIR" "$timeout" <<'PY'
|
import subprocess
|
import sys
|
import time
|
|
script_dir = sys.argv[1]
|
timeout = float(sys.argv[2])
|
deadline = time.monotonic() + timeout
|
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[-6000:], end="")
|
sys.exit(0)
|
time.sleep(0.2)
|
|
print(last_output[-6000:], end="")
|
sys.exit(1)
|
PY
|
}
|
|
run_window_logic_check() {
|
swift "$SCRIPT_DIR/window-logic-qa.swift" "$@" 2>&1 | head -c 6000
|
local status="${PIPESTATUS[0]}"
|
return "$status"
|
}
|
|
cleanup() {
|
post_key_with_system_events escape 2>/dev/null || true
|
if [ -n "${APP_PID:-}" ] && kill -0 "$APP_PID" 2>/dev/null; then
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
fi
|
stop_current_aligner 2>/dev/null || true
|
}
|
|
[ -x "$APP/Contents/MacOS/Aligner" ] || fail "missing built app: $APP"
|
mkdir -p "$WORK_DIR"
|
|
SCREENS="$(screen_count)"
|
if [ "$SCREENS" -lt 2 ]; then
|
fail "expected at least 2 screens, got $SCREENS"
|
fi
|
|
stop_current_aligner
|
rm -f "$REPORT"
|
|
"$APP/Contents/MacOS/Aligner" \
|
--round0-skip-permissions \
|
--round01-quick-switch-report="$REPORT" >"$ALIGNER_LOG" 2>&1 &
|
APP_PID=$!
|
|
trap cleanup EXIT
|
sleep "$START_WAIT"
|
|
for index in $(seq 0 $((SCREENS - 1))); do
|
echo "==> screen-$index"
|
swift "$SCRIPT_DIR/round1-move-mouse-to-screen-center.swift" "$index" 2>&1 | head -c 6000
|
|
post_key_with_system_events option-tab
|
sleep "$OPEN_WAIT"
|
|
run_window_logic_check \
|
--expect-quick-switch \
|
--expect-single-aligner-overlay \
|
--expect-overlay-on-mouse-screen \
|
--expect-overlay-uses-screen-frame
|
|
next_index=$(( (index + 1) % SCREENS ))
|
if [ "$next_index" -ne "$index" ]; then
|
echo "==> visible-retrigger-to-screen-$next_index"
|
swift "$SCRIPT_DIR/round1-move-mouse-to-screen-center.swift" "$next_index" 2>&1 | head -c 6000
|
|
post_key_with_system_events option-tab
|
sleep "$OPEN_WAIT"
|
|
run_window_logic_check \
|
--expect-quick-switch \
|
--expect-single-aligner-overlay \
|
--expect-overlay-on-mouse-screen \
|
--expect-overlay-uses-screen-frame
|
|
if [ "$index" -eq 0 ]; then
|
echo "==> rapid-visible-retrigger-$RAPID_RETRIGGER_COUNT"
|
for count in $(seq 1 "$RAPID_RETRIGGER_COUNT"); do
|
target_index=$(( count % SCREENS ))
|
swift "$SCRIPT_DIR/round1-move-mouse-to-screen-center.swift" "$target_index" 2>&1 | head -c 6000
|
post_key_with_system_events option-tab
|
sleep "$RAPID_RETRIGGER_WAIT"
|
|
run_window_logic_check \
|
--expect-quick-switch \
|
--expect-single-aligner-overlay \
|
--expect-overlay-on-mouse-screen \
|
--expect-overlay-uses-screen-frame
|
done
|
fi
|
fi
|
|
post_key_with_system_events escape
|
sleep "$CLOSE_WAIT"
|
wait_for_no_quick_switch 5.0
|
done
|
|
if kill -0 "$APP_PID" 2>/dev/null; then
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
fi
|
stop_current_aligner
|
trap - EXIT
|
|
echo "Round01 dual-screen placement QA passed"
|
echo "Screens covered: $SCREENS"
|
echo "Log directory: $WORK_DIR"
|