Skip to content

feat: add setup-mocks, a non-isolated suite with mocking setup files - #2

Draft
hi-ogawa wants to merge 7 commits into
vitest-dev:mainfrom
hi-ogawa:setup-mocks
Draft

hi-ogawa wants to merge 7 commits into
vitest-dev:mainfrom
hi-ogawa:setup-mocks

Conversation

@hi-ogawa

@hi-ogawa hi-ogawa commented Oct 27, 2026 •

Copy link
Copy Markdown

vitest-dev/vitest#11752 reports a 6,600-file suite with isolate: false and setup files that vi.mock modules going from 96 s to 58 s with its changes. In that shape a reused worker runs those setup files again before every test file, so anything Vitest does per file or per run request, such as resolving the setup files and their mock paths or asking the main process for warm modules, repeats hundreds of times per worker, and each round trip waits behind the other workers' calls on the main thread. No existing app has mocking setup files, so none of them can see this cost. This adds setup-mocks, a shared tree of 1500 modules that every test file evaluates, 800 test files that each also import one module of their own, and 6 setup files that each mock a service the tests assert on.

The default matrix runs only isolate: false, because isolate: true re-evaluates the whole tree for every file and takes about a minute per rep. The 25% and 50% maxWorkers rows are there to show how per-file round trips scale with the number of workers. The app passes on the pinned 4.1.10, and the README section leaves its 4.1.10 vs 5.0.0 table for a later measurement.

Benchmark: vitest main vs vitest-dev/vitest#11752

Setup:

  • Machine: Apple M4 Pro, 12 cores, node v24.22.1, no other heavy work running. On 12 cores maxWorkers defaults to 11; 25% is 3 and 50% is 6.
  • Builds: two local vitest builds, both reporting 5.1.0-beta.1: main at 1a77c6cbc (the PR's parent) and the PR head 6fbc6ae58. Each is linked by pointing the app's node_modules/vitest at its packages/vitest.
  • Times are wall time of vitest run.

setup-mocks

The default matrix, pnpm bench --apps setup-mocks --runs 5, in two rounds: main, PR, main, PR. The maxWorkers: 1 row was added to the matrix afterwards and measured on its own with the same protocol. Main and PR are the median of all 10 runs; the last column is the change within each round.

pool fsModuleCache maxWorkers state main PR Δ Δ per round
forks false 11 (default) cold 5.67s 3.84s −32% −27%, −34%
forks false 11 (default) warm 5.21s 3.76s −28% −21%, −29%
forks true 11 (default) cold 5.68s 4.10s −28% −24%, −27%
forks true 11 (default) warm 3.20s 1.92s −40% −42%, −37%
threads false 11 (default) cold 2.68s 2.17s −19% −19%, −16%
threads false 11 (default) warm 2.61s 2.20s −16% −13%, −16%
threads true 11 (default) cold 4.75s 3.72s −22% −24%, −21%
threads true 11 (default) warm 2.79s 1.74s −38% −39%, −35%
forks false 3 (25%) warm 5.59s 3.57s −36% −38%, −33%
forks false 6 (50%) warm 5.07s 3.69s −27% −27%, −27%
forks false 1 warm 5.72s 4.89s −15% −12%, −16%

The PR is faster in every cell in both rounds: by 16–40% on the pooled medians and 13–42% within single rounds, and the two rounds agree within 8 points. The largest gains are with warm fsModuleCache (−40% forks, −38% threads) and with forks at 25% maxWorkers (−36%).

With maxWorkers: 1, all files go to the one worker as a single run request, so the PR's kept snapshot changes nothing there (main fetches it once too). The −15% comes from the resolve cache alone: setup files and their vi.mock paths stop going to the main process before every file. With 3–11 workers the same cell (forks, no fsModuleCache, warm) gains −27% to −36%, so roughly half the gain there needs more than one worker. On main, the one-worker run (5.72 s) is barely slower than 11 workers (5.21 s).

Existing apps

To see how the PR does on suites without mocking setup files: seven cells of existing apps, all env: node, default maxWorkers, warm (caches wiped, one untimed priming run, then timed runs, median). Main and PR alternate per cell. The two enterprise-monolith isolate: false cells moved by −16% and +11% at 3 runs, so they were rerun with 10 runs per build, in four blocks (main, PR, main, PR) with caches wiped and re-primed for each block.

app pool isolate fsModuleCache runs main PR Δ
node-library forks false true 3 0.34s 0.33s ~0
enterprise-monolith forks false false 10 3.77s 3.82s ~0
enterprise-monolith threads false true 10 2.58s 2.51s −3%
barrel-hell threads false true 3 0.69s 0.68s ~0
deps-heavy vmThreads false false 3 0.72s 0.72s ~0
node-library (control) forks true false 3 0.72s 0.69s −3%
enterprise-monolith (control) forks true false 3 6.78s 6.51s −4%

Every cell is flat. The isolate: true controls can't change by construction, since each file gets a fresh worker whose caches start empty, so their −3% and −4% are noise: the runs overlap (enterprise-monolith on main alone spans 5.85–7.12 s). Single runs of enterprise-monolith spread by up to ~20% (forks on main 3.53–4.28 s), which is why its 3-run medians moved. The PR neither helps nor hurts these suites; its gain shows up in the setup-mocks shape, where every file re-runs setup files that resolve and mock modules.

late-graphs (loss case for keeping one snapshot per worker)

This also adds late-graphs: 4 trivial test files that sort first, so each of the 4 workers starts with one, then 24 test files that share 12 chains of 250 modules, so the second dozen reuses chains the first dozen loaded, mostly on other workers. It targets vitest-dev/vitest#11761, which fetches the warm-modules snapshot once per worker: the server builds a snapshot from the requesting files' graphs, so a worker whose first file is trivial keeps a snapshot that misses the chains its later files reuse. vitest-dev/vitest#11760 (resolve cache only) is the control. Forks, isolate: false, vitest run, median of 3 runs.

pool fsModuleCache state main #11760 Δ #11761 Δ
forks false cold 2.20s 2.23s ~0 2.22s ~0
forks false warm 2.17s 2.24s +3% 2.15s ~0
forks true cold 2.52s 2.35s −7% 2.45s −3%
forks true warm 1.30s 1.32s ~0 1.30s ~0

#11761 does fetch those chains module by module, but at this size it costs no measurable time. For comparison, on setup-mocks #11761 alone is −25…−26% with several workers and ~0 with one, and #11760 alone is −10…−11%.

hi-ogawa and others added 5 commits October 27, 2026 12:32
A 1500-module shared tree, 800 test files, and 6 setup files that each
vi.mock a service. A reused worker re-runs the setup files before every
file, so per-file and per-run-request costs show up here and nowhere else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
hi-ogawa and others added 2 commits October 27, 2026 18:02
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant