Set up full test suite in CI and Xcode Cloud (#447)

* Set up full test suite in CI and Xcode Cloud

Add build-ghosttykit.yml workflow to pre-build and publish
GhosttyKit.xcframework as a GitHub release on manaflow-ai/ghostty,
keyed by submodule SHA. Add ci_scripts/ci_post_clone.sh for Xcode
Cloud to download the pre-built xcframework with retry logic. Create
cmux-ci scheme that runs both cmuxTests and cmuxUITests. Switch the
CI tests job from running a single UI test class to the full suite.

* Run unit tests + single UI test class on self-hosted runner

The self-hosted runner can't launch the full app for UI tests (no GUI
session), so run all unit tests via cmux-unit scheme and keep the
original UpdatePillUITests as a smoke test. Full UI test suite runs
on Xcode Cloud which has proper macOS GUI support.

* Handle expected test failures in unit tests step

xcodebuild returns exit code 65 even for expected failures
(XCTExpectFailure). Parse the summary line to only fail the CI job
when there are unexpected failures.
This commit is contained in:
Lawrence Chen 2026-02-27 04:18:26 -08:00 committed by GitHub
parent 8968f787ca
commit ae3bcd7eea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 211 additions and 8 deletions

97
.github/workflows/build-ghosttykit.yml vendored Normal file
View file

@ -0,0 +1,97 @@
name: Build GhosttyKit
on:
push:
branches:
- main
pull_request:
jobs:
build-ghosttykit:
# Never run self-hosted jobs for fork pull requests.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: self-hosted
concurrency:
group: self-hosted-build
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
submodules: recursive
- name: Get ghostty SHA
id: ghostty-sha
run: |
SHA=$(git -C ghostty rev-parse HEAD)
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "Ghostty SHA: $SHA"
- name: Check if xcframework release already exists
id: check-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="xcframework-${{ steps.ghostty-sha.outputs.sha }}"
if gh release view "$TAG" --repo manaflow-ai/ghostty >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Release $TAG already exists, skipping build"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Release $TAG not found, will build"
fi
- name: Select Xcode
if: steps.check-release.outputs.exists == 'false'
run: |
set -euo pipefail
if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then
XCODE_DIR="/Applications/Xcode.app/Contents/Developer"
else
XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)"
if [ -n "$XCODE_APP" ]; then
XCODE_DIR="$XCODE_APP/Contents/Developer"
else
echo "No Xcode.app found under /Applications" >&2
exit 1
fi
fi
echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV"
export DEVELOPER_DIR="$XCODE_DIR"
xcodebuild -version
- name: Build GhosttyKit.xcframework
if: steps.check-release.outputs.exists == 'false'
run: |
set -euo pipefail
if ! command -v zig >/dev/null 2>&1; then
if command -v brew >/dev/null 2>&1; then
brew install zig
else
echo "zig is required to build GhosttyKit.xcframework. Install zig and retry." >&2
exit 1
fi
fi
cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false -Doptimize=ReleaseFast
- name: Package xcframework
if: steps.check-release.outputs.exists == 'false'
run: |
set -euo pipefail
rm -rf GhosttyKit.xcframework
cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework
tar czf GhosttyKit.xcframework.tar.gz GhosttyKit.xcframework
- name: Upload xcframework release
if: steps.check-release.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GHOSTTY_RELEASE_TOKEN }}
run: |
set -euo pipefail
TAG="xcframework-${{ steps.ghostty-sha.outputs.sha }}"
gh release create "$TAG" \
--repo manaflow-ai/ghostty \
--title "GhosttyKit xcframework (${{ steps.ghostty-sha.outputs.sha }})" \
--notes "Pre-built GhosttyKit.xcframework for commit ${{ steps.ghostty-sha.outputs.sha }}" \
GhosttyKit.xcframework.tar.gz
echo "Published release $TAG"

View file

@ -37,7 +37,7 @@ jobs:
- name: Typecheck
run: bun tsc --noEmit
ui-tests:
tests:
# Never run self-hosted jobs for fork pull requests.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: self-hosted
@ -93,8 +93,28 @@ jobs:
# Remove stale build cache to avoid incremental build errors
rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-*
- name: Run unit tests
run: |
set -euo pipefail
# xcodebuild exits 65 even for expected failures (XCTExpectFailure).
# Capture output and fail only if there are unexpected failures.
set +e
OUTPUT=$(xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \
-destination "platform=macOS" test 2>&1)
EXIT_CODE=$?
set -e
echo "$OUTPUT"
if [ "$EXIT_CODE" -ne 0 ]; then
SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1)
if echo "$SUMMARY" | grep -q "(0 unexpected)"; then
echo "All failures are expected, treating as pass"
else
echo "Unexpected test failures detected"
exit 1
fi
fi
- name: Run UI tests
run: |
set -euo pipefail
# Run directly on the self-hosted macOS runner.
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" -only-testing:cmuxUITests/UpdatePillUITests test