···670670671671## bench <Experimental /> {#bench}
672672673673-- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void`
673673+::: warning Updated in Vitest 5
674674+The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`, and the `bench.skip` / `bench.only` / `bench.todo` helpers have been removed. `bench` is now a [test-context fixture](/guide/test-context#bench) accessed from inside a `test()`.
674675675675-::: danger
676676-Benchmarking is experimental and does not follow SemVer.
676676+See the [Benchmarking guide](/guide/benchmarking) for the new API.
677677:::
678678-679679-`bench` defines a benchmark. In Vitest terms, benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results.
680680-681681-Vitest uses the [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument.
682682-683683-```ts
684684-import { bench } from 'vitest'
685685-686686-bench('normal sorting', () => {
687687- const x = [1, 5, 4, 2, 3]
688688- x.sort((a, b) => {
689689- return a - b
690690- })
691691-}, { time: 1000 })
692692-```
693693-694694-```ts
695695-export interface Options {
696696- /**
697697- * time needed for running a benchmark task (milliseconds)
698698- * @default 500
699699- */
700700- time?: number
701701-702702- /**
703703- * number of times that a task should run if even the time option is finished
704704- * @default 10
705705- */
706706- iterations?: number
707707-708708- /**
709709- * function to get the current timestamp in milliseconds
710710- */
711711- now?: () => number
712712-713713- /**
714714- * An AbortSignal for aborting the benchmark
715715- */
716716- signal?: AbortSignal
717717-718718- /**
719719- * Throw if a task fails (events will not work if true)
720720- */
721721- throws?: boolean
722722-723723- /**
724724- * warmup time (milliseconds)
725725- * @default 100ms
726726- */
727727- warmupTime?: number
728728-729729- /**
730730- * warmup iterations
731731- * @default 5
732732- */
733733- warmupIterations?: number
734734-735735- /**
736736- * setup function to run before each benchmark task (cycle)
737737- */
738738- setup?: Hook
739739-740740- /**
741741- * teardown function to run after each benchmark task (cycle)
742742- */
743743- teardown?: Hook
744744-}
745745-```
746746-After the test case is run, the output structure information is as follows:
747747-748748-```
749749- name hz min max mean p75 p99 p995 p999 rme samples
750750-· normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638
751751-```
752752-```ts
753753-export interface TaskResult {
754754- /*
755755- * the last error that was thrown while running the task
756756- */
757757- error?: unknown
758758-759759- /**
760760- * The amount of time in milliseconds to run the benchmark task (cycle).
761761- */
762762- totalTime: number
763763-764764- /**
765765- * the minimum value in the samples
766766- */
767767- min: number
768768- /**
769769- * the maximum value in the samples
770770- */
771771- max: number
772772-773773- /**
774774- * the number of operations per second
775775- */
776776- hz: number
777777-778778- /**
779779- * how long each operation takes (ms)
780780- */
781781- period: number
782782-783783- /**
784784- * task samples of each task iteration time (ms)
785785- */
786786- samples: number[]
787787-788788- /**
789789- * samples mean/average (estimate of the population mean)
790790- */
791791- mean: number
792792-793793- /**
794794- * samples variance (estimate of the population variance)
795795- */
796796- variance: number
797797-798798- /**
799799- * samples standard deviation (estimate of the population standard deviation)
800800- */
801801- sd: number
802802-803803- /**
804804- * standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
805805- */
806806- sem: number
807807-808808- /**
809809- * degrees of freedom
810810- */
811811- df: number
812812-813813- /**
814814- * critical value of the samples
815815- */
816816- critical: number
817817-818818- /**
819819- * margin of error
820820- */
821821- moe: number
822822-823823- /**
824824- * relative margin of error
825825- */
826826- rme: number
827827-828828- /**
829829- * median absolute deviation
830830- */
831831- mad: number
832832-833833- /**
834834- * p50/median percentile
835835- */
836836- p50: number
837837-838838- /**
839839- * p75 percentile
840840- */
841841- p75: number
842842-843843- /**
844844- * p99 percentile
845845- */
846846- p99: number
847847-848848- /**
849849- * p995 percentile
850850- */
851851- p995: number
852852-853853- /**
854854- * p999 percentile
855855- */
856856- p999: number
857857-}
858858-```
859859-860860-### bench.skip
861861-862862-- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void`
863863-864864-You can use `bench.skip` syntax to skip running certain benchmarks.
865865-866866-```ts
867867-import { bench } from 'vitest'
868868-869869-bench.skip('normal sorting', () => {
870870- const x = [1, 5, 4, 2, 3]
871871- x.sort((a, b) => {
872872- return a - b
873873- })
874874-})
875875-```
876876-877877-### bench.only
878878-879879-- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void`
880880-881881-Use `bench.only` to only run certain benchmarks in a given suite. This is useful when debugging.
882882-883883-```ts
884884-import { bench } from 'vitest'
885885-886886-bench.only('normal sorting', () => {
887887- const x = [1, 5, 4, 2, 3]
888888- x.sort((a, b) => {
889889- return a - b
890890- })
891891-})
892892-```
893893-894894-### bench.todo
895895-896896-- **Type:** `(name: string | Function) => void`
897897-898898-Use `bench.todo` to stub benchmarks to be implemented later.
899899-900900-```ts
901901-import { bench } from 'vitest'
902902-903903-bench.todo('unimplemented test')
904904-```
+15-29
docs/config/benchmark.md
···991010Options used when running `vitest bench`.
11111212+## benchmark.enabled
1313+1414+- **Type:** `boolean`
1515+- **Default:** `false`
1616+1717+Enables the benchmark project. When set, Vitest creates a dedicated benchmark project alongside your regular test project, runs files matching [`benchmark.include`](#benchmark-include) in it, and exposes the [`bench` fixture](/guide/test-context#bench) to those files. Running `vitest bench` enables this automatically.
1818+1219## benchmark.include
13201421- **Type:** `string[]`
···32393340When defined, Vitest will run all matched files with `import.meta.vitest` inside.
34413535-## benchmark.reporters
4242+## benchmark.retainSamples
36433737-- **Type:** `Arrayable<BenchmarkBuiltinReporters | Reporter>`
3838-- **Default:** `'default'`
4444+- **Type:** `boolean`
4545+- **Default:** `false`
39464040-Custom reporter for output. Can contain one or more built-in report names, reporter instances, and/or paths to custom reporters.
4747+Include the `samples` array of per-iteration timings on every benchmark result. Disabled by default to reduce memory usage; enable when a custom reporter or API consumer needs the raw samples.
41484242-## benchmark.outputFile
43494444-Deprecated in favor of `benchmark.outputJson`.
5050+## benchmark.suppressExportGetterWarnings
45514646-## benchmark.outputJson {#benchmark-outputJson}
5252+- **Type:** `boolean`
5353+- **Default:** `false`
47544848-- **Type:** `string | undefined`
4949-- **Default:** `undefined`
5555+Suppress the warning printed when a benchmark accesses module export getters too many times. Vitest tracks getter access during benchmark runs because Vite's module runner wraps every export in a getter, and excessive access can dominate the measurement (see [Module Runner Overhead](/guide/benchmarking#module-runner-overhead)). Enable this when you've intentionally accepted the overhead, or when the warning is noisy for benchmarks where the getter cost is negligible.
50565151-A file path to store the benchmark result, which can be used for `--compare` option later.
5252-5353-For example:
5454-5555-```sh
5656-# save main branch's result
5757-git checkout main
5858-vitest bench --outputJson main.json
5959-6060-# change a branch and compare against main
6161-git checkout feature
6262-vitest bench --compare main.json
6363-```
6464-6565-## benchmark.compare {#benchmark-compare}
6666-6767-- **Type:** `string | undefined`
6868-- **Default:** `undefined`
6969-7070-A file path to a previous benchmark result to compare against current runs.
+1-1
docs/config/index.md
···8899- Create `vitest.config.ts`, which will have the higher priority and will **override** the configuration from `vite.config.ts` (Vitest supports all conventional JS and TS extensions, but doesn't support `json`) - it means all options in your `vite.config` will be **ignored**
1010- Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts`
1111-- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test`/`benchmark` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests
1111+- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests
12121313To configure `vitest` itself, add `test` property in your Vite config. You'll also need to add a reference to Vitest types using a [triple slash command](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of your config file, if you are importing `defineConfig` from `vite` itself.
1414
-1
docs/config/runner.md
···66# runner
7788- **Type:** `VitestRunnerConstructor`
99-- **Default:** `node`, when running tests, or `benchmark`, when running benchmarks
1091110Path to a custom test runner. This is an advanced feature and should be used with custom library runners. You can read more about it in [the documentation](/api/advanced/runner).
+480
docs/guide/benchmarking.md
···11+---
22+title: Benchmarking | Guide
33+---
44+55+# Benchmarking
66+77+Vitest lets you write benchmarks alongside your tests using the `bench` fixture from the [test context](/guide/test-context). Benchmarks are powered by [Tinybench](https://github.com/tinylibs/tinybench) and are defined inside regular `test()` calls, giving you access to the full power of Vitest's test runner: retries, lifecycle hooks, filtering, and assertions.
88+99+## Defining a Benchmark
1010+1111+Use the `bench` fixture to define a benchmark. Call `.run()` to execute it:
1212+1313+```ts
1414+import { expect, test } from 'vitest'
1515+1616+test('parsing performance', async ({ bench }) => {
1717+ const result = await bench('parse', () => {
1818+ JSON.parse('{"key":"value"}')
1919+ }).run()
2020+})
2121+```
2222+2323+The `bench()` function registers a benchmark without executing it. Calling `.run()` runs the benchmark and returns the result. After the test completes, Vitest prints a single-row version of the [comparison table](#comparing-benchmarks) (ops/sec, mean time, percentiles, etc.), so you get the same output for a one-off benchmark as you do for `bench.compare()`.
2424+2525+::: warning
2626+The `bench` fixture is only available in files matched by [`benchmark.include`](/config/#benchmark-include) (default: `**/*.{bench,benchmark}.?(c|m)[jt]s?(x)`). Using `{ bench }` inside a regular test file will throw an error.
2727+2828+Whether a file participates in the benchmark run is decided by the filename, not by whether the test uses the `bench` fixture. Renaming `parser.test.ts` to `parser.bench.ts` (or adjusting `benchmark.include`) is what moves it into the benchmark project.
2929+:::
3030+3131+## Running Benchmarks
3232+3333+Benchmark files are matched by [`benchmark.include`](/config/#benchmark-include) (default: `**/*.{bench,benchmark}.?(c|m)[jt]s?(x)`) and run in their own project, separate from your regular tests. There are three ways to run them, depending on whether you want to skip them, run them alongside tests, or run them on their own.
3434+3535+### `vitest` (default)
3636+3737+Without [`benchmark.enabled`](/config/#benchmark-enabled), the `vitest` command only runs regular tests. Benchmark files are ignored entirely. This is the default and the right choice for day-to-day development, since benchmarks are slow and noisy and shouldn't run on every save.
3838+3939+### `vitest` with `benchmark.enabled`
4040+4141+Set `benchmark.enabled: true` in your config to run benchmarks together with regular tests:
4242+4343+```ts [vitest.config.ts]
4444+import { defineConfig } from 'vitest/config'
4545+4646+export default defineConfig({
4747+ test: {
4848+ benchmark: {
4949+ enabled: true,
5050+ },
5151+ },
5252+})
5353+```
5454+5555+With this config, `vitest` runs your regular tests first, then runs the benchmarks in a separate isolated group (so benchmark execution never overlaps with test execution and adds noise to results). Useful in CI when you want a single command to validate correctness and performance.
5656+5757+### `vitest bench`
5858+5959+The `bench` subcommand runs only benchmarks and skips regular tests:
6060+6161+```bash
6262+vitest bench
6363+```
6464+6565+This implicitly enables `benchmark.enabled` for the run, so you don't need to set it in the config. Like the `vitest` command, it accepts filename filters and `-t`/`--testNamePattern` to narrow the run:
6666+6767+```bash
6868+# only benchmarks in files matching "parser"
6969+vitest bench parser
7070+7171+# only benchmarks whose test name matches "JSON"
7272+vitest bench -t JSON
7373+```
7474+7575+## Comparing Benchmarks
7676+7777+Use `bench.compare()` to compare multiple benchmarks against each other:
7878+7979+```ts
8080+import { expect, test } from 'vitest'
8181+8282+test('compare JSON libraries', async ({ bench }) => {
8383+ const input = '{"key":"value","nested":{"a":1}}'
8484+8585+ const result = await bench.compare(
8686+ bench('JSON.parse', () => {
8787+ JSON.parse(input)
8888+ }),
8989+ bench('custom parser', () => {
9090+ customParse(input)
9191+ }),
9292+ )
9393+})
9494+```
9595+9696+When comparing benchmarks, Vitest runs them using interleaved iterations to reduce environmental bias (CPU throttling, GC pressure, etc.) and prints a comparison table after the test completes:
9797+9898+<<< ./snippets/benchmark-table.ansi
9999+100100+### Options
101101+102102+You can pass [options](https://tinylibs.github.io/tinybench/interfaces/BenchOptions.html) as the last argument to `bench.compare()`:
103103+104104+```ts
105105+test('compare with options', async ({ bench }) => {
106106+ const result = await bench.compare(
107107+ bench('lib1', () => { lib1() }),
108108+ bench('lib2', () => { lib2() }),
109109+ {
110110+ iterations: 100,
111111+ time: 1000,
112112+ },
113113+ )
114114+})
115115+```
116116+117117+You can also pass per-benchmark [options](https://tinylibs.github.io/tinybench/interfaces/FnOptions.html) as the second argument, matching how `test()` accepts options:
118118+119119+```ts
120120+test('benchmarks with setup', async ({ bench }) => {
121121+ const result = await bench.compare(
122122+ bench('with-cache', () => {
123123+ readFromCache()
124124+ }),
125125+ bench(
126126+ 'without-cache',
127127+ { beforeEach: () => clearCache() },
128128+ () => { readFromDisk() },
129129+ ),
130130+ )
131131+})
132132+```
133133+134134+## Comparing Across Projects
135135+136136+When your workspace defines multiple projects (e.g., different browsers or runtimes), pass `perProject: true` in the bench options to compare how the same benchmark performs across all of them. Vitest still prints the result inline for the current project, and additionally collects per-project results into a single comparison table at the end of the test run.
137137+138138+```ts
139139+import { test } from 'vitest'
140140+141141+test('simple example', async ({ bench }) => {
142142+ await bench('1 + 1', { perProject: true }, () => {
143143+ 1 + 1
144144+ }).run()
145145+})
146146+```
147147+148148+The same test file runs in each project (chromium, firefox, webkit, etc.), and Vitest groups the results:
149149+150150+<<< ./snippets/benchmark-per-project.ansi
151151+152152+You can also mix `perProject` benchmarks with regular ones inside `bench.compare()`:
153153+154154+```ts
155155+test('compare implementations across browsers', async ({ bench }) => {
156156+ await bench.compare(
157157+ bench('JSON.parse', { perProject: true }, () => {
158158+ JSON.parse('{"key":"value"}')
159159+ }),
160160+ bench('custom parser', () => {
161161+ customParse('{"key":"value"}')
162162+ }),
163163+ )
164164+})
165165+```
166166+167167+In this case, `custom parser` appears in the normal inline comparison table per project, while `JSON.parse` is additionally collected into the cross-project comparison table at the end.
168168+169169+## Asserting Performance
170170+171171+Use `toBeFasterThan()` and `toBeSlowerThan()` matchers to assert relative performance between benchmarks:
172172+173173+```ts
174174+import { expect, test } from 'vitest'
175175+176176+test('lib1 is faster than lib2', async ({ bench }) => {
177177+ const result = await bench.compare(
178178+ bench('lib1', () => { lib1() }),
179179+ bench('lib2', () => { lib2() }),
180180+ )
181181+182182+ expect(result.get('lib1')).toBeFasterThan(result.get('lib2'))
183183+})
184184+```
185185+186186+The `delta` option specifies the minimum relative difference required for the assertion to pass. This helps avoid flaky tests caused by benchmark noise:
187187+188188+```ts
189189+// lib1 must be at least 10% faster than lib2
190190+expect(result.get('lib1')).toBeFasterThan(result.get('lib2'), {
191191+ delta: 0.1,
192192+})
193193+194194+// lib2 must be at least 20% slower than lib1
195195+expect(result.get('lib2')).toBeSlowerThan(result.get('lib1'), {
196196+ delta: 0.2,
197197+})
198198+```
199199+200200+You can also assert absolute performance using standard matchers:
201201+202202+```ts
203203+test('parsing is fast enough', async ({ bench }) => {
204204+ const result = await bench('parse', () => {
205205+ parse(largeInput)
206206+ }).run()
207207+208208+ expect(result.throughput.mean).toBeGreaterThan(10_000)
209209+})
210210+```
211211+212212+## Retries
213213+214214+Since benchmarks can be noisy, use the `retry` option to automatically retry failing benchmark tests:
215215+216216+```ts
217217+test('performance comparison', { retry: 3 }, async ({ bench }) => {
218218+ const result = await bench.compare(
219219+ bench('lib1', () => { lib1() }),
220220+ bench('lib2', () => { lib2() }),
221221+ )
222222+223223+ expect(result.get('lib1')).toBeFasterThan(result.get('lib2'))
224224+})
225225+```
226226+227227+## Storing and Replaying Results
228228+229229+Two primitives let you persist benchmark results to disk and compare against them in future runs: the `writeResult` option saves a result, and `bench.from()` reads one back.
230230+231231+### `writeResult`
232232+233233+Pass `writeResult` as a per-bench option to write the result to a JSON file every time the benchmark runs. The path is resolved against the project root:
234234+235235+```ts
236236+test('parse', async ({ bench }) => {
237237+ await bench(
238238+ 'parse',
239239+ { writeResult: './benchmarks/parse.json' },
240240+ () => parse(largeInput),
241241+ ).run()
242242+})
243243+```
244244+245245+- The benchmark always runs. There is no skip-when-cached behaviour and no CLI flag, the file is overwritten on every successful run.
246246+- If the function throws, the file is not written.
247247+- Commit these files alongside your code so reviewers and CI share the same reference points.
248248+249249+::: warning
250250+If you commit these files, keep in mind that benchmark results vary significantly between environments (developer machines, CI runners, different OSes). Designate a single environment (typically CI) to generate the file, and avoid regenerating it locally.
251251+:::
252252+253253+### `bench.from()`
254254+255255+`bench.from(name, source)` is a registration that doesn't execute a function. It reads a previously stored result and feeds it into `bench.compare()` (or returns it directly when you call `.run()`).
256256+257257+The source can be a path (relative to the project root) or a function that returns the result data, including a Promise:
258258+259259+```ts
260260+test('compare against the stored baseline', async ({ bench }) => {
261261+ const result = await bench.compare(
262262+ bench(
263263+ 'current',
264264+ { writeResult: './benchmarks/parse.json' },
265265+ () => parse(largeInput),
266266+ ),
267267+ bench.from('previous', './benchmarks/parse.json'),
268268+ bench.from('remote', () => fetch('https://path/to/external/file.json').then(r => r.json())),
269269+ )
270270+271271+ expect(result.get('current')).toBeFasterThan(result.get('previous'))
272272+})
273273+```
274274+275275+You can keep historical artifacts for older versions and compare them against the current implementation. Because `bench.from()` never invokes the function that produced the file, the original benchmark code can be deleted once the artifact is committed:
276276+277277+```ts
278278+test('compare parser versions', async ({ bench }) => {
279279+ const input = '{"key":"value"}'
280280+281281+ await bench.compare(
282282+ bench.from('v1', './benchmarks/parse.v1.json'),
283283+ bench.from('v2', './benchmarks/parse.v2.json'),
284284+ bench(
285285+ 'current',
286286+ { writeResult: './benchmarks/parse.current.json' },
287287+ () => customParser(input),
288288+ ),
289289+ )
290290+})
291291+```
292292+293293+To produce a new historical artifact, point a fresh `bench()` at that version's implementation, set `writeResult` to a versioned path (`./benchmarks/parse.v3.json`), run it once, then replace the call with `bench.from('v3', './benchmarks/parse.v3.json')`.
294294+295295+To regenerate the baseline on demand, gate the write behind an environment variable so the same test either refreshes the artifact or compares against it:
296296+297297+```ts
298298+test('compare parser versions', async ({ bench }) => {
299299+ if (import.meta.env.VITE_WRITE_BENCH) {
300300+ const baseline = bench('baseline', { writeResult: './my-bench.json' }, () => fn())
301301+ await baseline.run()
302302+ }
303303+ else {
304304+ const baseline = bench.from('baseline', './my-bench.json')
305305+ await bench.compare(bench('current', () => fn()), baseline)
306306+ }
307307+})
308308+```
309309+310310+Run `VITE_WRITE_BENCH=1 vitest bench` to refresh the stored result, and `vitest bench` to compare the current implementation against it.
311311+312312+### Per-project artifacts
313313+314314+In a multi-project workspace (different browsers, different runtimes), share one benchmark file across projects by including `${projectName}` in the path. The placeholder is substituted with the current project name at write time:
315315+316316+```ts
317317+test('cross-project baseline', async ({ bench }) => {
318318+ await bench(
319319+ 'parse',
320320+ // eslint-disable-next-line no-template-curly-in-string
321321+ { perProject: true, writeResult: './benchmarks/parse.${projectName}.json' },
322322+ () => parse(largeInput),
323323+ ).run()
324324+})
325325+```
326326+327327+Use the same template in `bench.from()` so each project reads its own artifact.
328328+329329+## Stability
330330+331331+Benchmarks are inherently flaky: CPU load, thermal throttling, GC pressure, and background processes all affect results. Vitest takes several steps to minimize this noise:
332332+333333+- **Separate project**: Benchmark files are grouped into their own project based on the [`benchmark.include`](/config/#benchmark-include) pattern. The `bench` fixture is only exposed in files matched by that pattern. Using it inside a regular test file will throw an error.
334334+- **No concurrency**: Tests within a benchmark file always run sequentially. Benchmark files themselves also run one at a time, never in parallel. This prevents benchmarks from interfering with each other.
335335+336336+To further improve stability:
337337+338338+- Use the [`retry`](#retries) option to automatically rerun flaky benchmark assertions.
339339+- Use the [`delta`](#asserting-performance) option in `toBeFasterThan` / `toBeSlowerThan` to allow for acceptable variance.
340340+- Avoid running benchmarks alongside CPU-intensive processes.
341341+- Close browsers, IDEs, and other applications that compete for CPU time.
342342+343343+### Dead Code Elimination
344344+345345+JavaScript engines can optimize away code that has no observable side effects. If your benchmark function doesn't use its result, the engine may skip the computation entirely, producing misleadingly fast numbers:
346346+347347+```ts
348348+test('parsing', async ({ bench }) => {
349349+ // BAD: the engine may eliminate the work
350350+ await bench('parse', () => {
351351+ JSON.parse(input)
352352+ }).run()
353353+354354+ // GOOD: the result is consumed
355355+ await bench('parse', () => {
356356+ const result = JSON.parse(input)
357357+ doSomething(result)
358358+ }).run()
359359+})
360360+```
361361+362362+This applies to all engines (V8, JavaScriptCore, SpiderMonkey) but is especially aggressive in V8's TurboFan and JavaScriptCore's FTL compiler tiers.
363363+364364+### Module Runner Overhead
365365+366366+By default, Vitest runs tests in Node.js using Vite's module runner (configured by [`experimental.viteModuleRunner`](/config/experimental#experimental-vitemodulerunner)). This transforms all module exports into getters, so every access to an imported binding goes through something like `__vite_ssr_module__.value`. In regular tests this overhead is negligible, but in benchmarks where a function is called millions of times, the getter call itself can dominate the measurement.
367367+368368+Vitest will print a warning if it detects excessive getter calls (which you can silence via [`benchmark.suppressExportGetterWarnings`](/config/benchmark#benchmark-suppressexportgetterwarnings)), but you should be aware of this when benchmarking imported functions:
369369+370370+```ts
371371+import { parse } from './parser.js'
372372+373373+const _parse = parse
374374+375375+test('parsing', async ({ bench }) => {
376376+ // BAD: every call to `parse` goes through a getter
377377+ await bench('parse', () => {
378378+ parse(input)
379379+ }).run()
380380+381381+ // GOOD: store the reference locally to bypass the getter
382382+ await bench('parse', () => {
383383+ _parse(input)
384384+ }).run()
385385+})
386386+```
387387+388388+If you are the library author, the same overhead applies inside the library you are benchmarking: every cross-module call within its source goes through the same getter wrapper. If you are benchmarking your own library, you have two ways to remove this:
389389+390390+**Benchmark the pre-built artifact.** Import the library through its package name (which resolves to its built output) instead of reaching into its source. The built file has already collapsed internal imports into direct references, so Vite's module runner sees a single module with no internal getters:
391391+392392+```ts
393393+// BAD: every internal call inside the library goes through a getter
394394+import { parse } from '../src/index.ts'
395395+396396+// GOOD: the published entry has no internal getters
397397+import { parse } from 'my-library'
398398+```
399399+400400+If you compare your library against other packages, benchmark the same kind of artifact for every implementation. For workspace packages, make sure the package name resolves to the built output instead of source, for example by externalizing the package in Vite or by importing from `dist`.
401401+402402+**Disable the module runner for the benchmark.** If the benchmark does not need Vite transforms, mocks, or Vitest module interception, disable [`experimental.viteModuleRunner`](/config/experimental#experimental-vitemodulerunner) for the benchmark project so Node runs native ESM directly.
403403+404404+This only affects Node.js mode. Browser mode uses native ESM imports and does not have this overhead.
405405+406406+### Engine-Specific Considerations
407407+408408+#### V8 (Node.js, Chrome)
409409+410410+- **JIT tiering**: V8 compiles functions through multiple optimization tiers (Sparkplug → Maglev → TurboFan). A function may run at different speeds during warmup vs. steady-state. Tinybench handles warmup automatically, but very short benchmark runs may not reach the highest optimization tier.
411411+- **Deoptimization**: V8 can "bail out" of optimized code mid-benchmark if it encounters unexpected types or shapes. Keep the types consistent in your benchmark function:
412412+413413+ ```ts
414414+ test('process items', async ({ bench }) => {
415415+ // BAD: mixed shapes cause deoptimization
416416+ await bench('process', () => {
417417+ for (const item of items) {
418418+ // some items have { name: string }, others have { name: string, id: number }
419419+ process(item)
420420+ }
421421+ }).run()
422422+423423+ // GOOD: consistent object shapes
424424+ await bench('process', () => {
425425+ for (const item of items) {
426426+ // all items have the same shape { name: string, id: number }
427427+ process(item)
428428+ }
429429+ }).run()
430430+ })
431431+ ```
432432+433433+- **Garbage collection**: Large allocations inside the benchmark loop add GC noise. If you're measuring computation, pre-allocate data in a `setup` hook rather than inside the benchmarked function:
434434+435435+ ```ts
436436+ test('sorting', async ({ bench }) => {
437437+ const original = Array.from({ length: 10000 }, () => Math.random())
438438+ let data: number[]
439439+440440+ // BAD: allocates a new array every iteration, GC adds noise
441441+ await bench('sort', () => {
442442+ const data = Array.from({ length: 10000 }, () => Math.random())
443443+ data.sort()
444444+ }).run()
445445+446446+ // GOOD: pre-allocate, copy in beforeEach
447447+ await bench(
448448+ 'sort',
449449+ () => { data.sort() },
450450+ {
451451+ beforeEach() {
452452+ data = [...original]
453453+ },
454454+ },
455455+ ).run()
456456+ })
457457+ ```
458458+459459+#### JavaScriptCore (Bun, Safari)
460460+461461+- **Different optimization thresholds**: JSC uses its own JIT tiers (LLInt → Baseline → DFG → FTL) with different inlining and optimization heuristics. A benchmark that is fast on V8 may behave very differently on JSC.
462462+- **Async benchmarks**: Bun's event loop implementation differs from Node.js. If your benchmark involves async operations or timers, results may not be directly comparable across runtimes.
463463+464464+#### Browser
465465+466466+- **Timer resolution**: Browsers may reduce `performance.now()` precision (e.g., to 100μs or even 1ms) as a security mechanism. This makes very fast operations difficult to measure accurately, so increase iterations to compensate:
467467+468468+ ```ts
469469+ test('fast operations', async ({ bench }) => {
470470+ await bench.compare(
471471+ bench('fast-op', () => { fastOp() }),
472472+ bench('other-op', () => { otherOp() }),
473473+ {
474474+ // more iterations help overcome low timer resolution
475475+ iterations: 1000,
476476+ },
477477+ )
478478+ })
479479+ ```
480480+- **Cross-browser differences**: V8 (Chrome), SpiderMonkey (Firefox), and JSC (Safari) optimize different patterns differently. A benchmark that shows one library winning in Chrome may show the opposite in Firefox.
+1-1
docs/guide/cli-generated.md
···311311- **CLI:** `--mode <name>`
312312- **Config:** [mode](/config/mode)
313313314314-Override Vite mode (default: `test` or `benchmark`)
314314+Override Vite mode (default: `test`)
315315316316### isolate
317317
+30
docs/guide/migration.md
···1313Vitest 5.0 is currently in beta. This section tracks breaking changes as they are merged and may change before the stable release.
1414:::
15151616+### Benchmarking API Rewrite
1717+1818+The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`; it is a [test-context fixture](/guide/test-context#bench) accessed from inside a regular `test()`. See the [Benchmarking guide](/guide/benchmarking) for the new API.
1919+2020+Removed, with replacements where applicable:
2121+2222+- **`bench(name, fn)` at module scope**: destructure `bench` from the test context instead.
2323+2424+```ts
2525+// v4
2626+import { bench } from 'vitest' // [!code --]
2727+2828+bench('sort', () => { // [!code --]
2929+ [3, 1, 2].sort() // [!code --]
3030+}) // [!code --]
3131+3232+// v5
3333+import { test } from 'vitest' // [!code ++]
3434+3535+test('sort', async ({ bench }) => { // [!code ++]
3636+ await bench('sort', () => { [3, 1, 2].sort() }).run() // [!code ++]
3737+}) // [!code ++]
3838+```
3939+4040+- **`bench.skip`, `bench.only`, `bench.todo`** are removed. Use the regular `test.skip`, `test.only`, `test.todo` on the surrounding `test()` instead.
4141+- **`benchmark.reporters` / `benchmark.outputFile`** are removed. Benchmark output is now part of the default reporter and the `json` reporter; configure those at the top level via `test.reporters` instead.
4242+- **`benchmark.compare` config and the `--compare` CLI flag** are removed. Pass [`writeResult`](/guide/benchmarking#storing-and-replaying-results) as a per-bench option to persist a result, and read it back with [`bench.from()`](/guide/benchmarking#bench-from) inside `bench.compare()`.
4343+- **`benchmark.outputJson` config and the `--outputJson` CLI flag** are removed. Use `--reporter=json --outputFile=<path>` to capture benchmark results; the JSON reporter now includes a `benchmarks` field on each test case.
4444+- **`Vitest` instance `mode` property** is now always `'test'`. The previous `'benchmark'` value is no longer used; benchmarks run inside a dedicated project of the same `Vitest` instance.
4545+1646### Removed `test.sequential`, `describe.sequential`, and `sequential` Options
17471848Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency.
+23
docs/guide/test-context.md
···117117}, 2000)
118118```
119119120120+### `bench` <Version>5.0.0</Version> {#bench}
121121+122122+The `bench` fixture lets you define and run benchmarks inside regular tests. You can measure throughput, compare implementations, and assert relative performance:
123123+124124+```ts
125125+import { expect, test } from 'vitest'
126126+127127+test('compare parsers', async ({ bench }) => {
128128+ const result = await bench.compare(
129129+ bench('JSON.parse', () => {
130130+ JSON.parse('{"key":"value"}')
131131+ }),
132132+ bench('custom parser', () => {
133133+ customParse('{"key":"value"}')
134134+ }),
135135+ )
136136+137137+ expect(result.get('JSON.parse')).toBeFasterThan(result.get('custom parser'))
138138+})
139139+```
140140+141141+See the [Benchmarks guide](/guide/benchmarking) for full documentation on comparisons, baselines, and assertion matchers.
142142+120143### `onTestFailed`
121144122145The [`onTestFailed`](/api/hooks#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test.
···5566# Vitest
7788-Vitest instance requires the current test mode. It can be either:
88+## mode <Deprecated /> {#mode}
991010-- `test` when running runtime tests
1111-- `benchmark` when running benchmarks <Experimental />
1212-1313-::: details New in Vitest 4
1414-Vitest 4 added several new APIs (they are marked with a "4.0.0+" badge) and removed deprecated APIs:
1515-1616-- `invalidates`
1717-- `changedTests` (use [`onFilterWatchedSpecification`](#onfilterwatchedspecification) instead)
1818-- `server` (use [`vite`](#vite) instead)
1919-- `getProjectsByTestFile` (use [`getModuleSpecifications`](#getmodulespecifications) instead)
2020-- `getFileWorkspaceSpecs` (use [`getModuleSpecifications`](#getmodulespecifications) instead)
2121-- `getModuleProjects` (filter by [`this.projects`](#projects) yourself)
2222-- `updateLastChanged` (renamed to [`invalidateFile`](#invalidatefile))
2323-- `globTestSpecs` (use [`globTestSpecifications`](#globtestspecifications) instead)
2424-- `globTestFiles` (use [`globTestSpecifications`](#globtestspecifications) instead)
2525-- `listFile` (use [`getRelevantTestSpecifications`](#getrelevanttestspecifications) instead)
2626-:::
2727-2828-## mode
2929-3030-### test
3131-3232-Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files.
3333-3434-### benchmark <Experimental /> {#benchmark}
3535-3636-Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files.
1010+Since Vitest 5, this property is always `'test'`.
37113812## config
3913
···1414 '**/node_modules/**',
1515 '**/.git/**',
1616]
1717-export const benchmarkConfigDefaults: Required<
1818- Omit<BenchmarkUserOptions, 'outputFile' | 'compare' | 'outputJson'>
1919-> = {
1717+export const benchmarkConfigDefaults: Required<BenchmarkUserOptions> = {
1818+ enabled: false,
2019 include: ['**/*.{bench,benchmark}.?(c|m)[jt]s?(x)'],
2120 exclude: defaultExclude,
2221 includeSource: [],
2323- reporters: ['default'],
2424- includeSamples: false,
2222+ retainSamples: false,
2323+ suppressExportGetterWarnings: false,
2424+ // Populated automatically when Vitest clones the parent project; the default
2525+ // here applies to the (unused) raw config that's never run as a benchmark.
2626+ projectName: '',
2527}
26282729// These are the generic defaults for coverage. Providers may also set some provider specific defaults.
···11-import type { JsonTestResults, Vitest } from 'vitest/node'
11+import type { TestBenchmark } from 'vitest'
22+import type { JsonTestResult, JsonTestResults, Vitest } from 'vitest/node'
23import { readdirSync } from 'node:fs'
34import { readFile } from 'node:fs/promises'
45import { beforeAll, describe, expect, onTestFailed, test } from 'vitest'
···1213 let stderr: string
1314 let stdout: string
1415 let browserResultJson: JsonTestResults
1515- let passedTests: any[]
1616- let failedTests: any[]
1616+ let passedTests: JsonTestResult[]
1717+ let failedTests: JsonTestResult[]
1718 let vitest: Vitest
1819 const events: string[] = []
2020+ const emittedBenchmarks: Array<{ projectName: string; testName: string; benchmark: TestBenchmark }> = []
19212022 beforeAll(async () => {
2123 ({
···2830 {
2931 onBrowserInit(project) {
3032 events.push(`onBrowserInit ${project.name}`)
3333+ },
3434+ onTestCaseBenchmark(testCase, benchmark) {
3535+ emittedBenchmarks.push({
3636+ projectName: testCase.project.name || '',
3737+ testName: testCase.fullName,
3838+ benchmark,
3939+ })
3140 },
3241 },
3342 'json',
···4756 }))
48574958 const browserResult = await readFile('./browser.json', 'utf-8')
5050- browserResultJson = JSON.parse(browserResult)
5151- const getPassed = results => results.filter(result => result.status === 'passed' && !result.message)
5252- const getFailed = results => results.filter(result => result.status === 'failed')
5959+ browserResultJson = JSON.parse(browserResult) as JsonTestResults
6060+ const getPassed = (results: JsonTestResult[]) => results.filter(result => result.status === 'passed' && !result.message)
6161+ const getFailed = (results: JsonTestResult[]) => results.filter(result => result.status === 'failed')
5362 passedTests = getPassed(browserResultJson.testResults)
5463 failedTests = getFailed(browserResultJson.testResults)
5564 })
···7079 .toEqual(vitest.projects.map(() => expect.arrayContaining(runtimeTestFiles)))
71807281 const testFilesCount = readdirSync('./test')
7373- .filter(n => n.includes('.test.') || n.includes('.test-d.'))
8282+ .filter(n => n.includes('.test.') || n.includes('.test-d.') || n.includes('.bench.'))
7483 .length + 1 // 1 is in-source-test
75847685 expect(browserResultJson.testResults).toHaveLength(testFilesCount * instances.length)
7786 expect(passedTests).toHaveLength(browserResultJson.testResults.length)
7887 expect(failedTests).toHaveLength(0)
8888+ })
8989+9090+ test('benchmarks run in a dedicated `(bench)` project per browser instance', () => {
9191+ const benchProjects = vitest.projects.filter(p => p.name.endsWith('(bench)'))
9292+ expect(benchProjects.map(p => p.name).sort()).toEqual(
9393+ instances.map(({ browser }) => `${browser} (bench)`).sort(),
9494+ )
9595+ })
9696+9797+ test('perProject benchmarks emit tasks with the perProject flag in every browser', () => {
9898+ const records = emittedBenchmarks.filter(e =>
9999+ e.testName === 'perProject registrations flow through the browser RPC (onTestBenchmark)',
100100+ )
101101+ // the test calls `.run()` twice, so each browser produces 2 benchmark records
102102+ expect(records.length, `perProject emitted: ${records.length}`).toBe(2 * instances.length)
103103+ for (const record of records) {
104104+ expect(record.benchmark.tasks, `empty tasks for ${record.projectName}`).toHaveLength(1)
105105+ const [task] = record.benchmark.tasks
106106+ expect(task.perProject, `missing perProject flag on ${record.projectName}/${task.name}`).toBe(true)
107107+ expect(task.fromStore).toBeUndefined()
108108+ }
109109+ })
110110+111111+ test('bench.compare emits one benchmark with both registrations ranked', () => {
112112+ const records = emittedBenchmarks.filter(e =>
113113+ e.testName === 'bench.compare resolves a BenchStorage in the browser',
114114+ )
115115+ expect(records.length).toBe(instances.length)
116116+ for (const record of records) {
117117+ expect(record.benchmark.tasks.map(t => t.name).sort(), `unexpected tasks for ${record.projectName}`).toEqual(['a', 'b'])
118118+ expect(record.benchmark.tasks.map(t => t.rank).sort()).toEqual([1, 2])
119119+ }
120120+ })
121121+122122+ test('writeResult flows through the write-artifact RPC in every browser', () => {
123123+ const records = emittedBenchmarks.filter(e =>
124124+ e.testName === 'writeResult exercises the writeBenchmarkResult RPC round-trip',
125125+ )
126126+ expect(records.length).toBe(instances.length)
127127+ for (const record of records) {
128128+ expect(record.benchmark.tasks, `empty tasks for ${record.projectName}`).toHaveLength(1)
129129+ const [task] = record.benchmark.tasks
130130+ expect(task.name).toBe('with-write')
131131+ }
79132 })
8013381134 test('tags are collected', () => {
···110163 test('runs in-source tests', () => {
111164 expect(stdout).toContain('src/actions.ts')
112165 const actionsTest = passedTests.find(t => t.name.includes('/actions.ts'))
113113- expect(actionsTest).toBeDefined()
166166+ expect.assert(actionsTest)
114167 expect(actionsTest.assertionResults).toHaveLength(1)
115168 })
116169···231284 return
232285 }
233286 if (testCase.project.name === 'chromium' || testCase.project.name === 'chrome') {
234234- expect(testCase.result().errors[0].stacks).toEqual([
287287+ expect(testCase.result().errors?.[0].stacks).toEqual([
235288 {
236289 line: 11,
237290 column: 12,
···380433 })
381434382435 const lines = stderr.split('\n')
383383- const timeoutErrorsIndexes = []
436436+ const timeoutErrorsIndexes: number[] = []
384437 lines.forEach((line, index) => {
385438 if (line.includes('TimeoutError:')) {
386439 timeoutErrorsIndexes.push(index)
+5-1
test/browser/specs/utils.ts
···4343 $viteConfig: viteOverrides,
4444 }, include, runnerOptions)
45454646- return { ...result, stderr: result.stderr.replace('Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest\'s version when using it.\n', '') }
4646+ return {
4747+ ...result,
4848+ ctx: result.ctx!,
4949+ stderr: result.stderr.replace('Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest\'s version when using it.\n', ''),
5050+ }
4751}
+43
test/browser/test/browser.bench.ts
···11+import { expect, test } from 'vitest'
22+33+// Keep runs tiny — this file smoke-tests the browser RPC path
44+// (onTestBenchmark, readBenchmarkResult / writeBenchmarkResult).
55+// It is not a measurement harness.
66+const fastBenchOptions = {
77+ time: 0,
88+ iterations: 2,
99+ warmupTime: 0,
1010+ warmupIterations: 0,
1111+}
1212+1313+test('perProject registrations flow through the browser RPC (onTestBenchmark)', async ({ bench }) => {
1414+ await bench('1 + 1', { perProject: true, ...fastBenchOptions }, () => {
1515+ const result = 1 + 1
1616+ expect.assert(result === 2)
1717+ }).run()
1818+ await bench('1 + 2', { perProject: true, ...fastBenchOptions }, () => {
1919+ const result = 1 + 2
2020+ expect.assert(result === 3)
2121+ }).run()
2222+})
2323+2424+test('bench.compare resolves a BenchStorage in the browser', async ({ bench }) => {
2525+ const storage = await bench.compare(
2626+ bench('a', () => { const _ = 1 + 1 }),
2727+ bench('b', () => { const _ = 1 + 2 }),
2828+ fastBenchOptions,
2929+ )
3030+ // runtime smoke — every registration is accessible with a valid BenchResult
3131+ expect.assert(typeof storage.get('a').latency.mean === 'number')
3232+ expect.assert(typeof storage.get('b').latency.mean === 'number')
3333+})
3434+3535+test('writeResult exercises the writeBenchmarkResult RPC round-trip', async ({ bench }) => {
3636+ // The browser worker forwards writeResult through the WebSocket RPC to the
3737+ // node side. We don't assert on the file contents here (the spec layer can
3838+ // do that), just that the round-trip completes without throwing.
3939+ const result = await bench('with-write', { writeResult: './out/with-write.json', ...fastBenchOptions }, () => {
4040+ const _ = 1 + 1
4141+ }).run()
4242+ expect.assert(typeof result.latency.mean === 'number')
4343+})
+103
test/e2e/test/benchmarking.test-d.ts
···11+import type {
22+ BaselineData,
33+ Bench,
44+ BenchFnOptions,
55+ BenchFromSource,
66+ BenchRegistration,
77+ BenchResult,
88+ BenchStorage,
99+ TestContext,
1010+} from 'vitest'
1111+import { assertType, expect, expectTypeOf, test } from 'vitest'
1212+1313+test('plain `bench()` returns BenchRegistration with the name literal narrowed', ({ bench }) => {
1414+ const reg = bench('plain', () => {})
1515+ expectTypeOf(reg).toEqualTypeOf<BenchRegistration<'plain'>>()
1616+ expectTypeOf(reg.name).toEqualTypeOf<'plain'>()
1717+})
1818+1919+test('`bench(name, options, fn)` is the preferred options-second overload', ({ bench }) => {
2020+ const reg = bench('x', { beforeEach: () => {} }, () => {})
2121+ expectTypeOf(reg).toEqualTypeOf<BenchRegistration<'x'>>()
2222+})
2323+2424+test('`bench(name, fn, options)` is a type error — options as third arg is not accepted', ({ bench }) => {
2525+ // @ts-expect-error legacy (name, fn, options) form is no longer accepted
2626+ bench('x', () => {}, { beforeEach: () => {} } satisfies BenchFnOptions)
2727+})
2828+2929+test('`bench.compare(...regs)` returns a BenchStorage keyed by the UNION of registration names', async ({ bench }) => {
3030+ const storage = await bench.compare(bench('a', () => {}), bench('b', () => {}))
3131+ expectTypeOf(storage).toEqualTypeOf<BenchStorage<'a' | 'b'>>()
3232+ expectTypeOf(storage.get('a')).toEqualTypeOf<BenchResult>()
3333+ expectTypeOf(storage.get('b')).toEqualTypeOf<BenchResult>()
3434+})
3535+3636+test('`bench.compare(...).get(unknownName)` is a type error', async ({ bench }) => {
3737+ const storage = await bench.compare(bench('a', () => {}), bench('b', () => {}))
3838+ // @ts-expect-error 'missing' is not one of 'a' | 'b'
3939+ storage.get('missing')
4040+})
4141+4242+test('`bench.compare` accepts BenchCompareOptions as the trailing argument', async ({ bench }) => {
4343+ const storage = await bench.compare(
4444+ bench('a', () => {}),
4545+ bench('b', () => {}),
4646+ { time: 10, iterations: 5 },
4747+ )
4848+ expectTypeOf(storage).toEqualTypeOf<BenchStorage<'a' | 'b'>>()
4949+})
5050+5151+test('`expect(result).toBeFasterThan` and `.toBeSlowerThan` are callable on BenchResult', () => {
5252+ const a = {} as BenchResult
5353+ const b = {} as BenchResult
5454+ // calling both matchers must type-check; runtime behaviour is in Bucket F
5555+ assertType<void>(expect(a).toBeFasterThan(b))
5656+ assertType<void>(expect(a).toBeFasterThan(b, { delta: 0.1 }))
5757+ assertType<void>(expect(a).toBeSlowerThan(b))
5858+ assertType<void>(expect(a).toBeSlowerThan(b, { delta: 0.1 }))
5959+})
6060+6161+test('`TestContext.bench` is typed as the `Bench` factory', () => {
6262+ type CtxBench = TestContext['bench']
6363+ expectTypeOf<CtxBench>().toEqualTypeOf<Bench>()
6464+})
6565+6666+test('`bench.from(name, path)` returns BenchRegistration with the name literal narrowed', ({ bench }) => {
6767+ const reg = bench.from('baseline', 'results/baseline.json')
6868+ expectTypeOf(reg).toEqualTypeOf<BenchRegistration<'baseline'>>()
6969+ expectTypeOf(reg.name).toEqualTypeOf<'baseline'>()
7070+})
7171+7272+test('`bench.from(name, source)` accepts a function returning BaselineData', ({ bench }) => {
7373+ const sync: BenchFromSource = () => ({} as BaselineData)
7474+ const async: BenchFromSource = async () => ({} as BaselineData)
7575+ expectTypeOf(bench.from('s', sync)).toEqualTypeOf<BenchRegistration<'s'>>()
7676+ expectTypeOf(bench.from('a', async)).toEqualTypeOf<BenchRegistration<'a'>>()
7777+})
7878+7979+test('`bench.from(...).fn` is optional — `bench.from` registrations carry no benchmark function', ({ bench }) => {
8080+ const reg = bench.from('baseline', 'results.json')
8181+ expectTypeOf(reg.fn).toEqualTypeOf<BenchRegistration<'baseline'>['fn']>()
8282+ expectTypeOf(reg.fn).toBeNullable()
8383+})
8484+8585+test('`bench.from` registrations contribute to the name union in `bench.compare`', async ({ bench }) => {
8686+ const storage = await bench.compare(
8787+ bench('live', () => {}),
8888+ bench.from('baseline', 'results.json'),
8989+ )
9090+ expectTypeOf(storage).toEqualTypeOf<BenchStorage<'live' | 'baseline'>>()
9191+ expectTypeOf(storage.get('baseline')).toEqualTypeOf<BenchResult>()
9292+})
9393+9494+test('`bench.from(fn, source)` accepts a function and uses its name as the benchmark name', ({ bench }) => {
9595+ function myBench() {}
9696+ const reg = bench.from(myBench, 'results.json')
9797+ expectTypeOf(reg).toEqualTypeOf<BenchRegistration<string>>()
9898+})
9999+100100+test('`bench.from(name)` without a source is a type error', ({ bench }) => {
101101+ // @ts-expect-error second argument (source) is required
102102+ bench.from('baseline')
103103+})
···4242 hookTimeout: number
4343 retry: SerializableRetry
4444 includeTaskLocation: boolean | undefined
4545- diffOptions?: DiffOptions
4645 tags: TestTagDefinition[]
4746 tagsFilter: string[] | undefined
4847 strictTags: boolean
4848+4949+ /**
5050+ * @internal
5151+ */
5252+ _diffOptions?: DiffOptions
4953 mergeReportsLabel: string | undefined
5054}
5155···9397 | 'test-failure'
9498 | (string & Record<string, never>)
9599100100+export interface TestTryOptions {
101101+ retry: number
102102+ repeats: number
103103+}
104104+96105export interface VitestRunner {
97106 /**
98107 * First thing that's getting called before actually collecting and running tests.
···123132 */
124133 onBeforeTryTask?: (
125134 test: Test,
126126- options: { retry: number; repeats: number },
135135+ options: TestTryOptions,
127136 ) => unknown
128137 /**
129138 * When the task has finished running, but before cleanup hooks are called
···138147 */
139148 onAfterTryTask?: (
140149 test: Test,
141141- options: { retry: number; repeats: number },
150150+ options: TestTryOptions,
142151 ) => unknown
143152 /**
144153 * Called after the retry resolution happened. Unlike `onAfterTryTask`, the test now has a new state.
···146155 */
147156 onAfterRetryTask?: (
148157 test: Test,
149149- options: { retry: number; repeats: number },
158158+ options: TestTryOptions,
150159 ) => unknown
151160152161 /**
+38
packages/runner/src/types/tasks.ts
···11import type { Awaitable, TestError } from '@vitest/utils'
22+import type { Statistics } from 'tinybench'
23import type { TestFixtures } from '../fixture'
34import type { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from '../hooks'
45import type { kChainableContext, TypedChainableFunction } from '../utils/chain'
···340341 */
341342 artifacts: TestArtifact[]
342343 fullTestName: string
344344+ /**
345345+ * An array of benchmark results generated by `context.bench` function.
346346+ * The benchmark is added only after `bench().run` or `bench.compare` is resolved.
347347+ *
348348+ * @experimental
349349+ */
350350+ benchmarks: TestBenchmark[]
351351+}
352352+353353+export interface TestBenchmark {
354354+ name: string
355355+ tasks: TestBenchmarkTask[]
356356+}
357357+358358+export type TestBenchmarkStatistics = Statistics
359359+360360+export interface BaselineData {
361361+ latency: TestBenchmarkStatistics
362362+ throughput: TestBenchmarkStatistics
363363+ period: number
364364+ totalTime: number
365365+}
366366+367367+export interface TestBenchmarkTask {
368368+ name: string
369369+ latency: TestBenchmarkStatistics
370370+ throughput: TestBenchmarkStatistics
371371+ period: number
372372+ totalTime: number
373373+ rank: number
374374+ perProject?: boolean
375375+ /**
376376+ * `true` when the task was produced by `bench.from()` rather than by
377377+ * executing a function. Reporters can use this to render the row as a
378378+ * static reference (no margin of error, no samples).
379379+ */
380380+ fromStore?: boolean
343381}
344382345383export type Task = Test | Suite | File
···11-import { defineConfig } from "vitest/config"
22-33-// to see the difference better, increase sleep time and iterations e.g. by
44-// SLEEP_BENCH_MS=100 pnpm -C test/benchmark test bench -- --root fixtures/sequential --fileParallelism
55-66-export default defineConfig({
77- test: {
88- globalSetup: ["./setup.ts"]
99- }
1010-});