Skip to content
Open
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
4 changes: 3 additions & 1 deletion docs/test/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,9 @@ jobs:

Every shard must read the _same set_ of timings files for the shards to add up to the whole suite. That is why a run reads the previous run's files (restored from the cache), and why it writes its own where sibling shards still in flight won't pick them up (`next/` above). Add `--no-isolate` to the `bun test` line if step 2 applies to you.

**6. Within a file: [`test.concurrent`](#concurrent-test-execution)** for I/O-bound tests that spend their time awaiting.
**6. Fail PRs fast: [`--changed-first`](/test/parallel#running-affected-files-first-with---changed-first).** Same detection as `--changed`, but the affected files run first and the rest still run. Add `--bail` and a PR that breaks what it touched fails without waiting on the rest of the suite.

**7. Within a file: [`test.concurrent`](#concurrent-test-execution)** for I/O-bound tests that spend their time awaiting.

## Performance

Expand Down
11 changes: 11 additions & 0 deletions docs/test/parallel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ The file is plain JSON, slowest first, so it doubles as a "what's slow" report:

You can pass `--timings` more than once. Bun reads the files as one table and skips paths that don't exist yet. `--update-timings` writes to the **first** path. Under `--shard` that output contains only the files the shard ran, so the shards' outputs are disjoint. Read together on the next run, they add up to the whole suite with no merge step. [Large codebases](/test/index#large-codebases) on the main page has the full CI workflow.

## Running affected files first with `--changed-first`

```sh terminal icon="terminal"
bun test --changed-first # uncommitted + untracked vs HEAD
bun test --changed-first=main # vs a branch or commit
```

`--changed-first` uses the same git + module-graph detection as `--changed` to find which test files are affected by your edits, but instead of skipping the rest it moves the affected files to the front of the run. On a PR CI job that still wants full coverage, this surfaces failures in what you touched early; combined with `--bail` the run stops on the first such failure instead of working through the rest of the suite.

Affected files are ordered slowest-first when `--timings` is available, otherwise by path; the remaining files keep the order they would otherwise have. Under `--parallel` the affected files are dispatched from a shared queue first, so every affected file is handed out before any worker starts on its own chunk (an unaffected file may already be running when the last affected one finishes). Under `--shard` each file stays in the shard it would normally belong to and is simply run first within that shard. `--changed` and `--changed-first` cannot be passed together.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## How it compares

2 000 TypeScript test files × 8 small tests each, all importing a small app built on `zod`, `date-fns` and `lodash`, with a shared setup file (custom matcher + `beforeEach`/`afterEach`) loaded via each runner's preload mechanism — the shape of a large application's unit-test suite ([`bench/test/app`](https://github.com/oven-sh/bun/tree/main/bench/test/app), `bun app/setup.ts 2000 20`). 16-core Apple M4 Max:
Expand Down
5 changes: 5 additions & 0 deletions src/options_types/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,10 @@ pub struct TestOptions {
/// Otherwise the value is a git ref (commit, branch, tag) to diff
/// against.
pub changed: Option<Box<[u8]>>,
/// `bun test --changed-first[=<since>]`. Same detection as `changed`,
/// but affected test files are moved to the front of the run instead
/// of the rest being dropped. Mutually exclusive with `changed`.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub changed_first: Option<Box<[u8]>>,
/// `bun test --shard=M/N`. When set, test files are sorted by path
/// and only every Nth file (starting from M-1) is run. index is
/// 1-based; both are validated at parse time so `1 <= index <= count`.
Expand Down Expand Up @@ -509,6 +513,7 @@ impl Default for TestOptions {
parallel_delay_ms: None,
test_worker: false,
changed: None,
changed_first: None,
shard: None,
timings_files: Vec::new(),
update_timings: false,
Expand Down
12 changes: 12 additions & 0 deletions src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,9 @@ pub(crate) const TEST_ONLY_PARAMS: &[ParamType] = &[
parse_param!(
"--changed <STR>? Only run test files affected by changed files according to git. Optionally pass a commit or branch to compare against."
),
parse_param!(
"--changed-first <STR>? Run test files affected by changed files first, then the rest. Same detection as --changed. Optionally pass a commit or branch to compare against."
),
parse_param!(
"--isolate Run each test file in a fresh global object. Leaked handles from one file cannot affect another."
),
Expand Down Expand Up @@ -1902,6 +1905,15 @@ fn parse_test_command_options(args: &clap::Args<clap::Help>, ctx: Context<'_>) {
if let Some(since) = args.option(b"--changed") {
ctx.test_options.changed = Some(since.into());
}
if let Some(since) = args.option(b"--changed-first") {
if ctx.test_options.changed.is_some() {
bun_core::pretty_errorln!(
"<r><red>error<r>: --changed and --changed-first cannot be used together"
);
Global::exit(1);
}
ctx.test_options.changed_first = Some(since.into());
}
if let Some(shard) = args.option(b"--shard") {
let Some(sep) = strings::index_of_char(shard, b'/') else {
bun_core::pretty_errorln!(
Expand Down
Loading
Loading