Skip to content

Commit e613113

Browse files
wan9chiclaude
andauthored
ci(test): shard Windows e2e_snapshots into 5 parallel jobs (#398)
## Summary **Half the CI time** by sharing Windows e2e_snapshots Windows is the long pole of CI: per-step PTY timeout is 60s on Windows vs 20s on Unix, and `vite_task_bin` e2e fixtures expand to 156 trials that run serially inside a single job. Linux/macOS finish well before Windows on every PR. This PR shards only the Windows `e2e_snapshots` test binary 5 ways while leaving every other platform (Linux GNU, macOS arm64, macOS x64, musl) untouched. ## Approach - `crates/vite_task_bin/tests/e2e_snapshots/main.rs` reads two new env vars: - `VT_SHARD_INDEX` (1..=total) + `VT_SHARD_TOTAL` → round-robin partition the generated `Vec<Trial>` at the **case** level (not the fixture level), so heavy fixtures' cases split across shards. - `VT_SKIP_E2E=1` → early-return from `main` so the sibling non-e2e Windows job can run the full workspace `cargo test` while this binary self-skips. - Local sanity check: 156 trials split 32/31/31/31/31, union = full list, no duplicates. ## CI matrix Was 4 entries → now 9: 3 non-Windows (unchanged) + 6 Windows (5 e2e shards + 1 non-e2e). All entries run the same three `cargo test` commands, parameterized by per-shard `scope` and `run_env` matrix fields — single source of truth, no per-shard branching in the steps. | shard kind | scope | run_env | | ---------- | --------------------------------------- | ----------------------------------- | | non-Windows| *(empty)* | *(empty)* | | `e2e-N` | `-p vite_task_bin --test e2e_snapshots` | `VT_SHARD_INDEX=N VT_SHARD_TOTAL=5` | | `non-e2e` | *(empty)* | `VT_SKIP_E2E=1` | ## Test plan - [ ] CI runs all 9 `test` matrix entries successfully on this PR - [ ] One Windows e2e shard log shows ~31 trials executed - [ ] `windows-non-e2e` log runs plan_snapshots + fspy + unit tests, with e2e_snapshots reporting no work - [ ] Linux/macOS/musl jobs unchanged in timing and behaviour - [ ] Slowest Windows e2e shard wall-clock is meaningfully shorter than the previous monolithic Windows test job Will monitor CI timings after first run and adjust shard count or strategy if balance is poor. --- _Generated by [Claude Code](https://claude.ai/code/session_017U5JHuUmN8NrcZcoD2mZhE)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 884df0c commit e613113

2 files changed

Lines changed: 98 additions & 8 deletions

File tree

  • .github/workflows
  • crates/vite_task_bin/tests/e2e_snapshots

.github/workflows/ci.yml

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
test:
6969
needs: detect-changes
7070
if: needs.detect-changes.outputs.code-changed == 'true'
71-
name: Test
71+
name: Test (${{ matrix.shard }})
7272
strategy:
7373
fail-fast: false
7474
matrix:
@@ -77,18 +77,71 @@ jobs:
7777
target: x86_64-unknown-linux-gnu
7878
cargo_cmd: cargo-zigbuild
7979
build_target: x86_64-unknown-linux-gnu.2.17
80-
- os: windows-latest
81-
target: x86_64-pc-windows-msvc
82-
cargo_cmd: cargo
83-
build_target: x86_64-pc-windows-msvc
80+
shard: linux-gnu
81+
scope: ''
82+
run_env: ''
8483
- os: namespace-profile-mac-default
8584
target: aarch64-apple-darwin
8685
cargo_cmd: cargo
8786
build_target: aarch64-apple-darwin
87+
shard: macos-arm64
88+
scope: ''
89+
run_env: ''
8890
- os: namespace-profile-mac-default
8991
target: x86_64-apple-darwin
9092
cargo_cmd: cargo
9193
build_target: x86_64-apple-darwin
94+
shard: macos-x64
95+
scope: ''
96+
run_env: ''
97+
# Windows e2e fixtures dominate wall-clock (60s per PTY step vs 20s on
98+
# Unix). Coverage is partitioned by crate: the e2e shards run
99+
# `-p vite_task_bin` and the non-e2e shard runs
100+
# `--workspace --exclude vite_task_bin`; the union is the workspace
101+
# by construction. The e2e_snapshots harness self-shards via
102+
# VT_SHARD_INDEX/VT_SHARD_TOTAL across the 5 e2e jobs.
103+
- os: windows-latest
104+
target: x86_64-pc-windows-msvc
105+
cargo_cmd: cargo
106+
build_target: x86_64-pc-windows-msvc
107+
shard: windows-e2e-1
108+
scope: '-p vite_task_bin'
109+
run_env: 'VT_SHARD_INDEX=1 VT_SHARD_TOTAL=5'
110+
- os: windows-latest
111+
target: x86_64-pc-windows-msvc
112+
cargo_cmd: cargo
113+
build_target: x86_64-pc-windows-msvc
114+
shard: windows-e2e-2
115+
scope: '-p vite_task_bin'
116+
run_env: 'VT_SHARD_INDEX=2 VT_SHARD_TOTAL=5'
117+
- os: windows-latest
118+
target: x86_64-pc-windows-msvc
119+
cargo_cmd: cargo
120+
build_target: x86_64-pc-windows-msvc
121+
shard: windows-e2e-3
122+
scope: '-p vite_task_bin'
123+
run_env: 'VT_SHARD_INDEX=3 VT_SHARD_TOTAL=5'
124+
- os: windows-latest
125+
target: x86_64-pc-windows-msvc
126+
cargo_cmd: cargo
127+
build_target: x86_64-pc-windows-msvc
128+
shard: windows-e2e-4
129+
scope: '-p vite_task_bin'
130+
run_env: 'VT_SHARD_INDEX=4 VT_SHARD_TOTAL=5'
131+
- os: windows-latest
132+
target: x86_64-pc-windows-msvc
133+
cargo_cmd: cargo
134+
build_target: x86_64-pc-windows-msvc
135+
shard: windows-e2e-5
136+
scope: '-p vite_task_bin'
137+
run_env: 'VT_SHARD_INDEX=5 VT_SHARD_TOTAL=5'
138+
- os: windows-latest
139+
target: x86_64-pc-windows-msvc
140+
cargo_cmd: cargo
141+
build_target: x86_64-pc-windows-msvc
142+
shard: windows-non-e2e
143+
scope: '--workspace --exclude vite_task_bin'
144+
run_env: ''
92145
runs-on: ${{ matrix.os }}
93146
steps:
94147
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
@@ -124,13 +177,13 @@ jobs:
124177
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
125178

126179
- name: Build tests
127-
run: ${{ matrix.cargo_cmd }} test --no-run --target ${{ matrix.build_target }}
180+
run: ${{ matrix.cargo_cmd }} test --no-run ${{ matrix.scope }} --target ${{ matrix.build_target }}
128181

129182
# Default `cargo test` runs only tests that need nothing beyond the
130183
# Rust toolchain; this step verifies that contract before Node.js
131184
# and pnpm enter the picture.
132185
- name: Run tests
133-
run: ${{ matrix.cargo_cmd }} test --target ${{ matrix.build_target }}
186+
run: ${{ matrix.run_env }} ${{ matrix.cargo_cmd }} test ${{ matrix.scope }} --target ${{ matrix.build_target }}
134187

135188
# x86_64-apple-darwin runs on arm64 runner under Rosetta; install x64 Node
136189
# so fspy's x86_64 preload dylib can be injected into spawned node procs.
@@ -139,7 +192,7 @@ jobs:
139192
architecture: ${{ matrix.target == 'x86_64-apple-darwin' && 'x64' || '' }}
140193

141194
- name: Run ignored tests
142-
run: ${{ matrix.cargo_cmd }} test --target ${{ matrix.build_target }} -- --ignored
195+
run: ${{ matrix.run_env }} ${{ matrix.cargo_cmd }} test ${{ matrix.scope }} --target ${{ matrix.build_target }} -- --ignored
143196

144197
test-musl:
145198
needs: detect-changes

crates/vite_task_bin/tests/e2e_snapshots/main.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,28 @@ fn run_case(
547547
Ok(())
548548
}
549549

550+
/// Parses `VT_SHARD_INDEX` (1..=total) and `VT_SHARD_TOTAL` env vars for CI
551+
/// sharding. Both unset means "run all trials". Both set selects one shard via
552+
/// round-robin. Exactly one set is a CI misconfiguration and panics.
553+
fn parse_shard_env() -> Option<(usize, usize)> {
554+
let index = std::env::var("VT_SHARD_INDEX").ok();
555+
let total = std::env::var("VT_SHARD_TOTAL").ok();
556+
match (index, total) {
557+
(None, None) => None,
558+
(Some(i), Some(t)) => {
559+
let index: usize = i.parse().expect("VT_SHARD_INDEX must be a positive integer");
560+
let total: usize = t.parse().expect("VT_SHARD_TOTAL must be a positive integer");
561+
assert!(total > 0, "VT_SHARD_TOTAL must be > 0");
562+
assert!(
563+
(1..=total).contains(&index),
564+
"VT_SHARD_INDEX must be in 1..={total}, got {index}"
565+
);
566+
Some((index, total))
567+
}
568+
_ => panic!("VT_SHARD_INDEX and VT_SHARD_TOTAL must both be set or both unset"),
569+
}
570+
}
571+
550572
#[expect(clippy::disallowed_types, reason = "Path required for CARGO_MANIFEST_DIR path traversal")]
551573
fn main() {
552574
let tmp_dir = tempfile::tempdir().unwrap();
@@ -576,6 +598,8 @@ fn main() {
576598
args.test_threads = Some(1);
577599
}
578600

601+
let shard = parse_shard_env();
602+
579603
let tests: Vec<libtest_mimic::Trial> = fixture_paths
580604
.into_iter()
581605
.flat_map(|fixture_path| {
@@ -626,5 +650,18 @@ fn main() {
626650
})
627651
.collect();
628652

653+
let tests = match shard {
654+
Some((index, total)) => {
655+
let count = tests.len();
656+
tests
657+
.into_iter()
658+
.enumerate()
659+
.filter(|(i, _)| i * total / count + 1 == index)
660+
.map(|(_, t)| t)
661+
.collect()
662+
}
663+
None => tests,
664+
};
665+
629666
libtest_mimic::run(&args, tests).exit();
630667
}

0 commit comments

Comments
 (0)