Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 68 additions & 28 deletions .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ on:
required: false
default: ""
type: string
# Space-separated cmuxTests/ suites when `unit_suite` came from a diff
# that only edits those suites. `app-host unit tests` then runs just
# them on one worker instead of every suite on seven. Empty runs all.
unit_selectors:
required: false
default: ""
type: string
# `|`-delimited names of the strict steps below that own a suite in
# unit_selectors. A changed-suites run runs those steps as they are,
# since each gives its suites an app host and settings of their own.
unit_strict_steps:
required: false
default: ""
type: string
product_artifacts:
required: false
default: "layered"
Expand Down Expand Up @@ -967,7 +981,7 @@ jobs:
# transitive skip otherwise marks every macOS job skipped even when
# linux-preflight itself succeeds. Require the direct needs explicitly.
if: ${{ !cancelled() && needs.macos-compile-admission.result == 'success' && inputs.macos == 'true' && (inputs.full_suite == 'true' || inputs.unit_suite == 'true') }}
name: app-host unit tests (${{ matrix.shard }}/7)
name: ${{ inputs.unit_selectors != '' && 'app-host unit tests (changed suites)' || format('app-host unit tests ({0}/7)', matrix.shard) }}
# App-host XCTest needs a runner that can broker testmanagerd control
# sessions, so route through the shared MACOS_RUNNER_15 var like the other
# macOS jobs. The fallback is what fork pull requests get, because
Expand All @@ -980,7 +994,9 @@ jobs:
# only needs a verdict, and the first failure already is one.
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix:
shard: [1, 2, 3, 4, 5, 6, 7]
# Shard 8 matches none of the numbered shards below that own a strict
# step, so a changed-suites run executes only its own batch.
shard: ${{ fromJSON(inputs.unit_selectors != '' && '[8]' || '[1, 2, 3, 4, 5, 6, 7]') }}
env:
CMUX_NODE_PRODUCT_CACHE_ROOT: ${{ vars.CMUX_NODE_PRODUCT_CACHE_ROOT }}
CMUX_NODE_PRODUCT_CACHE_MAX_BYTES: ${{ vars.CMUX_NODE_PRODUCT_CACHE_MAX_BYTES }}
Expand All @@ -991,6 +1007,7 @@ jobs:
# closed if a setup step or environment handoff loses either redirect.
CMUX_CI_APP_HOST_ISOLATION_REQUIRED: "1"
CMUX_APP_HOST_SHARD: ${{ matrix.shard }}
CMUX_APP_HOST_UNIT_SELECTORS: ${{ inputs.unit_selectors }}
CMUX_APP_HOST_CAPTURE_XCRESULTS: "1"
CMUX_CI_XCODE_APP: ${{ github.event_name == 'pull_request' && (vars.CMUX_CI_XCODE_APP_PR || vars.CMUX_CI_XCODE_APP_MACOS_15) || vars.CMUX_CI_XCODE_APP_MACOS_15 }}
CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26"
Expand Down Expand Up @@ -1241,7 +1258,7 @@ jobs:
echo "CMUX_APP_HOST_TEST_INVENTORY=$inventory_json" >> "$GITHUB_ENV"

- name: Upload built app-host test inventory
if: ${{ matrix.shard == 1 }}
if: ${{ matrix.shard == 1 || inputs.unit_selectors != '' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cmux-app-host-test-inventory-${{ github.run_id }}-${{ github.run_attempt }}
Expand All @@ -1266,7 +1283,7 @@ jobs:
fi

- name: Run My Devices regressions
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) || contains(inputs.unit_strict_steps, '|Run My Devices regressions|')) }}
env:
CMUX_APP_HOST_XCODEBUILD_ATTEMPTS: "1"
run: |
Expand Down Expand Up @@ -1315,7 +1332,7 @@ jobs:
test-without-building

- name: Run Pi Feed ownership regressions
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) || contains(inputs.unit_strict_steps, '|Run Pi Feed ownership regressions|')) }}
run: |
# Swift Testing assertion failures are tolerated in the full sharded
# app-host suite. Keep Feed ingestion and ownership on a non-tolerant
Expand All @@ -1339,7 +1356,7 @@ jobs:

- name: Run remote tmux mirror detach and placement regressions
id: remote-tmux-mirror
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) || contains(inputs.unit_strict_steps, '|Run remote tmux mirror detach and placement regressions|')) }}
run: |
# Closing a mirrored workspace must never kill the remote tmux session,
# and --new-window must consolidate mirrors moved across source windows.
Expand Down Expand Up @@ -1444,7 +1461,7 @@ jobs:
retention-days: 7

- name: Run focused browser regressions
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run focused browser regressions|')) }}
run: |
# Focused gates for https://github.com/manaflow-ai/cmux/issues/7380 and #8054.
# The tolerant full-suite step can crash-skip late suites (see #5888
Expand All @@ -1467,7 +1484,7 @@ jobs:
done

- name: Run remote tmux mirror active-pane regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run remote tmux mirror active-pane regression|')) }}
run: |
# Focused gates for https://github.com/manaflow-ai/cmux/issues/7372.
# The tolerant full-suite step can crash-skip late suites (see #5888
Expand All @@ -1483,7 +1500,7 @@ jobs:
test-without-building

- name: Run remote tmux mirror layout identity regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run remote tmux mirror layout identity regression|')) }}
run: |
# The tolerant full-suite step may accept expected failures or stop
# after an app-host crash. Keep #7833's identity suite non-tolerant
Expand All @@ -1498,7 +1515,7 @@ jobs:
test-without-building

- name: Run socket ACL reload regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run socket ACL reload regression|')) }}
run: |
# The tolerant full-suite step can accept assertion failures or
# crash-skip late suites. Keep #7984's live reload and denial tests
Expand Down Expand Up @@ -1531,7 +1548,7 @@ jobs:
CMUX_CLI_BIN="$CLI_BIN" python3 tests/test_cli_creation_initial_command.py

- name: Run settings file-store no-op persistence regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run settings file-store no-op persistence regression|')) }}
run: |
# The broad app-host suite tolerates expected failures and can stop
# after an app-host crash. Keep the settings persistence regression
Expand All @@ -1546,7 +1563,7 @@ jobs:
test-without-building

- name: Run issue 8373 sidebar convergence regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run issue 8373 sidebar convergence regression|')) }}
timeout-minutes: 5
run: |
# The broad app-host suite tolerates Swift Testing assertion failures.
Expand All @@ -1572,7 +1589,7 @@ jobs:
grep -F "ISSUE_8373_STRESS " "$test_log"

- name: Run sidebar workspace-switch layout regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run sidebar workspace-switch layout regression|')) }}
run: |
# The tolerant full-suite step can accept Swift Testing assertion
# failures when XCTest reports "0 unexpected". Keep #9612's real
Expand All @@ -1588,7 +1605,7 @@ jobs:
test-without-building

- name: Run terminal portal visibility regressions
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run terminal portal visibility regressions|')) }}
run: |
# These tests mutate the process-wide terminal portal registry and
# include Swift Testing assertions that the tolerant broad shard can
Expand Down Expand Up @@ -1630,7 +1647,7 @@ jobs:
node-version: "20"

- name: Run Ghostty numeric locale tests
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_B_SHARD) || contains(inputs.unit_strict_steps, '|Run Ghostty numeric locale tests|')) }}
run: |
# LC_NUMERIC is process-global. Keep these tests in their own app-host
# invocation and disable Xcode's parallel test execution for it.
Expand All @@ -1645,7 +1662,7 @@ jobs:
test-without-building

- name: Run global search shortcut regressions
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_GLOBAL_SEARCH_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_GLOBAL_SEARCH_SHARD) || contains(inputs.unit_strict_steps, '|Run global search shortcut regressions|')) }}
timeout-minutes: 40
run: |
# This serialized container owns global shortcut settings. Keep its
Expand Down Expand Up @@ -1673,7 +1690,7 @@ jobs:
"$test_log" cmuxTests/GlobalSearchShortcutBehaviorTests

- name: Run Cloud machine ordering acceptance
if: ${{ !cancelled() && steps.prepare-app-host-home.outcome == 'success' && matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
if: ${{ !cancelled() && steps.prepare-app-host-home.outcome == 'success' && (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) || contains(inputs.unit_strict_steps, '|Run Cloud machine ordering acceptance|')) }}
timeout-minutes: 10
run: |
# Separate invocations prove each selector executed tests; a healthy
Expand Down Expand Up @@ -1705,6 +1722,11 @@ jobs:
LOGICAL_BATCHES_PER_WORKER=2
LOGICAL_SHARD_TOTAL=$((PHYSICAL_SHARD_TOTAL * LOGICAL_BATCHES_PER_WORKER))
LOGICAL_SHARDS=("$PHYSICAL_SHARD" "$((PHYSICAL_SHARD + PHYSICAL_SHARD_TOTAL))")
if [ -n "${CMUX_APP_HOST_UNIT_SELECTORS:-}" ]; then
# A changed-suites run: one batch holding exactly those suites.
LOGICAL_SHARD_TOTAL=1
LOGICAL_SHARDS=(1)
fi

# Each worker runs two balanced batches sequentially. Every invocation
# writes to a regular file, and this script mirrors that file into the
Expand Down Expand Up @@ -1748,16 +1770,34 @@ jobs:
for reservation in ${CMUX_APP_HOST_RESERVED_WALL_SECONDS:-}; do
reserve_args+=(--reserve "$reservation")
done
python3 scripts/ci/cmux_unit_test_shard.py \
--shard-index "$logical_shard" \
--shard-total "$LOGICAL_SHARD_TOTAL" \
--physical-shard-total "$PHYSICAL_SHARD_TOTAL" \
${reserve_args[@]+"${reserve_args[@]}"} \
--output "$shard_args" || {
local shard_status=$?
echo "Failed to generate app-host unit-test batch ${logical_shard}/${LOGICAL_SHARD_TOTAL}"
return "$shard_status"
}
if [ -n "${CMUX_APP_HOST_UNIT_SELECTORS:-}" ]; then
# Suites a strict step owns ran in that step, not this batch.
python3 - "$shard_args" <<'PY'
import os
import sys
sys.path.insert(0, "scripts/ci")
from cmux_unit_test_shard import FOCUSED_GATE_SELECTORS
with open(sys.argv[1], "w", encoding="utf-8") as handle:
for selector in os.environ["CMUX_APP_HOST_UNIT_SELECTORS"].split():
if selector not in FOCUSED_GATE_SELECTORS:
handle.write(f"-only-testing:{selector}\n")
PY
if [ ! -s "$shard_args" ]; then
echo "Every changed suite ran in its strict step; no shared batch."
return 0
fi
else
python3 scripts/ci/cmux_unit_test_shard.py \
--shard-index "$logical_shard" \
--shard-total "$LOGICAL_SHARD_TOTAL" \
--physical-shard-total "$PHYSICAL_SHARD_TOTAL" \
${reserve_args[@]+"${reserve_args[@]}"} \
--output "$shard_args" || {
local shard_status=$?
echo "Failed to generate app-host unit-test batch ${logical_shard}/${LOGICAL_SHARD_TOTAL}"
return "$shard_status"
}
fi
if [ ! -r "$shard_args" ]; then
echo "Missing selector file for app-host unit-test batch ${logical_shard}/${LOGICAL_SHARD_TOTAL}"
return 1
Expand Down Expand Up @@ -1936,7 +1976,7 @@ jobs:
bun-version: "1.3.6"

- name: Run agent notification semantics
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
if: ${{ (matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) || contains(inputs.unit_strict_steps, '|Run agent notification semantics|')) }}
run: |
set -euo pipefail
# Reuse compile admission's product instead of rebuilding the app in
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ jobs:
ghosttykit_release: ${{ steps.unchanged_inputs.outputs.compile_admitted == 'true' && 'false' || steps.linux_guards.outputs.ghosttykit_release }}
full_suite: ${{ steps.suite.outputs.full_suite }}
unit_suite: ${{ steps.suite.outputs.unit_suite }}
unit_selectors: ${{ steps.suite.outputs.unit_selectors }}
unit_strict_steps: ${{ steps.suite.outputs.unit_strict_steps }}
coverage_gap: ${{ steps.suite.outputs.coverage_gap }}
compile_admitted: ${{ steps.unchanged_inputs.outputs.compile_admitted == 'true' && 'true' || steps.admitted.outputs.compile_admitted }}
source_tree: ${{ steps.source-identity.outputs.tree }}
Expand Down Expand Up @@ -150,6 +152,11 @@ jobs:
emit_all_areas
exit 0
fi
# The changed lines of cmuxTests/, so choose_ci_suite.py can tell
# which declarations a test edit touched. Absent reads as "every
# line of each changed file".
git diff --no-renames -U0 "$BASE_SHA" "$MERGE_SHA" -- cmuxTests \
> /tmp/cmux-ci-tests.diff || rm -f /tmp/cmux-ci-tests.diff

if [ ! -s /tmp/cmux-ci-changed-files.txt ]; then
echo "PR diff is empty; skipping product-area CI."
Expand Down Expand Up @@ -465,6 +472,9 @@ jobs:
if [ -f /tmp/cmux-ci-changed-files.txt ]; then
files_args=(--files-from /tmp/cmux-ci-changed-files.txt)
fi
if [ -f /tmp/cmux-ci-tests.diff ]; then
files_args+=(--diff-from /tmp/cmux-ci-tests.diff)
fi
python3 scripts/ci/choose_ci_suite.py \
--event-name "$EVENT_NAME" \
--pull-request-policy "$PULL_REQUEST_POLICY" \
Expand Down Expand Up @@ -969,6 +979,8 @@ jobs:
macos: ${{ needs.changes.outputs.macos }}
full_suite: ${{ needs.changes.outputs.full_suite }}
unit_suite: ${{ needs.changes.outputs.unit_suite }}
unit_selectors: ${{ needs.changes.outputs.unit_selectors }}
unit_strict_steps: ${{ needs.changes.outputs.unit_strict_steps }}
swift_packages: ${{ needs.changes.outputs.swift_packages }}
compile_admitted: ${{ needs.changes.outputs.compile_admitted }}
release_build: ${{ needs.changes.outputs.release_build }}
Expand Down
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ Add `full-ci` only when the user or agreed validation plan explicitly calls for
the broad suite; state which additional lanes are needed and why.

Normal PR CI can already run routed tests, including Swift package and CLI
wrapper checks, without `full-ci`. The label permits eligible app-host shards,
wrapper checks, without `full-ci`. A `cmuxTests/` diff runs the suites it
declares or extends on one app-host worker, with no label. `unit-ci` runs every
app-host suite across all seven workers; `full-ci` adds the other lanes on top.
Neither is needed to test the suites you edited. The label permits eligible app-host shards,
lag builds, and other full-suite lanes; path routing, release routing, and job
dependencies still apply. It does not request every repository test. Inspect
actual executed tests on the current SHA: a green skipped job is not coverage.
Expand Down
Loading
Loading