Repository navigation
Conversation
…y a glob Files that match a project's testMatch run together in their own global with [test] preload first and the project's preload after it. The serial loop and the parallel worker both derive the project from the file path and swap the global only when the project changes. load_preloads now consumes vm.preload once per global and the runner refills it after every swap.
|
Updated 2:02 PM PT - Oct 15th, 2026
✅ @robobun, your commit 814a32f1b3e85c8407ee9030afa6cc31d704bbf2 passed in 🧪 To try this PR locally: bunx bun-pr 45379That installs a local version of the PR into your bun-45379 --bun |
| /// `[[test.projects]]`: each table needs a unique `name` and a | ||
| /// `testMatch`. `preload` is optional. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// `[[test.projects]]` in bunfig.toml, in file order. A test file belongs | ||
| /// to the first project whose `testMatch` matches it, else to the base | ||
| /// run. Each project runs in its own global with `[test] preload` plus | ||
| /// its own `preload`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// Globs matched against the test file path relative to the project | ||
| /// root, like `pathIgnorePatterns`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| //! `[[test.projects]]`: which project a test file belongs to, and which | ||
| //! preloads its global needs. | ||
| //! | ||
| //! Files are grouped by project and each group runs in its own global. | ||
| //! The serial loop and the parallel worker both derive the key from the | ||
| //! path with [`TestProjects::key_for`] and swap the global when the key | ||
| //! changes. `[test] preload` runs first in every global, then the | ||
| //! project's own `preload`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// Index into the `[[test.projects]]` table plus one. `BASE_KEY` is a file | ||
| /// that matches no project: it runs with `[test] preload` only. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// `preloads` and `projects` are the process-lifetime CLI context's | ||
| /// fields, so the views never dangle. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// The first project whose `testMatch` matches `abs_path` relative to | ||
| /// the project root, else `BASE_KEY`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// The preload list a fresh global needs before it runs a file with | ||
| /// this key: `[test] preload`, then the project's own. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// Stable partition of `files` by key, so each project's files are | ||
| /// contiguous and the global is swapped once per project. The order | ||
| /// inside a group is kept (`--randomize` and `--shard` already ran). |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // Keep each project's files contiguous so a worker swaps its global | ||
| // (and re-runs the preloads) as rarely as possible. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // `fresh`: the global has not loaded its preloads yet. `cur_key`: | ||
| // the project whose preloads the current global loaded. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // Without --isolate a worker keeps its global across files and | ||
| // swaps it only when the next file belongs to another project. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // `vm.preload` is filled per global by the file loops (`run_all_tests` | ||
| // and the parallel worker) from `reporter.projects`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // `[[test.projects]]` swaps the global between project groups with | ||
| // the same machinery `--isolate` uses between files. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // The global is fresh before the first file and after every | ||
| // swap: its preloads have not run yet, so the first file in | ||
| // it loads them and runs the preload-level `beforeAll`. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// Retires the global the last file ran in and creates a fresh one. | ||
| /// The caller sets `vm.preload` for the next file: `load_preloads` | ||
| /// consumes the list once per global. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// `[[test.projects]]`, in file order. A file belongs to the first | ||
| /// project whose `testMatch` matches it. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
Fixes #44642
Status: waits for #44833. That draft adds
[test] environment,--environmentand the@jest-environment/@vitest-environmentdocblocks, switches the DOM per file without--isolate, and makes the builtins immune to DOM globals. It covers the reporter's DOM versus server case and changes the same runner files as this PR. After it lands, this PR is rebased onto itsEnvironmentplumbing as a thin[[test.projects]]layer for preloads that are not a DOM (a database, a server mock) scoped to a glob. The rework plan is in Notes.Problem
bun testhas one process-widepreloadlist. A happy-dom registration in[test] preload(the setupdocs/test/dom.mdxrecommends) replacesfetch,Request,Response,URLand the timers for every file, so server tests in the same repo break.--isolatefor the whole suite with a preload that branches onBun.main. The reporter measured the second at 6 to 9x slower.Fix
[[test.projects]]to bunfig:name,testMatch(globs, relative to the project root likepathIgnorePatterns),preload. A file belongs to the first project that matches it. Files that match no project run with[test] preloadonly.--randomizeand--shard. Each group runs in its own global: the runner swaps the global at a group boundary with the same machinery--isolateuses between files, then loads[test] preloadand the project'spreloadinto the fresh global.load_preloads(src/runtime/jsc_hooks.rs) now consumesvm.preloadonce per global. The serial loop (src/runtime/cli/test_command.rs) and the parallel worker (src/runtime/cli/test/parallel/runner.rs) refill it after every swap. Both derive the project key from the path with one function (src/runtime/cli/test/Projects.rs). A worker swaps lazily when the next file's project differs.test/cli/test/test-projects.test.ts(6 tests, all fail on the released bun). Alsotest/cli/test/isolation.test.ts,parallel.test.ts,path-ignore-patterns.test.ts,concurrent-test-glob.test.ts,test-randomize.test.ts,test-shard.test.ts,rerun-each.test.ts,bun-test.test.ts.Background
--isolategives each test file a fresh JS global on the same VM:swap_global_for_test_isolationstops leaked handles, retires the old global and creates a new one. It assertsvm.test_isolation_enabled, which this PR sets when the projects table is non-empty.FirstLasttellsbun:testwhether a file is the first or last in its global, so preload-levelbeforeAllandafterAllrun once per global. Groups set it from the group bounds. A--parallelworker keepsfirst: true, last: trueas before.[[test.environments]]glob table with a per-entryisolateflag, or a per-file docblock. The review weighed both. A per-entryisolateflag fights the run-wide isolation state and re-runs the shared preload after every isolated file, the slowdown the reporter wants to avoid. A docblock costs everybun testuser one file read per test file. The projects table is read once per process, matches Jest and Vitestprojects, and groups files so the shared preload runs once per group.Downsides
beforeAll/afterAllrun once per project instead of once per run, and each group boundary pays one global swap (the--isolateper-file cost, paid once per project). Not measured: hyperfine is not available in the build container.[test] preloadand no table:run_all_testsclones the preload list once per run (oneVecplus oneBox<[u8]>per preload), the clone that VM init did before. Per file: zero allocations and zero syscalls,key_forreturns at theis_emptycheck. Under--isolate, oneVecplus oneBoxper preload per file replaces a cached re-import of the same list. Binary size not measured: bloaty is not available in the container.--parallelworkers read[[test.projects]]from their own bunfig. A coordinator run with--config=<other file>does not forward it to workers (pre-existing for every[test]key).Notes
Rework plan once #44833 lands:
Rebase onto its
Environmentplumbing. A project entry becomes{ name?, testMatch, environment?, preload? }.namebecomes optional: nothing consumes it today.Stop overloading
vm.test_isolation_enabledfor the group swap: add a distinct swaps-globals flag, and restore theif !test_isolation_enabled { preload.clear() }guard inload_preloads.Fold the swap, the hook-scope reset and the
vm.preloadrefill into one project-keyed helper used by the serial loop, the worker loop and the--rerun-eachboundary from bun test: give each --rerun-each iteration a fresh global under --isolate #43184.Keep
Projects.rs(key_for,group,preloads_for) and the stable partition after--randomizeand--shard.Point
docs/test/dom.mdxat[test] environmentand the docblocks for the DOM case. Reserve projects for other preloads.jsc::initialize(short_lived_globals)stays tied to--isolate. It is a JIT tier-up threshold. Project globals are long-lived.reporter.projects.group()is a stable sort by key, so the order inside a group is the one--randomizeor--shardproduced. The parallel coordinator sorts lexicographically first and then groups, so each worker's contiguous chunk stays inside one project where possible.A worker that steals a file from another project swaps its global before that file and again when it returns (tested:
a --parallel worker swaps its global when the project changes).--rerun-eachrepeats a file insideTestCommand::runwithout a swap. The preload list is already consumed, so repeats do not re-import it. Under--isolatethe old code re-imported the (cached) list on each repeat with no visible effect.Probed on the released bun (1.4.3): bunfig
preload = ["./dom-preload.ts"]where the preload setsglobalThis.window, thenserver.test.tsassertingtypeof window === "undefined"fails. No existing key scopes the preload.[human-review] gate passed · iteration 0 · 12 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 0 rejected · iteration 0
evidence per changed file
root cause · written by the author bot
The underlying problem is that
bun testhas a single process-widepreloadlist, so a DOM registrator such as happy-dom replaces Bun's realfetch,URL,Request, timers and similar globals for every file in the run, forcing repositories with both server and DOM tests into two separate invocations with duplicated config. The proposed change adds[[test.projects]]entries keyed bytestMatchglobs, partitions files by their first matching project, and runs each group's preloads in a freshly swapped global at group boundaries so the DOM registration cannot leak into server files. Th…