Pre-launch app from CI shell to escape XCTest sandbox

The XCTest runner is sandboxed, causing Process-spawned apps to
inherit sandbox restrictions. The CI step now:
1. Builds for testing first (separate step)
2. Launches display helper and app from the shell (non-sandboxed)
3. Waits for app diagnostics and render stats
4. Writes manifests for the test to find the pre-launched state
5. Runs test-without-building

The test detects the pre-launch manifest and skips its own app launch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
austinpower1258 2026-03-23 04:05:46 -07:00
parent 94c656fbbd
commit 0c4415ceba
2 changed files with 167 additions and 9 deletions

View file

@ -478,38 +478,173 @@ jobs:
sleep $((attempt * 5))
done
- name: Build for testing (display resolution)
run: |
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
-disableAutomaticPackageResolution \
-destination "platform=macOS" \
build-for-testing
- name: Run display resolution churn UI regression
run: |
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
HELPER_PATH="/tmp/create-virtual-display"
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"
TOKEN="$(uuidgen)"
DIAG_PATH="/tmp/cmux-ui-test-display-churn-${TOKEN}.json"
DISPLAY_READY="/tmp/cmux-ui-test-display-${TOKEN}.ready"
DISPLAY_ID_PATH="/tmp/cmux-ui-test-display-${TOKEN}.id"
DISPLAY_START="/tmp/cmux-ui-test-display-${TOKEN}.start"
DISPLAY_DONE="/tmp/cmux-ui-test-display-${TOKEN}.done"
HELPER_LOG="/tmp/cmux-ui-test-display-${TOKEN}-helper.log"
rm -f "$MANIFEST_PATH"
trap 'rm -f "$MANIFEST_PATH"' EXIT
cleanup() {
pkill -x "cmux DEV" 2>/dev/null || true
rm -f "$DIAG_PATH" "$DISPLAY_READY" "$DISPLAY_ID_PATH" "$DISPLAY_START" "$DISPLAY_DONE" "$HELPER_LOG"
rm -f /tmp/cmux-ui-test-prelaunch.json /tmp/cmux-ui-test-display-harness.json
}
trap cleanup EXIT
# Build display helper
clang -framework Foundation -framework CoreGraphics \
-o "$HELPER_PATH" scripts/create-virtual-display.m
cat >"$MANIFEST_PATH" <<EOF
{"helperBinaryPath":"$HELPER_PATH"}
EOF
# Find the app binary
APP_BINARY=$(find ~/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app/Contents/MacOS/cmux DEV" -print -quit 2>/dev/null || true)
if [ -z "$APP_BINARY" ]; then
echo "ERROR: App binary not found in DerivedData" >&2
exit 1
fi
echo "App binary: $APP_BINARY"
for attempt in 1 2; do
cleanup 2>/dev/null || true
# Launch display helper from shell (non-sandboxed)
"$HELPER_PATH" \
--modes "1920x1080,1728x1117,1600x900,1440x810" \
--ready-path "$DISPLAY_READY" \
--display-id-path "$DISPLAY_ID_PATH" \
--start-path "$DISPLAY_START" \
--done-path "$DISPLAY_DONE" \
--iterations 40 \
--interval-ms 40 \
> "$HELPER_LOG" 2>&1 &
HELPER_PID=$!
# Wait for display ready
echo "Waiting for virtual display..."
for i in $(seq 1 24); do
if [ -f "$DISPLAY_READY" ]; then break; fi
sleep 0.5
done
if [ ! -f "$DISPLAY_READY" ]; then
echo "ERROR: Virtual display not ready after 12s" >&2
cat "$HELPER_LOG" 2>/dev/null || true
continue
fi
DISPLAY_ID=$(cat "$DISPLAY_ID_PATH")
echo "Virtual display ready: ID=$DISPLAY_ID"
# Launch app from shell (non-sandboxed, outside XCTest sandbox)
CMUX_UI_TEST_MODE=1 \
CMUX_UI_TEST_DIAGNOSTICS_PATH="$DIAG_PATH" \
CMUX_UI_TEST_DISPLAY_RENDER_STATS=1 \
CMUX_UI_TEST_TARGET_DISPLAY_ID="$DISPLAY_ID" \
CMUX_TAG="ui-tests-display-resolution" \
"$APP_BINARY" > /tmp/cmux-ui-test-app.log 2>&1 &
APP_PID=$!
echo "App launched: PID=$APP_PID"
# Wait for app diagnostics
echo "Waiting for app diagnostics..."
APP_READY=false
for i in $(seq 1 30); do
if [ -f "$DIAG_PATH" ]; then
if python3 -c "import json; d=json.load(open('$DIAG_PATH')); assert d.get('pid')" 2>/dev/null; then
APP_READY=true
break
fi
fi
if ! kill -0 "$APP_PID" 2>/dev/null; then
echo "ERROR: App crashed during startup"
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -30 || true
break
fi
sleep 0.5
done
if [ "$APP_READY" != "true" ]; then
echo "Attempt $attempt: App not ready after 15s"
pkill -x "cmux DEV" 2>/dev/null || true
kill "$HELPER_PID" 2>/dev/null || true
if [ "$attempt" -eq 2 ]; then
echo "Display resolution UI regression failed after 2 attempts" >&2
echo "--- App log ---"
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -50 || true
echo "--- Helper log ---"
cat "$HELPER_LOG" 2>/dev/null | tail -20 || true
echo "--- Diagnostics ---"
cat "$DIAG_PATH" 2>/dev/null || echo "(not found)"
exit 1
fi
sleep 3
continue
fi
echo "App started. Diagnostics:"
cat "$DIAG_PATH"
# Wait for render stats (terminal surface initialization)
echo "Waiting for render stats..."
RENDER_READY=false
for i in $(seq 1 40); do
if python3 -c "import json; d=json.load(open('$DIAG_PATH')); assert d.get('renderStatsAvailable') == '1'" 2>/dev/null; then
RENDER_READY=true
echo "Render stats available after $((i / 2))s"
break
fi
sleep 0.5
done
if [ "$RENDER_READY" != "true" ]; then
echo "WARNING: Render stats not available after 20s. Diagnostics:"
cat "$DIAG_PATH" 2>/dev/null || true
echo "--- App log ---"
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -30 || true
fi
# Write manifests so test can find the pre-launched state
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"
cat >"$MANIFEST_PATH" <<MANIFEST_EOF
{"readyPath":"$DISPLAY_READY","displayIDPath":"$DISPLAY_ID_PATH","startPath":"$DISPLAY_START","donePath":"$DISPLAY_DONE","logPath":"$HELPER_LOG"}
MANIFEST_EOF
PRELAUNCH_PATH="/tmp/cmux-ui-test-prelaunch.json"
cat >"$PRELAUNCH_PATH" <<PRELAUNCH_EOF
{"diagnosticsPath":"$DIAG_PATH"}
PRELAUNCH_EOF
# Run test — app is already launched from shell
if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
-disableAutomaticPackageResolution \
-destination "platform=macOS" \
-only-testing:cmuxUITests/DisplayResolutionRegressionUITests \
test; then
test-without-building; then
exit 0
fi
pkill -x "cmux DEV" 2>/dev/null || true
kill "$HELPER_PID" 2>/dev/null || true
if [ "$attempt" -eq 2 ]; then
echo "Display resolution UI regression failed after 2 attempts" >&2
exit 1
fi
echo "Attempt $attempt failed, retrying..."
pkill -x "cmux DEV" 2>/dev/null || true
sleep 3
done