Repository navigation
Add bun test --shard=M/N for splitting tests across CI jobs - #29366
Conversation
|
Updated 11:05 PM PT - Apr 17th, 2026
❌ @Jarred-Sumner, your commit 9e167b8 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 29366That installs a local version of the PR into your bun-29366 --bun |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
0a4e021 to
f711389
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/cli/test_command.zig (1)
1638-1660:⚠️ Potential issue | 🟡 Minor
--watchstill exits immediately for an empty shard.Line 1643 only keeps the watcher alive for the
--changedempty-result case. With the new--shardbehavior,bun test --watch --shard=M/Ncan now legitimately produce zero selected files and exit 0, but this branch still disables watch mode for that case. That makes the new empty-shard success path inconsistent under--watch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/test_command.zig` around lines 1638 - 1660, The watcher-enabling condition currently skips the --watch path when a shard results in zero selected test_files; update the if condition around vm.hot_reload so it also keeps the watcher alive when a shard was requested but there are test files to consider. Concretely, in the block that checks test_files.len and ctx.test_options.changed, add a branch for ctx.test_options.shard (e.g. "or (ctx.test_options.shard != null and all_test_files.len > 0)") so the vm.hot_reload setup and ChangedFilesFilter/HotReloader calls still run for empty-shard runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/cli/test/test-shard.test.ts`:
- Around line 34-151: Add two tests to the existing --shard suite: one that
verifies composition with --changed and one that verifies composition with
--randomize. For the --changed case, create a fixture (use makeFixture e.g.
"shard-changed", N) and invoke the test runner via runShard while passing the
--changed flag along with the desired shard (e.g. runShard(cwd, "1/2",
["--changed"])) and assert that only the changed files are partitioned across
shards (no overlap/gaps and summary line present). For the --randomize case,
create a fixture (makeFixture e.g. "shard-randomize", M) and call runShard twice
with the same fixed seed via --randomize=<seed> plus the shard (e.g.
runShard(cwd, "2/4", ["--randomize=42"])), then assert both runs produce the
same ran list and correct shard partitioning; ensure exit codes and stderr shard
summary checks mirror existing tests (use runShard, makeFixture, and the same
assertion patterns as other shard tests).
---
Outside diff comments:
In `@src/cli/test_command.zig`:
- Around line 1638-1660: The watcher-enabling condition currently skips the
--watch path when a shard results in zero selected test_files; update the if
condition around vm.hot_reload so it also keeps the watcher alive when a shard
was requested but there are test files to consider. Concretely, in the block
that checks test_files.len and ctx.test_options.changed, add a branch for
ctx.test_options.shard (e.g. "or (ctx.test_options.shard != null and
all_test_files.len > 0)") so the vm.hot_reload setup and
ChangedFilesFilter/HotReloader calls still run for empty-shard runs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d0d54f2a-19e5-4073-bd37-775711f99408
📥 Commits
Reviewing files that changed from the base of the PR and between 0a4e021e9d0cb67e4bc5c16c68c2579db93590d4 and f711389.
📒 Files selected for processing (5)
completions/bun.zshsrc/cli.zigsrc/cli/Arguments.zigsrc/cli/test_command.zigtest/cli/test/test-shard.test.ts
|
✅ No merge conflicts detected when merging into Your branch is good to go! |
There was a problem hiding this comment.
♻️ Duplicate comments (2)
test/cli/test/test-shard.test.ts (2)
34-174:⚠️ Potential issue | 🟠 MajorMissing
--changed+--shardcoverage leaves a contract gap.The suite still has no e2e case that runs
bun test --changed --shard=M/Nand verifies shard partitioning after changed-file filtering. That behavior is part of this PR’s contract and can regress unnoticed without a dedicated test.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/cli/test/test-shard.test.ts` around lines 34 - 174, Add an end-to-end test in test/cli/test/test-shard.test.ts that covers the combination of --changed and --shard: use makeFixture to create a repo with multiple test files and mark one or more files as "changed", then invoke runShard(cwd, "M/N", ["--changed"]) (or runShard with a third args array) for the shard values you expect to exercise; assert that the selected ran files are the correct shard partition of the filtered (changed-only) test list, that stderr contains the "--shard=" summary, and that exitCode is 0 to prevent regressions in the changed+shard contract.
76-97:⚠️ Potential issue | 🟠 Major
--randomizecomposition test does not actually validate randomization behavior.This test passes
--seedbut never enables--randomize, andrunShard()sortsranbefore returning, so “same seed ⇒ same order” is not testable here. As written, it only validates set membership, not shuffle semantics.Suggested patch
-async function runShard(cwd: string, shard: string, extra: string[] = []) { +async function runShard(cwd: string, shard: string, extra: string[] = [], sortRan = true) { await using proc = Bun.spawn({ cmd: [bunExe(), "test", `--shard=${shard}`, ...extra], @@ - const ran = stdout + const ran = stdout .split("\n") .filter(l => l.startsWith("RAN ")) - .map(l => l.slice(4)) - .sort(); + .map(l => l.slice(4)); - return { ran, stderr, exitCode }; + return { ran: sortRan ? [...ran].sort() : ran, stderr, exitCode }; } @@ - const plain = await runShard(cwd, "2/4"); - const seeded1 = await runShard(cwd, "2/4", ["--seed=123"]); - const seeded2 = await runShard(cwd, "2/4", ["--seed=123"]); - const otherSeed = await runShard(cwd, "2/4", ["--seed=999999"]); + const plain = await runShard(cwd, "2/4"); + const seeded1 = await runShard(cwd, "2/4", ["--randomize", "--seed=123"], false); + const seeded2 = await runShard(cwd, "2/4", ["--randomize", "--seed=123"], false); + const otherSeed = await runShard(cwd, "2/4", ["--randomize", "--seed=999999"], false); @@ - expect(seeded1.ran).toEqual(plain.ran); - expect(seeded2.ran).toEqual(plain.ran); - expect(otherSeed.ran).toEqual(plain.ran); + expect([...seeded1.ran].sort()).toEqual(plain.ran); + expect([...seeded2.ran].sort()).toEqual(plain.ran); + expect([...otherSeed.ran].sort()).toEqual(plain.ran); @@ - // Same seed → same result. + // Same seed → same order. + expect(seeded1.ran).toEqual(seeded2.ran);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/cli/test/test-shard.test.ts` around lines 76 - 97, The test claims to validate --randomize but never enables it and runShard currently sorts ran before returning, so it only checks set membership; fix by calling runShard with the --randomize flag for the seeded runs (e.g. runShard(cwd, "2/4", ["--randomize","--seed=123"]) and runShard(cwd, "2/4", ["--randomize","--seed=999999"])) and ensure runShard does not unconditionally sort the returned ran array (or remove the sort there) so the test can assert deterministic ordering for the same seed via seeded1.ran === seeded2.ran and different ordering for otherSeed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@test/cli/test/test-shard.test.ts`:
- Around line 34-174: Add an end-to-end test in test/cli/test/test-shard.test.ts
that covers the combination of --changed and --shard: use makeFixture to create
a repo with multiple test files and mark one or more files as "changed", then
invoke runShard(cwd, "M/N", ["--changed"]) (or runShard with a third args array)
for the shard values you expect to exercise; assert that the selected ran files
are the correct shard partition of the filtered (changed-only) test list, that
stderr contains the "--shard=" summary, and that exitCode is 0 to prevent
regressions in the changed+shard contract.
- Around line 76-97: The test claims to validate --randomize but never enables
it and runShard currently sorts ran before returning, so it only checks set
membership; fix by calling runShard with the --randomize flag for the seeded
runs (e.g. runShard(cwd, "2/4", ["--randomize","--seed=123"]) and runShard(cwd,
"2/4", ["--randomize","--seed=999999"])) and ensure runShard does not
unconditionally sort the returned ran array (or remove the sort there) so the
test can assert deterministic ordering for the same seed via seeded1.ran ===
seeded2.ran and different ordering for otherSeed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e9940e35-1d4a-47f0-9e45-8b89aab5b8b1
📒 Files selected for processing (1)
test/cli/test/test-shard.test.ts
|
Re the two ♻️ duplicate CodeRabbit points on the latest push:
|
There was a problem hiding this comment.
LGTM — implementation is correct and the significant UX bug was already addressed. Outstanding items (missing Output.flush() before exit, describe vs describe.concurrent, and the seed test only verifying set equality) are minor nits that don't affect functional correctness.
Extended reasoning...
Overview
The PR adds bun test --shard=M/N across five files: CLI type definition (src/cli.zig), argument parsing (src/cli/Arguments.zig), test command runtime (src/cli/test_command.zig), shell completions (completions/bun.zsh), and a new test file (test/cli/test/test-shard.test.ts). The sharding logic sorts test files by path for determinism, applies round-robin selection, prints a summary line, and gates on test_files.len > 0 to avoid confusing 0/0 output.
Security risks
None. The feature parses two unsigned integers from a CLI argument and does a modulo-based array selection. No user-controlled data reaches sensitive operations, file paths, or system calls beyond what bun test already does.
Level of scrutiny
Moderate — this is new user-facing functionality touching argument parsing and test file selection. However the logic is simple (sort + round-robin), all error cases produce clear messages with non-zero exit codes, and the test suite covers partitioning invariants, determinism, empty shards, and all invalid-input forms.
Other factors
A prior review cycle caught a real UX bug (misleading running 0/0 test files when no test files exist); the author fixed it promptly. The remaining open items from prior inline comments — missing Output.flush() before five Global.exit(1) calls in the shard parser, describe vs describe.concurrent in the test file, and the seed-order test only asserting set equality — are nits. The flush omission is pre-existing pattern in adjacent option parsers, flush-on-exit is reliable in practice, and all test suite invariants that matter (no overlap, no gaps, correct round-robin, correct exit codes) are verified. The --watch + --shard edge case was deliberately left as-is with a documented rationale that is reasonable for a CI-oriented flag.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/cli/test/test-shard.test.ts`:
- Around line 40-43: The test currently asserts r.exitCode before completing
stdout/stderr membership checks inside the results loop; move the exit code
assertion to after the stdout/stderr checks so stdout-derived assertions (e.g.,
expect(r.stderr).toContain("--shard=")) run first and any failures show richer
context. Locate the loop that iterates over results (the for (const r of
results) block) and reorder the assertions: keep
expect(r.stderr).toContain("--shard=") (and any other stdout/stderr
expectations) where they are, then call expect(r.exitCode).toBe(0) as the final
assertion for each r.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fcd563ad-eeb6-4212-ab56-da6e64d19d70
📒 Files selected for processing (1)
test/cli/test/test-shard.test.ts
There was a problem hiding this comment.
LGTM — all previously raised concerns have been addressed.
Extended reasoning...
Overview
This PR adds bun test --shard=M/N to split test files across CI jobs, touching CLI argument parsing (Arguments.zig, cli.zig), test runner logic (test_command.zig), zsh completions, and a comprehensive E2E test file (test-shard.test.ts).
Security risks
None. This is a purely additive CLI flag that filters an already-discovered list of local test files. No user input reaches network, filesystem writes, or privilege boundaries beyond the existing test runner.
Level of scrutiny
Moderate. The feature touches the test runner's file-dispatch path but is well-isolated: the shard block is a contained sort + round-robin loop on a local slice, guarded by test_files.len > 0. The parsing adds validation-gated early exits consistent with adjacent option parsers.
Other factors
- 13 E2E tests cover partitioning correctness (union == all files, intersection == ∅), determinism,
1/1, empty-shard exit 0, summary line, no-files case, and all invalid-input error messages. - All inline review concerns from prior runs were resolved: the
0/0misleading output guard,describe.concurrent, and the overclaimed randomize test comment. - No bugs found by the automated bug-hunting system.
ce5e65e to
3210bd8
Compare
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
src/sourcemap/InternalSourceMap.zig:1-10— src/sourcemap/InternalSourceMap.zig is a new 982-line file added by this PR that is completely unrelated to the --shard feature and is never imported or referenced by any other .zig file in the codebase. Please remove it from this PR to keep the diff focused on the --shard implementation.Extended reasoning...
What the issue is
src/sourcemap/InternalSourceMap.zig is a 982-line new file added in this PR implementing a sophisticated binary source-map storage format (windowed varint encoding, FindCache, Cursor, Builder, fromVLQ, appendVLQTo, etc.). The PR description covers only --shard functionality: CLI completion, argument parsing, test command logic, and test coverage. There is no mention of a new internal source map format anywhere in the PR description.
How it was confirmed
Running grep -r InternalSourceMap src/ --include=*.zig returns results only from within the file itself. The sourcemap.zig index file does not include or reference it. No other .zig file in the entire codebase imports it, which means the file is never compiled into the binary. This has been independently confirmed by three separate verifiers.
How it got here
The file was introduced in commit ce5e65e, which is described as 'ci: retrigger gate after clearing stale release build cache' — an unusual description for adding 982 lines of new source-map infrastructure. The initial --shard commit (e947c6e) only touched five files: completions/bun.zsh, src/cli.zig, src/cli/Arguments.zig, src/cli/test_command.zig, and test/cli/test/test-shard.test.ts. This strongly suggests the file was accidentally rebased in from another in-progress branch.
Impact
Because no other file imports InternalSourceMap.zig, it will not be compiled and has zero functional impact on the binary. The concerns are purely procedural: the PR diff is inflated by ~1000 lines of unreviewed, complex code; the file may create merge conflicts with whatever branch it originated from; and reviewers or future contributors may be confused about its purpose or relationship to the --shard feature.
How to fix
Remove src/sourcemap/InternalSourceMap.zig from this PR. It should be submitted in a dedicated PR for the internal source-map work once that feature is ready for review.
Step-by-step proof
- PR description: covers --shard only (CLI parsing, round-robin file selection, shell completion, E2E tests). No mention of source map changes.
- Initial --shard commit e947c6e: touches exactly 5 files, none in src/sourcemap/.
- Commit ce5e65e ('ci: retrigger gate...') adds src/sourcemap/InternalSourceMap.zig with 982 lines.
- grep -r InternalSourceMap src/ --include=*.zig: matches only within InternalSourceMap.zig itself.
- src/sourcemap/sourcemap.zig (the index): no reference to InternalSourceMap.
- Conclusion: orphaned file from another branch, accidentally rebased in.
There was a problem hiding this comment.
The pluralization nit is the only outstanding item — everything else has been addressed. LGTM.
Extended reasoning...
Overview
This PR adds bun test --shard=M/N across 5 files: zsh completions, CLI type definition, argument parser, test command runtime logic, and a new E2E test suite.
Security risks
None. This is a purely additive CLI flag that filters an already-discovered set of test file paths. No auth, crypto, or I/O boundary changes.
Level of scrutiny
The implementation is straightforward — sort, round-robin index, truncate slice. The tricky edge cases (empty shard, empty project, --changed interaction, --watch interaction) were raised in review and either fixed or consciously accepted with documented rationale. The test suite is comprehensive (13 tests, switched to describe.concurrent, timeout removed, randomize composition clarified).
Other factors
All my prior review comments have been addressed: the empty-shard guard was added, describe.concurrent was applied, the Output.flush() concern was resolved (Global.exit already flushes), the --watch --shard watcher behavior was consciously left as-is with a clear explanation, and the --randomize test description was corrected. The one remaining inline comment (pluralization of "test file" using denominator instead of numerator) is a cosmetic grammar nit that does not affect correctness.
There was a problem hiding this comment.
All previously raised issues have been addressed — the empty-shard guard, describe.concurrent, test comment accuracy, and the timeout removal. This PR modifies core test runner execution logic, so it's worth a human pass before landing.
Extended reasoning...
Overview
The PR adds --shard=M/N support across five files: CLI type definitions (cli.zig), argument parsing (Arguments.zig), test execution (test_command.zig), zsh completion, and a new E2E test suite (test-shard.test.ts). The shard logic sorts discovered test files lexicographically then applies a round-robin filter in-place on the mutable test_files slice before test execution.
Security risks
No security concerns — the feature is a test-file selection filter with no I/O beyond what bun test already does. Argument parsing validates both index and count before use.
Level of scrutiny
The change modifies the core test execution flow in test_command.zig, a production-critical path. Several rounds of review found real bugs (empty-shard messaging, describe.concurrent omission, overclaimed test assertions) that have all been fixed. The implementation is now clean and the test coverage is solid, but the test runner is high-impact code and a human pass is appropriate.
Other factors
All my previous inline feedback has been resolved or reasonably declined with clear explanations. No bugs were found by the automated hunting system in the final revision. The PR is well-documented and consistent with existing --changed patterns.
Test files are sorted by path for determinism, then distributed round-robin across N shards (file i goes to shard (i % N) + 1). Index is 1-based to match Jest/Vitest/Playwright conventions. An empty shard (fewer test files than shards) exits 0 instead of reporting 'No tests found'.
Avoids printing a confusing 'running 0/0 test files' before the 'No tests found!' error, and avoids double-messaging when --changed already reported nothing to run.
The --randomize composition test ran 4 runShard calls sequentially, hitting ~4.9s under ASAN with describe.concurrent load — right at the default 5s timeout. Parallelize with Promise.all (now ~2.5s) and add an explicit 20s timeout for headroom.
0bc6de4 to
7aa69c4
Compare
…h#29366) ## What does this PR do? Adds `bun test --shard=<index>/<count>` for splitting test files across multiple CI runners / GitHub Actions jobs. ```sh # In a matrix of 3 jobs: bun test --shard=1/3 bun test --shard=2/3 bun test --shard=3/3 ``` ``` --shard=2/3: running 3/10 test files f01.test.ts: (pass) t f04.test.ts: (pass) t f07.test.ts: (pass) t ``` ### How it works 1. Discovered test files are **sorted by path** so every machine sees the same ordering regardless of filesystem iteration order. 2. Files are distributed **round-robin**: file at sorted index `i` belongs to shard `(i % count) + 1`. This keeps shards balanced to within one file of each other. 3. The shard index is **1-based** (`1 <= index <= count`), matching Jest, Vitest, and Playwright. 4. Composes with `--changed` (sharding is applied after the changed-files filter) and `--randomize` (shuffle happens after shard selection, within the shard). 5. If a shard ends up with zero files (e.g. 2 test files, `--shard=5/5`), it exits `0` with a status line rather than erroring with "No tests found!". Invalid inputs (`0/3`, `4/3`, `1/0`, `abc`, `1/`, `/3`) produce a clear error and exit non-zero. ## How did you verify your code works? - `test/cli/test/test-shard.test.ts` — 13 tests covering partitioning (union == all files, intersection == ∅), determinism across runs, `1/1` running everything, the summary line, empty-shard exit code, and all invalid-input cases. - `bun bd test test/cli/test/test-changed.test.ts` still passes (touched the shared `pass_with_no_tests_from_filter` variable). - Gate: `0/13` pass without the `src/` changes, `13/13` with. Fixes oven-sh#4587 --------- Co-authored-by: robobun <[email protected]> Co-authored-by: Jarred Sumner <[email protected]>
…h#29366) ## What does this PR do? Adds `bun test --shard=<index>/<count>` for splitting test files across multiple CI runners / GitHub Actions jobs. ```sh # In a matrix of 3 jobs: bun test --shard=1/3 bun test --shard=2/3 bun test --shard=3/3 ``` ``` --shard=2/3: running 3/10 test files f01.test.ts: (pass) t f04.test.ts: (pass) t f07.test.ts: (pass) t ``` ### How it works 1. Discovered test files are **sorted by path** so every machine sees the same ordering regardless of filesystem iteration order. 2. Files are distributed **round-robin**: file at sorted index `i` belongs to shard `(i % count) + 1`. This keeps shards balanced to within one file of each other. 3. The shard index is **1-based** (`1 <= index <= count`), matching Jest, Vitest, and Playwright. 4. Composes with `--changed` (sharding is applied after the changed-files filter) and `--randomize` (shuffle happens after shard selection, within the shard). 5. If a shard ends up with zero files (e.g. 2 test files, `--shard=5/5`), it exits `0` with a status line rather than erroring with "No tests found!". Invalid inputs (`0/3`, `4/3`, `1/0`, `abc`, `1/`, `/3`) produce a clear error and exit non-zero. ## How did you verify your code works? - `test/cli/test/test-shard.test.ts` — 13 tests covering partitioning (union == all files, intersection == ∅), determinism across runs, `1/1` running everything, the summary line, empty-shard exit code, and all invalid-input cases. - `bun bd test test/cli/test/test-changed.test.ts` still passes (touched the shared `pass_with_no_tests_from_filter` variable). - Gate: `0/13` pass without the `src/` changes, `13/13` with. Fixes oven-sh#4587 --------- Co-authored-by: robobun <[email protected]> Co-authored-by: Jarred Sumner <[email protected]>
What does this PR do?
Adds
bun test --shard=<index>/<count>for splitting test files across multiple CI runners / GitHub Actions jobs.How it works
ibelongs to shard(i % count) + 1. This keeps shards balanced to within one file of each other.1 <= index <= count), matching Jest, Vitest, and Playwright.--changed(sharding is applied after the changed-files filter) and--randomize(shuffle happens after shard selection, within the shard).--shard=5/5), it exits0with a status line rather than erroring with "No tests found!".Invalid inputs (
0/3,4/3,1/0,abc,1/,/3) produce a clear error and exit non-zero.How did you verify your code works?
test/cli/test/test-shard.test.ts— 13 tests covering partitioning (union == all files, intersection == ∅), determinism across runs,1/1running everything, the summary line, empty-shard exit code, and all invalid-input cases.bun bd test test/cli/test/test-changed.test.tsstill passes (touched the sharedpass_with_no_tests_from_filtervariable).0/13pass without thesrc/changes,13/13with.Fixes #4587