Skip to content

Commit 94c59af

Browse files
committed
Merge remote-tracking branch 'origin/main' into task-browser-up-down-arrow-keys-merge
# Conflicts: # cmuxUITests/BrowserPaneNavigationKeybindUITests.swift
2 parents 010edb8 + b5fb304 commit 94c59af

76 files changed

Lines changed: 11863 additions & 839 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-ghosttykit.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
- main
77
pull_request:
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
12+
913
jobs:
1014
build-ghosttykit:
1115
# Never run WarpBuild jobs for fork pull requests (avoid billing on external PRs).

.github/workflows/ci-macos-compat.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ jobs:
130130
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \
131131
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
132132
-disableAutomaticPackageResolution \
133-
-destination "platform=macOS" test 2>&1
133+
-destination "platform=macOS" \
134+
-skip-testing:cmuxTests/AppDelegateShortcutRoutingTests/testCmdWClosesWindowWhenClosingLastSurfaceInLastWorkspace \
135+
test 2>&1
134136
}
135137
136138
set +e

.github/workflows/ci.yml

Lines changed: 240 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
- main
77
pull_request:
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
12+
913
jobs:
1014
workflow-guard-tests:
1115
runs-on: ubuntu-latest
@@ -74,7 +78,7 @@ jobs:
7478
# Never run WarpBuild jobs for fork pull requests (avoid billing on external PRs).
7579
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
7680
runs-on: warp-macos-15-arm64-6x
77-
timeout-minutes: 20
81+
timeout-minutes: 30
7882
steps:
7983
- name: Checkout
8084
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
@@ -171,14 +175,17 @@ jobs:
171175
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \
172176
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
173177
-disableAutomaticPackageResolution \
174-
-destination "platform=macOS" test 2>&1
178+
-destination "platform=macOS" \
179+
-skip-testing:cmuxTests/AppDelegateShortcutRoutingTests/testCmdWClosesWindowWhenClosingLastSurfaceInLastWorkspace \
180+
test 2>&1
175181
}
176182
177-
# xcodebuild exits 65 even for expected failures (XCTExpectFailure).
178-
# Capture output and fail only if there are unexpected failures.
183+
# Stream output via tee so CI logs are visible in real time, while still
184+
# capturing for post-run analysis of expected vs unexpected failures.
179185
set +e
180-
OUTPUT=$(run_unit_tests)
181-
EXIT_CODE=$?
186+
run_unit_tests | tee /tmp/test-output.txt
187+
EXIT_CODE=${PIPESTATUS[0]}
188+
OUTPUT=$(cat /tmp/test-output.txt)
182189
set -e
183190
184191
# SwiftPM binary artifact resolution can occasionally fail on ephemeral
@@ -190,12 +197,12 @@ jobs:
190197
mkdir -p ~/Library/Caches/org.swift.swiftpm
191198
rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-*
192199
set +e
193-
OUTPUT=$(run_unit_tests)
194-
EXIT_CODE=$?
200+
run_unit_tests | tee /tmp/test-output.txt
201+
EXIT_CODE=${PIPESTATUS[0]}
202+
OUTPUT=$(cat /tmp/test-output.txt)
195203
set -e
196204
fi
197205
198-
echo "$OUTPUT"
199206
if [ "$EXIT_CODE" -ne 0 ]; then
200207
SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1)
201208
if echo "$SUMMARY" | grep -q "(0 unexpected)"; then
@@ -231,9 +238,9 @@ jobs:
231238
232239
tests-build-and-lag:
233240
# Build the full cmux scheme and run the lag regression on WarpBuild.
234-
# XCUITests cannot run on WarpBuild (Virtualization.framework limitation:
235-
# XCUIApplication stuck "Running Background", 62s activation timeout per
236-
# test). Interactive UI tests run via test-e2e.yml on GitHub-hosted runners.
241+
# Keep lag validation separate from UI regressions so functional UI failures
242+
# and performance regressions stay isolated. Broader interactive UI suites
243+
# still run via test-e2e.yml on GitHub-hosted runners.
237244
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
238245
runs-on: warp-macos-15-arm64-6x
239246
timeout-minutes: 20
@@ -343,6 +350,7 @@ jobs:
343350
VDISPLAY_PID=$!
344351
echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV"
345352
sleep 3
353+
kill -0 "$VDISPLAY_PID"
346354
347355
- name: Run workspace churn typing-lag regression
348356
run: |
@@ -386,7 +394,16 @@ jobs:
386394
CMUX_LAG_KEY_EVENTS=180 \
387395
python3 tests/test_workspace_churn_up_arrow_lag.py
388396
389-
ui-display-resolution-regression:
397+
- name: Cleanup virtual display
398+
if: always()
399+
run: |
400+
set -euo pipefail
401+
if [ -n "${VDISPLAY_PID:-}" ]; then
402+
kill "$VDISPLAY_PID" >/dev/null 2>&1 || true
403+
fi
404+
rm -f /tmp/create-virtual-display
405+
406+
ui-regressions:
390407
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
391408
runs-on: warp-macos-15-arm64-6x
392409
timeout-minutes: 25
@@ -438,8 +455,8 @@ jobs:
438455
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
439456
with:
440457
path: .ci-source-packages
441-
key: spm-ui-display-resolution-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
442-
restore-keys: spm-ui-display-resolution-
458+
key: spm-ui-regressions-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
459+
restore-keys: spm-ui-regressions-
443460

444461
- name: Resolve Swift packages
445462
run: |
@@ -461,26 +478,225 @@ jobs:
461478
sleep $((attempt * 5))
462479
done
463480
464-
- name: Run display resolution churn UI regression
481+
- name: Build for testing (display resolution)
465482
run: |
466483
set -euo pipefail
467484
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
485+
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
486+
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
487+
-disableAutomaticPackageResolution \
488+
-destination "platform=macOS" \
489+
build-for-testing
490+
491+
- name: Create persistent virtual display
492+
run: |
493+
set -euo pipefail
468494
HELPER_PATH="/tmp/create-virtual-display"
469-
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"
495+
clang -framework Foundation -framework CoreGraphics \
496+
-o "$HELPER_PATH" scripts/create-virtual-display.m
497+
498+
VDISPLAY_READY="/tmp/cmux-vdisplay-persistent.ready"
499+
VDISPLAY_ID_PATH="/tmp/cmux-vdisplay-persistent.id"
500+
rm -f "$VDISPLAY_READY" "$VDISPLAY_ID_PATH"
501+
502+
"$HELPER_PATH" \
503+
--modes "1920x1080" \
504+
--ready-path "$VDISPLAY_READY" \
505+
--display-id-path "$VDISPLAY_ID_PATH" \
506+
> /tmp/cmux-vdisplay-persistent.log 2>&1 &
507+
echo "VDISPLAY_PERSISTENT_PID=$!" >> "$GITHUB_ENV"
508+
509+
echo "Waiting for persistent virtual display..."
510+
for i in $(seq 1 24); do
511+
if [ -f "$VDISPLAY_READY" ]; then break; fi
512+
sleep 0.5
513+
done
514+
if [ ! -f "$VDISPLAY_READY" ]; then
515+
echo "ERROR: Persistent virtual display not ready after 12s" >&2
516+
cat /tmp/cmux-vdisplay-persistent.log 2>/dev/null || true
517+
exit 1
518+
fi
519+
echo "Persistent virtual display ready: ID=$(cat "$VDISPLAY_ID_PATH")"
470520
471-
rm -f "$MANIFEST_PATH"
472-
trap 'rm -f "$MANIFEST_PATH"' EXIT
521+
- name: Run display resolution churn UI regression
522+
run: |
523+
set -euo pipefail
524+
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
525+
HELPER_PATH="/tmp/create-virtual-display"
526+
TOKEN="$(uuidgen)"
527+
DIAG_PATH="/tmp/cmux-ui-test-display-churn-${TOKEN}.json"
528+
DISPLAY_READY="/tmp/cmux-ui-test-display-${TOKEN}.ready"
529+
DISPLAY_ID_PATH="/tmp/cmux-ui-test-display-${TOKEN}.id"
530+
DISPLAY_START="/tmp/cmux-ui-test-display-${TOKEN}.start"
531+
DISPLAY_DONE="/tmp/cmux-ui-test-display-${TOKEN}.done"
532+
HELPER_LOG="/tmp/cmux-ui-test-display-${TOKEN}-helper.log"
533+
534+
cleanup() {
535+
pkill -x "cmux DEV" 2>/dev/null || true
536+
rm -f "$DIAG_PATH" "$DISPLAY_READY" "$DISPLAY_ID_PATH" "$DISPLAY_START" "$DISPLAY_DONE" "$HELPER_LOG"
537+
rm -f /tmp/cmux-ui-test-prelaunch.json /tmp/cmux-ui-test-display-harness.json
538+
}
539+
trap cleanup EXIT
473540
541+
# Build display helper
474542
clang -framework Foundation -framework CoreGraphics \
475543
-o "$HELPER_PATH" scripts/create-virtual-display.m
476544
477-
cat >"$MANIFEST_PATH" <<EOF
478-
{"helperBinaryPath":"$HELPER_PATH"}
479-
EOF
545+
# Find the app binary
546+
APP_BINARY=$(find ~/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app/Contents/MacOS/cmux DEV" -print -quit 2>/dev/null || true)
547+
if [ -z "$APP_BINARY" ]; then
548+
echo "ERROR: App binary not found in DerivedData" >&2
549+
exit 1
550+
fi
551+
echo "App binary: $APP_BINARY"
552+
553+
for attempt in 1 2; do
554+
cleanup 2>/dev/null || true
555+
556+
# Launch display helper from shell (non-sandboxed).
557+
# Use --start-delay-ms instead of --start-path because the XCTest
558+
# runner is sandboxed and can't write to /tmp/ for the start signal.
559+
# 10s delay gives the test time to capture baseline render stats.
560+
"$HELPER_PATH" \
561+
--modes "1920x1080,1728x1117,1600x900,1440x810" \
562+
--ready-path "$DISPLAY_READY" \
563+
--display-id-path "$DISPLAY_ID_PATH" \
564+
--done-path "$DISPLAY_DONE" \
565+
--iterations 40 \
566+
--interval-ms 40 \
567+
--start-delay-ms 10000 \
568+
> "$HELPER_LOG" 2>&1 &
569+
HELPER_PID=$!
570+
571+
# Wait for display ready
572+
echo "Waiting for virtual display..."
573+
for i in $(seq 1 24); do
574+
if [ -f "$DISPLAY_READY" ]; then break; fi
575+
sleep 0.5
576+
done
577+
if [ ! -f "$DISPLAY_READY" ]; then
578+
echo "ERROR: Virtual display not ready after 12s" >&2
579+
cat "$HELPER_LOG" 2>/dev/null || true
580+
continue
581+
fi
582+
DISPLAY_ID=$(cat "$DISPLAY_ID_PATH")
583+
echo "Virtual display ready: ID=$DISPLAY_ID"
584+
585+
# Launch app from shell (non-sandboxed, outside XCTest sandbox)
586+
CMUX_UI_TEST_MODE=1 \
587+
CMUX_UI_TEST_DIAGNOSTICS_PATH="$DIAG_PATH" \
588+
CMUX_UI_TEST_DISPLAY_RENDER_STATS=1 \
589+
CMUX_UI_TEST_TARGET_DISPLAY_ID="$DISPLAY_ID" \
590+
CMUX_TAG="ui-tests-display-resolution" \
591+
"$APP_BINARY" > /tmp/cmux-ui-test-app.log 2>&1 &
592+
APP_PID=$!
593+
echo "App launched: PID=$APP_PID"
594+
595+
# Wait for app diagnostics
596+
echo "Waiting for app diagnostics..."
597+
APP_READY=false
598+
for i in $(seq 1 30); do
599+
if [ -f "$DIAG_PATH" ]; then
600+
if python3 -c "import json; d=json.load(open('$DIAG_PATH')); assert d.get('pid')" 2>/dev/null; then
601+
APP_READY=true
602+
break
603+
fi
604+
fi
605+
if ! kill -0 "$APP_PID" 2>/dev/null; then
606+
echo "ERROR: App crashed during startup"
607+
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -30 || true
608+
break
609+
fi
610+
sleep 0.5
611+
done
612+
613+
if [ "$APP_READY" != "true" ]; then
614+
echo "Attempt $attempt: App not ready after 15s"
615+
pkill -x "cmux DEV" 2>/dev/null || true
616+
kill "$HELPER_PID" 2>/dev/null || true
617+
if [ "$attempt" -eq 2 ]; then
618+
echo "Display resolution UI regression failed after 2 attempts" >&2
619+
echo "--- App log ---"
620+
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -50 || true
621+
echo "--- Helper log ---"
622+
cat "$HELPER_LOG" 2>/dev/null | tail -20 || true
623+
echo "--- Diagnostics ---"
624+
cat "$DIAG_PATH" 2>/dev/null || echo "(not found)"
625+
exit 1
626+
fi
627+
sleep 3
628+
continue
629+
fi
630+
631+
echo "App started. Diagnostics:"
632+
cat "$DIAG_PATH"
633+
634+
# Wait for render stats (terminal surface initialization)
635+
echo "Waiting for render stats..."
636+
RENDER_READY=false
637+
for i in $(seq 1 40); do
638+
if python3 -c "import json; d=json.load(open('$DIAG_PATH')); assert d.get('renderStatsAvailable') == '1'" 2>/dev/null; then
639+
RENDER_READY=true
640+
echo "Render stats available after $((i / 2))s"
641+
break
642+
fi
643+
sleep 0.5
644+
done
645+
if [ "$RENDER_READY" != "true" ]; then
646+
echo "WARNING: Render stats not available after 20s. Diagnostics:"
647+
cat "$DIAG_PATH" 2>/dev/null || true
648+
echo "--- App log ---"
649+
cat /tmp/cmux-ui-test-app.log 2>/dev/null | tail -30 || true
650+
fi
651+
652+
# Write manifests so test can find the pre-launched state
653+
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"
654+
cat >"$MANIFEST_PATH" <<MANIFEST_EOF
655+
{"readyPath":"$DISPLAY_READY","displayIDPath":"$DISPLAY_ID_PATH","donePath":"$DISPLAY_DONE","logPath":"$HELPER_LOG"}
656+
MANIFEST_EOF
480657
658+
PRELAUNCH_PATH="/tmp/cmux-ui-test-prelaunch.json"
659+
cat >"$PRELAUNCH_PATH" <<PRELAUNCH_EOF
660+
{"diagnosticsPath":"$DIAG_PATH"}
661+
PRELAUNCH_EOF
662+
663+
# Run test — app is already launched from shell
664+
if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
665+
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
666+
-disableAutomaticPackageResolution \
667+
-destination "platform=macOS" \
668+
-only-testing:cmuxUITests/DisplayResolutionRegressionUITests \
669+
test-without-building; then
670+
exit 0
671+
fi
672+
673+
pkill -x "cmux DEV" 2>/dev/null || true
674+
kill "$HELPER_PID" 2>/dev/null || true
675+
676+
if [ "$attempt" -eq 2 ]; then
677+
echo "Display resolution UI regression failed after 2 attempts" >&2
678+
exit 1
679+
fi
680+
echo "Attempt $attempt failed, retrying..."
681+
sleep 3
682+
done
683+
684+
- name: Run browser find focus UI regression
685+
run: |
686+
set -euo pipefail
687+
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
481688
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
482689
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
483690
-disableAutomaticPackageResolution \
484691
-destination "platform=macOS" \
485-
-only-testing:cmuxUITests/DisplayResolutionRegressionUITests \
486-
test
692+
-maximum-test-execution-time-allowance 180 \
693+
-only-testing:cmuxUITests/BrowserPaneNavigationKeybindUITests/testCmdFFocusesBrowserFindFieldAfterCmdDCmdLNavigation \
694+
test-without-building
695+
696+
- name: Cleanup persistent virtual display
697+
if: always()
698+
run: |
699+
if [ -n "${VDISPLAY_PERSISTENT_PID:-}" ]; then
700+
kill "$VDISPLAY_PERSISTENT_PID" >/dev/null 2>&1 || true
701+
fi
702+
rm -f /tmp/cmux-vdisplay-persistent.ready /tmp/cmux-vdisplay-persistent.id /tmp/cmux-vdisplay-persistent.log

.github/workflows/nightly.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ on:
1313

1414
concurrency:
1515
group: nightly-build-${{ github.ref_name }}
16-
# Only the newest nightly matters. Cancel older runs so a fresh main push
17-
# does not sit behind an outdated build that would be discarded anyway.
18-
cancel-in-progress: true
16+
# Queue concurrent runs instead of canceling them so no build is lost.
17+
cancel-in-progress: false
1918

2019
permissions:
2120
contents: write

0 commit comments

Comments
 (0)