Files
callstack__agent-device/.github/workflows/macos.yml
Michał Pierzchała 02d548dfc9 ci: consolidate CI workflow from 15 jobs to 8 (#1996)
* ci: consolidate CI workflow from 15 jobs to 8

Merge single-gate ubuntu jobs into grouped jobs sharing one checkout and
install: Lint & Format (plus the static text assertions), Repo Guards
(layering/selector/wiring/maestro/mcp-metadata), Compatibility &
Provenance (shared fetch-depth: 0 checkout), Typecheck & Package, and
Integration Tests (absorbs the web smoke with step-scoped env). Every
gate remains an independently named run-gate step; the gate manifest
derives lane ownership structurally.

Drop the Bun setup from FreeRange: @chenglou/freerange's bin is a plain
Node script. It stays GitHub-owned; only the runtime requirement is
retired.

* ci: fold FreeRange into Repo Guards and skip no-op fixture release jobs

FreeRange runs on plain Node now, so its gate joins Repo Guards as the
last step instead of occupying its own worker for the slowest guard.
The fixture release matrix filters to entries that will actually build,
so a cached-fingerprint PR starts zero release runners.

* ci: fold host XCTests into the macOS smoke lane and shard Coverage

The macOS lane now builds one unit-test-flagged runner bundle that both
the host XCTest run and the replay smoke consume, so the host lane no
longer occupies its own macos-26 runner behind a separate queue. The
host lane's file moves with it, and check:xctest-selection follows.

Coverage shards across two runners via blob reports and merges them on
a report job that evaluates thresholds once over the full suite and
produces every coverage artifact. The tmpdir leak check runs per shard,
since a leak lands on whichever runner executed the file.

* ci: drop local shard-smoke artifacts from tracking

* ci: enforce coverage thresholds only on the merged run

A shard evaluates its own half-suite coverage, so the global gate fired
per shard. Shards now report without gating; Coverage Report keeps the
real thresholds over the full merged suite.

* ci: include hidden files when uploading coverage blobs
2026-08-24 16:42:43 +02:00

149 lines
5.8 KiB
YAML

name: macOS
on:
pull_request:
paths-ignore:
- 'docs/**'
- 'website/**'
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
- 'SECURITY.md'
- '.github/actions/build-docs/action.yml'
- '.github/workflows/deploy.yml'
- '.github/workflows/pr-preview.yml'
- '.github/workflows/pr-preview-cleanup.yml'
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# One macOS lane for everything the macOS host can prove: command coverage,
# the runner's pure-decision XCTests, the helper, and the replay smoke. All
# of it consumes one runner build, compiled with the unit-test flag so the
# host XCTest run below has a bundle to execute (#1781 A7).
#
# Which tests that build reaches is not a list: it is everything the macOS
# build compiles, and the simulator-only tests keep themselves out with an
# `os(iOS)` guard (see the classification note in RunnerTests.swift). The
# `-skip-testing:` entry is validated by `pnpm check:xctest-selection` for
# the same reason the nightly's is: `RunnerTests/testCommand` is the
# runner's 24-hour server entry point, and a typo re-arms the hang. The
# lane's reporter asserts the executed count equals the reach the check
# derives from source, so a build without the compile flag or a guard that
# compiles a file out reads as red rather than as a smaller green.
#
# Kill criterion: if the host run proves flaky where the simulator run of the
# same tests is not (a platform-branch difference this classification
# missed), gate the offender `os(iOS)` and it returns to the simulator lanes;
# if that happens to more than a handful, the host lane is the wrong tool and
# goes.
smoke-macos:
name: Smoke Tests
runs-on: macos-26
timeout-minutes: 80
env:
AGENT_DEVICE_STATE_DIR: ${{ github.workspace }}/.tmp/agent-device-state
AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH: ${{ github.workspace }}/.tmp/macos-runner-derived
AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS: '1'
RESULT_BUNDLE_PATH: ${{ github.workspace }}/.tmp/xctest-host/RunnerTests.xcresult
RESULT_SUMMARY_PATH: ${{ github.workspace }}/.tmp/xctest-host/test-results-summary.json
XCTEST_LANE: host
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup toolchain
uses: ./.github/actions/setup-node-pnpm
- name: Run macOS command coverage contract
uses: ./.github/actions/run-gate
with: { gate: macos-coverage }
- name: Restore and build macOS XCTest runner
uses: ./.github/actions/setup-apple-runner-build
with:
derived-path: ${{ env.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH }}
cache-key-prefix: macos-runner-prebuilt
gate: swift-runner-macos
xcuitest-platform: macos
xcuitest-destination: platform=macOS,arch=arm64
- name: Run the host-runnable runner XCTests
run: |
set -euo pipefail
XCTESTRUN_PATH="$(find "$AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH/Build/Products" -maxdepth 1 -name '*.xctestrun' -print -quit)"
test -n "$XCTESTRUN_PATH"
mkdir -p "$(dirname "$RESULT_BUNDLE_PATH")"
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN_PATH" \
-destination 'platform=macOS,arch=arm64' \
-skip-testing:AgentDeviceRunnerUITests/RunnerTests/testCommand \
-resultBundlePath "$RESULT_BUNDLE_PATH"
# Same shape as xctest-nightly.yml: extract is best-effort, the report step asserts.
- name: Extract the test-results summary
if: always()
run: |
set -uo pipefail
[ -d "$RESULT_BUNDLE_PATH" ] || exit 0
xcrun xcresulttool get test-results summary --path "$RESULT_BUNDLE_PATH" --compact \
> "$RESULT_SUMMARY_PATH" 2>/dev/null ||
xcrun xcresulttool get test-results summary --path "$RESULT_BUNDLE_PATH" \
> "$RESULT_SUMMARY_PATH" 2>/dev/null ||
rm -f "$RESULT_SUMMARY_PATH"
exit 0
- name: Report the run and assert it executed the source-derived set
if: always()
run: |
set -euo pipefail
if [ ! -s "$RESULT_SUMMARY_PATH" ]; then
echo 'No usable test-results summary was produced, so nothing here can show a test ran.'
echo 'The suite step above failed before or during the run; read its log first.'
echo '### iOS runner host XCTest lane' >> "$GITHUB_STEP_SUMMARY"
echo 'No result-bundle summary; see the run log and the uploaded bundle.' >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
node --experimental-strip-types scripts/xctest-run-summary.ts
- name: Upload result bundle
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: xctest-host-results-${{ github.run_id }}-${{ github.run_attempt }}
path: .tmp/xctest-host
if-no-files-found: warn
- name: Build macOS helper
uses: ./.github/actions/run-gate
with: { gate: macos-helper }
- name: Run macOS integration test
uses: ./.github/actions/run-gate
with:
gate: replay-macos
args: |
--retries
2
--report-junit
test/artifacts/replays-macos.junit.xml
- name: Upload macOS artifacts
if: always()
uses: ./.github/actions/upload-agent-device-artifacts
with:
artifact-name: macos-artifacts
agent-state-dir: ${{ env.AGENT_DEVICE_STATE_DIR }}
runner-derived-path: ${{ env.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH }}