Skip to content
Merged
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
18 changes: 12 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ jobs:
# now means Linux + macOS + the gates; Windows re-enters the gate when the
# tracked failures are fixed, not before.
platform-windows:
name: windows ${{ matrix.shard }}/4
name: windows ${{ matrix.shard }}/6
needs: select-windows-runner
if: >-
github.event_name == 'workflow_dispatch' && (github.event.inputs.lane == '' || github.event.inputs.lane == 'all')
Expand All @@ -673,11 +673,17 @@ jobs:
# margin. 25 leaves the outer bound in place — a wedged shard still dies —
# while making a completed shard the normal outcome. The crash-retry below can
# double a shard's work, and this ceiling has to cover that second attempt too.
#
# Four shards then grew into the ceiling: across five runs of one branch, completed
# shards took 17-25 minutes and run 33934756997 cancelled a green 3/4 at 25m12s —
# the same truncation as above. The bound is kept; the work per shard is cut instead.
# Six shards put each leg at roughly two-thirds of the four-shard wall time, back
# inside the margin 25 was chosen to provide.
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5, 6]
steps:
- name: Show selected runner
shell: bash
Expand Down Expand Up @@ -745,18 +751,18 @@ jobs:
set -uo pipefail
suite_log="$(mktemp -t ocx-windows-suite.XXXXXX)"
for attempt in 1 2; do
bun test --isolate --timeout 60000 tests --shard=${{ matrix.shard }}/4 2>&1 | tee "$suite_log"
bun test --isolate --timeout 60000 tests --shard=${{ matrix.shard }}/6 2>&1 | tee "$suite_log"
suite_status="${PIPESTATUS[0]}"
if [ "$suite_status" -eq 0 ]; then
exit 0
fi
if ! grep -Eqi 'oh no: Bun has crashed|Internal assertion failure|Segmentation fault at address|Illegal instruction|Bus error|Aborted \(core dumped\)' "$suite_log"; then
echo "::error::Windows shard ${{ matrix.shard }}/4 failed on attempt ${attempt} (exit ${suite_status}); assertion failures are not retried."
echo "::error::Windows shard ${{ matrix.shard }}/6 failed on attempt ${attempt} (exit ${suite_status}); assertion failures are not retried."
exit "$suite_status"
fi
echo "::warning::Bun runtime crash in Windows shard ${{ matrix.shard }}/4 (exit ${suite_status}, attempt ${attempt})."
echo "::warning::Bun runtime crash in Windows shard ${{ matrix.shard }}/6 (exit ${suite_status}, attempt ${attempt})."
done
echo "::error::Bun runtime crash repeated on Windows shard ${{ matrix.shard }}/4; failing after one retry."
echo "::error::Bun runtime crash repeated on Windows shard ${{ matrix.shard }}/6; failing after one retry."
exit 1

- name: CLI help smoke
Expand Down
20 changes: 15 additions & 5 deletions tests/ci-workflows/ci-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ describe("GitHub Actions hardening", () => {
const linuxShards = (ci.jobs?.test as { strategy?: { matrix?: { shard?: number[] } } })
?.strategy?.matrix?.shard ?? [];
expect(linuxShards).toEqual([1, 2, 3, 4]);
expect(workflow).toContain(`--shard=\${{ matrix.shard }}/${linuxShards.length}`);
// The Linux shards pass their divisor through TEST_SHARD to the batch runner. Before
// Windows sharded differently, this assertion matched the Windows step's --shard
// literal by coincidence; pin the Linux env line so it observes the Linux job.
expect(workflow).toContain(`TEST_SHARD: \${{ matrix.shard }}/${linuxShards.length}`);

// Every job that runs tests/ must fetch tags, because one of those tests reads
// them. tests/ci-workflows/release-version-line.test.ts compares package.json against the
Expand All @@ -187,13 +190,20 @@ describe("GitHub Actions hardening", () => {
expect(`${jobName}:${String(checkout?.with?.["fetch-tags"])}`).toBe(`${jobName}:true`);
}

// Windows uses the same shard matrix after the single-leg isolate budget was
// replaced. Keep the two matrices equal so a future edit cannot reintroduce
// a partial Windows suite while Linux stays fully tiled.
// Windows shards more finely than Linux: the same suite takes 17-25 minutes per
// quarter on windows-latest, which is the leg's own 25-minute ceiling (run
// 33934756997 cancelled a green 3/4 at 25m12s). The invariant that matters is the
// one above — the matrix and the divisor tile the suite exactly — so pin the
// Windows matrix to its own divisor rather than to Linux's, and pin it to be
// contiguous from 1 so a dropped entry cannot leave a slice of the suite unrun.
const windowsShards = (ci.jobs?.["platform-windows"] as {
strategy?: { matrix?: { shard?: number[] } };
})?.strategy?.matrix?.shard ?? [];
expect(windowsShards).toEqual(linuxShards);
expect(windowsShards).toEqual([1, 2, 3, 4, 5, 6]);
expect(windowsShards).toEqual(windowsShards.map((_, i) => i + 1));
const windowsSteps = (ci.jobs?.["platform-windows"] as { steps?: Array<{ run?: string }> })?.steps ?? [];
expect(windowsSteps.some(step => step.run?.includes(`--shard=\${{ matrix.shard }}/${windowsShards.length}`))).toBe(true);
expect(ci.jobs?.["platform-windows"]?.name).toBe(`windows \${{ matrix.shard }}/${windowsShards.length}`);

// The aggregate gate is the check a human trusts. Three ways to break it
// silently: drop `if: always()` so it skips (and a skipped job reports
Expand Down
Loading