vitest 5.0.0-beta.3 → 5.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/browser.d.ts +1 -1
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.Bay6B1Dz.js → base.BEGVMQrS.js} +6 -6
  4. package/dist/chunks/{browser.d.DM1g8UNp.d.ts → browser.d.BGxB4Xum.d.ts} +4 -25
  5. package/dist/chunks/{cac.DoK9yX-i.js → cac.CyXAEMkE.js} +22 -31
  6. package/dist/chunks/{cli-api.BCY9ylNq.js → cli-api.DJMXq34b.js} +502 -549
  7. package/dist/chunks/{config.d.C0UMwus7.d.ts → config.d.DXq1aBpy.d.ts} +7 -26
  8. package/dist/chunks/{creator.BqL2U_x4.js → creator.D66cVXYh.js} +2 -2
  9. package/dist/chunks/{defaults.DVfzlTkU.js → defaults.CUUnbOrq.js} +5 -3
  10. package/dist/chunks/global.d.BtKPuz2X.d.ts +194 -0
  11. package/dist/chunks/{globals.8_qjZdeE.js → globals.BuY-yD0m.js} +2 -1
  12. package/dist/chunks/{index.ukHtlBbI.js → index.CE58PZNH.js} +355 -146
  13. package/dist/chunks/{index.PuMGMNHF.js → index.CcluKS59.js} +4 -4
  14. package/dist/chunks/{index.CbgUM9E5.js → index.nQFVd50u.js} +2 -1
  15. package/dist/chunks/{init-forks.OoZmDo1g.js → init-forks.Ce3vGWgL.js} +1 -1
  16. package/dist/chunks/{init-threads.eSHAowcx.js → init-threads.8e1OLv5v.js} +1 -1
  17. package/dist/chunks/{init.YjNsCb-_.js → init.6qx-LaHs.js} +30 -2
  18. package/dist/chunks/{nativeModuleMocker.DKpFw0pk.js → nativeModuleMocker.DDZfQXLs.js} +1 -1
  19. package/dist/chunks/{plugin.d.C00LxKL6.d.ts → plugin.d.B7MTG_Fe.d.ts} +71 -82
  20. package/dist/chunks/{rpc.d.7JZuxZ8u.d.ts → rpc.d.OQ_EZi1Z.d.ts} +18 -2
  21. package/dist/chunks/{setup-common.eQsbxe88.js → setup-common.DdEF_hkE.js} +1 -1
  22. package/dist/chunks/{vm.BE_VOfSs.js → vm.Bu7mmcZq.js} +2 -2
  23. package/dist/chunks/{worker.d.Dv3hDCFf.d.ts → worker.d.yR22cs6X.d.ts} +3 -2
  24. package/dist/cli.js +1 -1
  25. package/dist/config.cjs +1 -1
  26. package/dist/config.d.ts +6 -8
  27. package/dist/config.js +1 -1
  28. package/dist/index.d.ts +23 -40
  29. package/dist/index.js +2 -1
  30. package/dist/module-evaluator.d.ts +4 -1
  31. package/dist/module-evaluator.js +14 -19
  32. package/dist/node.d.ts +20 -12
  33. package/dist/node.js +5 -5
  34. package/dist/runtime.js +2 -2
  35. package/dist/worker.d.ts +3 -3
  36. package/dist/worker.js +7 -6
  37. package/dist/workers/forks.js +8 -7
  38. package/dist/workers/runVmTests.js +4 -3
  39. package/dist/workers/threads.js +8 -7
  40. package/dist/workers/vmForks.js +4 -4
  41. package/dist/workers/vmThreads.js +4 -4
  42. package/package.json +15 -15
  43. package/dist/chunks/global.d.DZbA5YnY.d.ts +0 -101
@@ -1,22 +1,23 @@
1
- import { TestSyntaxError, getCurrentTest, getCurrentSuite, updateTask, createTaskCollector, getHooks, getFn, afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach, describe, it, onTestFailed, onTestFinished, recordArtifact, suite, test } from '@vitest/runner';
1
+ import { TestSyntaxError, getCurrentTest, createTaskCollector, getCurrentSuite, getHooks, getFn, afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach, describe, it, onTestFailed, onTestFinished, recordArtifact, suite, test } from '@vitest/runner';
2
2
  import { i as isChildProcess, w as waitForImportsToResolve, r as resetModules, g as getWorkerState } from './utils.BX5Fg8C4.js';
3
3
  import { getSafeTimers, delay } from '@vitest/utils/timers';
4
4
  import { isMockFunction, fn, spyOn, restoreAllMocks, resetAllMocks, clearAllMocks } from '@vitest/spy';
5
- import { getType, isObject, noop, assertTypes, ordinal, createSimpleStackTrace, getCallLastIndex, createDefer } from '@vitest/utils/helpers';
5
+ import { getType, isObject, noop, assertTypes, ordinal, createSimpleStackTrace, getCallLastIndex } from '@vitest/utils/helpers';
6
6
  import { positionToOffset, offsetToLineNumber, lineSplitRE } from '@vitest/utils/offset';
7
7
  import { parseSingleStack, parseErrorStacktrace } from '@vitest/utils/source-map';
8
8
  import { c as commonjsGlobal, g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
9
9
  import { R as RealDate, b as resetDate, m as mockDate, r as rpc, V as VitestEvaluatedModules } from './rpc.DFRWVnRh.js';
10
10
  import * as chai from 'chai';
11
11
  import { use, util } from 'chai';
12
- import { getNames, createChainable, getTests, getTestName, matchesTags } from '@vitest/runner/utils';
12
+ import { getNames, getTests, getTestName, createChainable, matchesTags } from '@vitest/runner/utils';
13
13
  import { processError } from '@vitest/utils/error';
14
14
  import { g as getSerializers, a as addSerializer } from './plugins.DrsmdUE2.js';
15
15
  import { format } from '@vitest/pretty-format';
16
16
  import { printDiffOrStringify, diff } from '@vitest/utils/diff';
17
17
  import { stringify, inspect } from '@vitest/utils/display';
18
18
  import c from 'tinyrainbow';
19
- import { normalize } from 'pathe';
19
+ import { isAbsolute, relative, normalize } from 'pathe';
20
+ import { Bench } from 'tinybench';
20
21
  import { expectTypeOf } from 'expect-type';
21
22
 
22
23
  const ChaiStyleAssertions = (chai, utils) => {
@@ -1590,6 +1591,48 @@ const JestExtend = (chai, utils) => {
1590
1591
  });
1591
1592
  };
1592
1593
 
1594
+ function isBenchResult(value) {
1595
+ return typeof value === "object" && value !== null && "latency" in value && typeof value.latency?.mean === "number";
1596
+ }
1597
+ function formatOps(ops) {
1598
+ return ops.toLocaleString("en-US", {
1599
+ minimumFractionDigits: 2,
1600
+ maximumFractionDigits: 2
1601
+ });
1602
+ }
1603
+ const benchMatchers = {
1604
+ toBeFasterThan(actual, expected, options) {
1605
+ const { matcherHint, RECEIVED_COLOR, EXPECTED_COLOR } = this.utils;
1606
+ const delta = options?.delta ?? 0;
1607
+ if (!isBenchResult(actual)) throw new TypeError(`${matcherHint(".toBeFasterThan")} expects the actual value to be a benchmark result.`);
1608
+ if (!isBenchResult(expected)) throw new TypeError(`${matcherHint(".toBeFasterThan")} expects the expected value to be a benchmark result.`);
1609
+ const threshold = expected.latency.mean * (1 - delta);
1610
+ const pass = actual.latency.mean < threshold;
1611
+ return {
1612
+ pass,
1613
+ message: () => {
1614
+ const relation = ((actual.latency.mean - expected.latency.mean) / expected.latency.mean * 100).toFixed(2);
1615
+ return pass ? `${matcherHint(".not.toBeFasterThan")}\n\nExpected to not be faster, but was ${Math.abs(Number(relation))}% faster.\n\nReceived: ${RECEIVED_COLOR(formatOps(actual.throughput.mean))} ops/sec\nExpected: ${EXPECTED_COLOR(formatOps(expected.throughput.mean))} ops/sec\n` : `${matcherHint(".toBeFasterThan")}\n\nExpected to be faster${delta > 0 ? ` by at least ${(delta * 100).toFixed(0)}%` : ""}, but was ${Number(relation) > 0 ? `${relation}% slower` : `only ${Math.abs(Number(relation))}% faster`}.\n\nReceived: ${RECEIVED_COLOR(formatOps(actual.throughput.mean))} ops/sec\nExpected: ${EXPECTED_COLOR(formatOps(expected.throughput.mean))} ops/sec\n`;
1616
+ }
1617
+ };
1618
+ },
1619
+ toBeSlowerThan(actual, expected, options) {
1620
+ const { matcherHint, RECEIVED_COLOR, EXPECTED_COLOR } = this.utils;
1621
+ const delta = options?.delta ?? 0;
1622
+ if (!isBenchResult(actual)) throw new TypeError(`${matcherHint(".toBeSlowerThan")} expects the actual value to be a benchmark result.`);
1623
+ if (!isBenchResult(expected)) throw new TypeError(`${matcherHint(".toBeSlowerThan")} expects the expected value to be a benchmark result.`);
1624
+ const threshold = expected.latency.mean * (1 + delta);
1625
+ const pass = actual.latency.mean > threshold;
1626
+ return {
1627
+ pass,
1628
+ message: () => {
1629
+ const relation = ((actual.latency.mean - expected.latency.mean) / expected.latency.mean * 100).toFixed(2);
1630
+ return pass ? `${matcherHint(".not.toBeSlowerThan")}\n\nExpected to not be slower, but was ${relation}% slower.\n\nReceived: ${RECEIVED_COLOR(formatOps(actual.throughput.mean))} ops/sec\nExpected: ${EXPECTED_COLOR(formatOps(expected.throughput.mean))} ops/sec\n` : `${matcherHint(".toBeSlowerThan")}\n\nExpected to be slower${delta > 0 ? ` by at least ${(delta * 100).toFixed(0)}%` : ""}, but was ${Number(relation) < 0 ? `${Math.abs(Number(relation))}% faster` : `only ${relation}% slower`}.\n\nReceived: ${RECEIVED_COLOR(formatOps(actual.throughput.mean))} ops/sec\nExpected: ${EXPECTED_COLOR(formatOps(expected.throughput.mean))} ops/sec\n`;
1631
+ }
1632
+ };
1633
+ }
1634
+ };
1635
+
1593
1636
  var fakeTimersSrc = {};
1594
1637
 
1595
1638
  var global;
@@ -5649,7 +5692,7 @@ class FakeTimers {
5649
5692
  }
5650
5693
  }
5651
5694
 
5652
- function copyStackTrace$1(target, source) {
5695
+ function copyStackTrace$2(target, source) {
5653
5696
  if (source.stack !== void 0) target.stack = source.stack.replace(source.message, target.message);
5654
5697
  return target;
5655
5698
  }
@@ -5670,7 +5713,7 @@ function waitFor(callback, options = {}) {
5670
5713
  const handleTimeout = () => {
5671
5714
  if (intervalId) clearInterval(intervalId);
5672
5715
  let error = lastError;
5673
- if (!error) error = copyStackTrace$1(/* @__PURE__ */ new Error("Timed out in waitFor!"), STACK_TRACE_ERROR);
5716
+ if (!error) error = copyStackTrace$2(/* @__PURE__ */ new Error("Timed out in waitFor!"), STACK_TRACE_ERROR);
5674
5717
  reject(error);
5675
5718
  };
5676
5719
  const checkCallback = () => {
@@ -5711,7 +5754,7 @@ function waitUntil(callback, options = {}) {
5711
5754
  let intervalId;
5712
5755
  const onReject = (error) => {
5713
5756
  if (intervalId) clearInterval(intervalId);
5714
- if (!error) error = copyStackTrace$1(/* @__PURE__ */ new Error("Timed out in waitUntil!"), STACK_TRACE_ERROR);
5757
+ if (!error) error = copyStackTrace$2(/* @__PURE__ */ new Error("Timed out in waitUntil!"), STACK_TRACE_ERROR);
5715
5758
  reject(error);
5716
5759
  };
5717
5760
  const onResolve = (result) => {
@@ -5850,9 +5893,17 @@ function createVitest() {
5850
5893
  defineHelper: (fn) => {
5851
5894
  return function __VITEST_HELPER__(...args) {
5852
5895
  const result = fn.apply(this, args);
5853
- if (result && typeof result === "object" && typeof result.then === "function") return (async function __VITEST_HELPER__() {
5854
- return await result;
5855
- })();
5896
+ if (result && typeof result === "object" && typeof result.then === "function") {
5897
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
5898
+ return (async function __VITEST_HELPER__() {
5899
+ try {
5900
+ return await result;
5901
+ } catch (error) {
5902
+ if (error instanceof Error && !error.stack?.includes("__VITEST_HELPER__")) copyStackTrace$1(error, stackTraceError);
5903
+ throw error;
5904
+ }
5905
+ })();
5906
+ }
5856
5907
  return result;
5857
5908
  };
5858
5909
  },
@@ -5979,6 +6030,10 @@ function getImporter(name) {
5979
6030
  return stack.includes(` at Object.${name}`) || stack.includes(`${name}@`) || stack.includes(` at ${name} (`);
5980
6031
  }) + 1])?.file || "";
5981
6032
  }
6033
+ function copyStackTrace$1(target, source) {
6034
+ if (source.stack !== void 0) target.stack = source.stack.replace(source.message, target.message);
6035
+ return target;
6036
+ }
5982
6037
 
5983
6038
  // these matchers are not supported because they don't make sense with poll
5984
6039
  const unsupported = [
@@ -7473,6 +7528,7 @@ function createExpect(test) {
7473
7528
  chai.util.addMethod(expect, "assertions", assertions);
7474
7529
  chai.util.addMethod(expect, "hasAssertions", hasAssertions);
7475
7530
  expect.extend(customMatchers);
7531
+ expect.extend(benchMatchers);
7476
7532
  return expect;
7477
7533
  }
7478
7534
  const globalExpect = createExpect();
@@ -7492,140 +7548,280 @@ function inject(key) {
7492
7548
  return getWorkerState().providedContext[key];
7493
7549
  }
7494
7550
 
7495
- const benchFns = /* @__PURE__ */ new WeakMap();
7496
- const benchOptsMap = /* @__PURE__ */ new WeakMap();
7497
- function getBenchOptions(key) {
7498
- return benchOptsMap.get(key);
7499
- }
7500
- function getBenchFn(key) {
7501
- return benchFns.get(key);
7502
- }
7503
- const bench = createBenchmark(function(name, fn = noop, options = {}) {
7504
- if (getWorkerState().config.mode !== "benchmark") throw new Error("`bench()` is only available in benchmark mode.");
7505
- const task = getCurrentSuite().task(formatName(name), {
7506
- ...this,
7507
- meta: { benchmark: true }
7508
- });
7509
- benchFns.set(task, fn);
7510
- benchOptsMap.set(task, options);
7511
- // vitest runner sets mode to `todo` if handler is not passed down
7512
- // but we store handler separately
7513
- if (!this.todo && task.mode === "todo") task.mode = "run";
7514
- });
7515
- function createBenchmark(fn) {
7516
- const benchmark = createChainable([
7517
- "skip",
7518
- "only",
7519
- "todo"
7520
- ], fn);
7521
- benchmark.skipIf = (condition) => condition ? benchmark.skip : benchmark;
7522
- benchmark.runIf = (condition) => condition ? benchmark : benchmark.skip;
7523
- return benchmark;
7524
- }
7525
- function formatName(name) {
7526
- return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
7527
- }
7528
-
7529
- function createBenchmarkResult(name) {
7530
- return {
7551
+ const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
7552
+ const kRegistration = Symbol("registration");
7553
+ const kFromSource = Symbol("fromSource");
7554
+ const kPerProject = Symbol("perProject");
7555
+ const kWriteResult = Symbol("writeResult");
7556
+ const kFinalize = Symbol("finalize");
7557
+ function isFromRegistration(reg) {
7558
+ return kFromSource in reg;
7559
+ }
7560
+ function substitutePath(template, projectName) {
7561
+ return template.replace(/\$\{projectName\}/g, projectName ?? "");
7562
+ }
7563
+ function createBench(test, config) {
7564
+ let benchIdx = 0;
7565
+ const pending = /* @__PURE__ */ new Set();
7566
+ const createTinybench = (options) => {
7567
+ const currentIndex = ++benchIdx;
7568
+ return new Bench({
7569
+ signal: test.context.signal,
7570
+ name: `${test.fullTestName} ${currentIndex}`,
7571
+ retainSamples: config.benchmark.retainSamples,
7572
+ ...options,
7573
+ now
7574
+ });
7575
+ };
7576
+ const resolveTemplate = (template) => substitutePath(template, config.benchmark.projectName);
7577
+ const resolveFromSource = async (source) => {
7578
+ if (typeof source === "function") return source();
7579
+ const resolved = resolveTemplate(source);
7580
+ const data = await rpc().readBenchmarkResult(resolved);
7581
+ if (data == null) throw new Error(`\`bench.from()\` could not find a result file at "${resolved}". Run the source benchmark first to create it.`);
7582
+ return data;
7583
+ };
7584
+ const taskFromBaseline = (name, data) => ({
7531
7585
  name,
7586
+ latency: data.latency,
7587
+ throughput: data.throughput,
7588
+ period: data.period,
7589
+ totalTime: data.totalTime,
7532
7590
  rank: 0,
7533
- rme: 0,
7534
- samples: []
7591
+ fromStore: true
7592
+ });
7593
+ const createCompareStorage = (bench, fromResults) => {
7594
+ return { get(name) {
7595
+ const stored = fromResults?.get(name);
7596
+ if (stored) return stored;
7597
+ const task = bench.getTask(name);
7598
+ if (!task) throw new Error(`task "${name}" was not defined`);
7599
+ return task.result;
7600
+ } };
7535
7601
  };
7536
- }
7537
- const benchmarkTasks = /* @__PURE__ */ new WeakMap();
7538
- async function runBenchmarkSuite(suite, runner) {
7539
- const { Task, Bench } = await runner.importTinybench();
7540
- const start = performance.now();
7541
- const benchmarkGroup = [];
7542
- const benchmarkSuiteGroup = [];
7543
- for (const task of suite.tasks) {
7544
- if (task.mode !== "run" && task.mode !== "queued") continue;
7545
- if (task.meta?.benchmark) benchmarkGroup.push(task);
7546
- else if (task.type === "suite") benchmarkSuiteGroup.push(task);
7547
- }
7548
- // run sub suites sequentially
7549
- for (const subSuite of benchmarkSuiteGroup) await runBenchmarkSuite(subSuite, runner);
7550
- if (benchmarkGroup.length) {
7551
- const defer = createDefer();
7552
- suite.result = {
7553
- state: "run",
7554
- startTime: start,
7555
- benchmark: createBenchmarkResult(suite.name)
7602
+ const serializeBenchmark = (tinybenchTasks, name, taskMeta, fromTasks) => {
7603
+ const tasks = tinybenchTasks.map((t) => {
7604
+ const result = t.result;
7605
+ if (result.state === "errored") throw result.error;
7606
+ if (result.state !== "completed") throw new Error(`task "${t.name}" did not complete: received "${result.state}"`);
7607
+ return {
7608
+ name: t.name,
7609
+ latency: result.latency,
7610
+ throughput: result.throughput,
7611
+ period: result.period,
7612
+ totalTime: result.totalTime,
7613
+ rank: 0,
7614
+ ...taskMeta?.get(t.name)
7615
+ };
7616
+ });
7617
+ if (fromTasks) tasks.push(...fromTasks);
7618
+ tasks.sort((a, b) => a.latency.mean - b.latency.mean);
7619
+ tasks.forEach((task, idx) => {
7620
+ task.rank = idx + 1;
7621
+ });
7622
+ return {
7623
+ name: name || test.fullTestName,
7624
+ tasks
7556
7625
  };
7557
- updateTask$1("suite-prepare", suite);
7558
- const addBenchTaskListener = (task, benchmark) => {
7559
- task.addEventListener("complete", (e) => {
7560
- const taskRes = e.task.result;
7561
- const result = benchmark.result.benchmark;
7562
- benchmark.result.state = "pass";
7563
- Object.assign(result, taskRes);
7564
- // compute extra stats and free raw samples as early as possible
7565
- const samples = result.samples;
7566
- result.sampleCount = samples.length;
7567
- result.median = samples.length % 2 ? samples[Math.floor(samples.length / 2)] : (samples[samples.length / 2] + samples[samples.length / 2 - 1]) / 2;
7568
- if (!runner.config.benchmark?.includeSamples) result.samples.length = 0;
7569
- updateTask$1("test-finished", benchmark);
7570
- }, { once: true });
7571
- task.addEventListener("error", (e) => {
7572
- const task = e.task;
7573
- defer.reject(benchmark ? task.result.error : e);
7574
- }, { once: true });
7626
+ };
7627
+ const recordBenchmark = async (tinybenchTasks, name, taskMeta, fromTasks) => {
7628
+ const serializedBenchmark = serializeBenchmark(tinybenchTasks, name, taskMeta, fromTasks);
7629
+ test.benchmarks.push(serializedBenchmark);
7630
+ await rpc().onTestBenchmark(test.id, serializedBenchmark);
7631
+ };
7632
+ const writeResultArtifact = async (template, result) => {
7633
+ const resolved = resolveTemplate(template);
7634
+ const data = {
7635
+ latency: result.latency,
7636
+ throughput: result.throughput,
7637
+ period: result.period,
7638
+ totalTime: result.totalTime
7575
7639
  };
7576
- benchmarkGroup.forEach((benchmark) => {
7577
- const benchmarkInstance = new Bench(getBenchOptions(benchmark));
7578
- const benchmarkFn = getBenchFn(benchmark);
7579
- benchmark.result = {
7580
- state: "run",
7581
- startTime: start,
7582
- benchmark: createBenchmarkResult(benchmark.name)
7583
- };
7584
- const task = new Task(benchmarkInstance, benchmark.name, benchmarkFn);
7585
- benchmarkTasks.set(benchmark, task);
7586
- addBenchTaskListener(task, benchmark);
7640
+ await rpc().writeBenchmarkResult(resolved, data);
7641
+ };
7642
+ const runBenchmarks = async (tinybench) => {
7643
+ const workerState = getWorkerState();
7644
+ const getterTracker = workerState.getterTracker;
7645
+ getterTracker?.resetInvocations();
7646
+ try {
7647
+ return await TestRunner.runBenchmarks(tinybench);
7648
+ } finally {
7649
+ const excessiveInvocations = config.benchmark.suppressExportGetterWarnings ? void 0 : getterTracker?.getExcessiveInvocations();
7650
+ if (excessiveInvocations?.length) {
7651
+ const entries = excessiveInvocations.map(({ moduleId, exportName }) => ` - ${formatModuleId(moduleId, workerState.config.root)} > ${exportName}`).join("\n");
7652
+ console.warn([
7653
+ c.yellow(c.bold("Benchmark Warning")),
7654
+ `Benchmark ${c.bold(`"${tinybench.name}"`)} accessed module export getters too many times.`,
7655
+ "",
7656
+ "This can make results unreliable because export getters add overhead.",
7657
+ "See https://vitest.dev/guide/benchmarking#module-runner-overhead",
7658
+ "",
7659
+ "Tracked exports:",
7660
+ entries
7661
+ ].join("\n"));
7662
+ }
7663
+ }
7664
+ };
7665
+ const runSingle = async (name, fn, fnOpts, options, meta, writeResult) => {
7666
+ const tinybench = createTinybench(options).add(name, fn, fnOpts);
7667
+ const tasks = await runBenchmarks(tinybench);
7668
+ const task = tinybench.getTask(name);
7669
+ if (task.result.state === "errored") throw task.result.error;
7670
+ await recordBenchmark(tasks, tinybench.name, meta ? new Map([[name, meta]]) : void 0);
7671
+ if (writeResult) await writeResultArtifact(writeResult, task.result);
7672
+ return task.result;
7673
+ };
7674
+ const runFrom = async (name, source) => {
7675
+ const data = await resolveFromSource(source);
7676
+ const benchmark = {
7677
+ name: test.fullTestName,
7678
+ tasks: [{
7679
+ ...taskFromBaseline(name, data),
7680
+ rank: 1
7681
+ }]
7682
+ };
7683
+ test.benchmarks.push(benchmark);
7684
+ await rpc().onTestBenchmark(test.id, benchmark);
7685
+ return data;
7686
+ };
7687
+ const bench = (nameOrFunction, a, b) => {
7688
+ validateBenchmarkProject(config);
7689
+ const { fn, fnOpts, writeResult, perProject } = normalizeBenchArgs(a, b);
7690
+ const name = typeof nameOrFunction === "function" ? nameOrFunction.name || "<anonymous>" : nameOrFunction;
7691
+ const meta = perProject ? { perProject: true } : void 0;
7692
+ const registration = {
7693
+ [kRegistration]: true,
7694
+ name,
7695
+ fn,
7696
+ fnOpts,
7697
+ run: (options) => {
7698
+ pending.delete(registration);
7699
+ return runSingle(name, fn, fnOpts, options, meta, writeResult);
7700
+ }
7701
+ };
7702
+ if (perProject) registration[kPerProject] = true;
7703
+ if (writeResult) registration[kWriteResult] = writeResult;
7704
+ pending.add(registration);
7705
+ return registration;
7706
+ };
7707
+ bench.from = (nameOrFunction, source) => {
7708
+ validateBenchmarkProject(config);
7709
+ if (typeof nameOrFunction !== "string" && typeof nameOrFunction !== "function") throw new TypeError("`bench.from()` requires a name (string or named function) as its first argument.");
7710
+ if (typeof source !== "string" && typeof source !== "function") throw new TypeError("`bench.from()` expects a string path or a function returning the result data as its second argument.");
7711
+ const name = typeof nameOrFunction === "function" ? nameOrFunction.name || "<anonymous>" : nameOrFunction;
7712
+ const registration = {
7713
+ [kRegistration]: true,
7714
+ [kFromSource]: source,
7715
+ name,
7716
+ run: () => {
7717
+ pending.delete(registration);
7718
+ return runFrom(name, source);
7719
+ }
7720
+ };
7721
+ pending.add(registration);
7722
+ return registration;
7723
+ };
7724
+ bench.compare = async (...args) => {
7725
+ validateBenchmarkProject(config);
7726
+ // extract optional trailing BenchCompareOptions argument
7727
+ const lastArg = args[args.length - 1];
7728
+ const benchOptions = lastArg != null && typeof lastArg === "object" && !(kRegistration in lastArg) ? args.pop() : void 0;
7729
+ const registrations = args;
7730
+ // Mark every passed-in registration as consumed before validation so a
7731
+ // throwing `bench.compare()` (wrong arity, wrong shape) doesn't also
7732
+ // trigger the unrun-bench warning — the user's intent was to consume them.
7733
+ for (const reg of registrations) if (reg != null && typeof reg === "object" && kRegistration in reg) pending.delete(reg);
7734
+ if (registrations.length < 2) throw new SyntaxError(`\`bench.compare()\` requires at least 2 benchmarks, received ${registrations.length} instead. ${registrations.length === 1 ? "Consider calling `bench().run()`. " : "Define benchmarks by calling `bench()`. "}See https://vitest.dev/guide/benchmarking#comparing-benchmarks`);
7735
+ for (const reg of registrations) if (reg == null || typeof reg !== "object" || !(kRegistration in reg)) throw new SyntaxError("`bench.compare()` expects every argument to be the return value of `bench` or `bench.from`.");
7736
+ const runnable = [];
7737
+ const fromEntries = [];
7738
+ for (const reg of registrations) if (isFromRegistration(reg)) fromEntries.push(reg);
7739
+ else runnable.push(reg);
7740
+ const taskMeta = /* @__PURE__ */ new Map();
7741
+ for (const reg of runnable) if (reg[kPerProject]) taskMeta.set(reg.name, { perProject: true });
7742
+ const fromResults = /* @__PURE__ */ new Map();
7743
+ const fromTasks = [];
7744
+ if (fromEntries.length > 0) {
7745
+ const resolved = await Promise.all(fromEntries.map(async (reg) => {
7746
+ return {
7747
+ reg,
7748
+ data: await resolveFromSource(reg[kFromSource])
7749
+ };
7750
+ }));
7751
+ for (const { reg, data } of resolved) {
7752
+ fromResults.set(reg.name, data);
7753
+ fromTasks.push(taskFromBaseline(reg.name, data));
7754
+ }
7755
+ }
7756
+ const tinybench = createTinybench(benchOptions);
7757
+ runnable.forEach((reg) => {
7758
+ tinybench.add(reg.name, reg.fn, reg.fnOpts);
7587
7759
  });
7588
- const { setTimeout } = getSafeTimers();
7589
- const tasks = [];
7590
- for (const benchmark of benchmarkGroup) {
7591
- const task = benchmarkTasks.get(benchmark);
7592
- updateTask$1("test-prepare", benchmark);
7593
- await task.warmup();
7594
- tasks.push([await new Promise((resolve) => setTimeout(async () => {
7595
- resolve(await task.run());
7596
- })), benchmark]);
7760
+ let tasks = [];
7761
+ if (runnable.length > 0) {
7762
+ tasks = await runBenchmarks(tinybench);
7763
+ const errors = tinybench.tasks.filter((task) => task.result.state === "errored").map((task) => task.result.error);
7764
+ if (errors.length > 0) throw new AggregateError(errors, "Some benchmarks failed");
7597
7765
  }
7598
- suite.result.duration = performance.now() - start;
7599
- suite.result.state = "pass";
7600
- updateTask$1("suite-finished", suite);
7601
- defer.resolve(null);
7602
- await defer;
7603
- }
7604
- function updateTask$1(event, task) {
7605
- updateTask(event, task, runner);
7606
- }
7766
+ await recordBenchmark(tasks, tinybench.name, taskMeta, fromTasks);
7767
+ // write artifacts for every runnable registration that requested it. We
7768
+ // do this after recording so a write failure can't be confused with a
7769
+ // benchmark failure in the reporter output.
7770
+ await Promise.all(runnable.filter((reg) => reg[kWriteResult] != null).map((reg) => {
7771
+ const task = tinybench.getTask(reg.name);
7772
+ return writeResultArtifact(reg[kWriteResult], task.result);
7773
+ }));
7774
+ return createCompareStorage(tinybench, fromResults);
7775
+ };
7776
+ bench[kFinalize] = () => {
7777
+ if (pending.size === 0) return;
7778
+ const names = [...pending].map((reg) => `"${reg.name}"`).join(", ");
7779
+ pending.clear();
7780
+ console.warn([
7781
+ c.yellow(c.bold("Benchmark Warning")),
7782
+ `Test ${c.bold(`"${test.fullTestName}"`)} registered benchmarks that never ran: ${names}.`,
7783
+ "",
7784
+ "Call `.run()` on the registration, or pass it to `bench.compare()`.",
7785
+ "See https://vitest.dev/guide/benchmarking#defining-a-benchmark"
7786
+ ].join("\n"));
7787
+ };
7788
+ return bench;
7607
7789
  }
7608
- class NodeBenchmarkRunner {
7609
- moduleRunner;
7610
- constructor(config) {
7611
- this.config = config;
7612
- }
7613
- async importTinybench() {
7614
- return await import('tinybench');
7615
- }
7616
- importFile(filepath, source) {
7617
- if (source === "setup") {
7618
- const moduleNode = getWorkerState().evaluatedModules.getModuleById(filepath);
7619
- if (moduleNode) getWorkerState().evaluatedModules.invalidateModule(moduleNode);
7620
- }
7621
- return this.moduleRunner.import(filepath);
7622
- }
7623
- async runSuite(suite) {
7624
- await runBenchmarkSuite(suite, this);
7625
- }
7626
- async runTask() {
7627
- throw new Error("`test()` and `it()` is only available in test mode.");
7790
+ function formatModuleId(moduleId, root) {
7791
+ if (!root || !isAbsolute(moduleId)) return moduleId;
7792
+ return relative(root, moduleId);
7793
+ }
7794
+ function normalizeBenchArgs(a, b) {
7795
+ if (typeof a === "function") {
7796
+ if (b !== void 0) throw new TypeError("`bench()` does not accept options as the third argument. Pass options as the second argument instead: `bench(name, options, fn)`.");
7797
+ return {
7798
+ fn: a,
7799
+ fnOpts: void 0,
7800
+ writeResult: void 0,
7801
+ perProject: false
7802
+ };
7628
7803
  }
7804
+ if (typeof b !== "function") throw new TypeError("`bench()` expects a benchmark function. Call `bench(name, fn)` or `bench(name, options, fn)`.");
7805
+ // Strip vitest-specific fields only when present so we don't allocate a new
7806
+ // object — preserving referential identity matters: users inspect
7807
+ // `registration.fnOpts` and tinybench's `add` sees the same object the
7808
+ // caller passed in.
7809
+ if (a.writeResult === void 0 && a.perProject === void 0) return {
7810
+ fn: b,
7811
+ fnOpts: a,
7812
+ writeResult: void 0,
7813
+ perProject: false
7814
+ };
7815
+ const { writeResult, perProject, ...fnOpts } = a;
7816
+ return {
7817
+ fn: b,
7818
+ fnOpts: Object.keys(fnOpts).length > 0 ? fnOpts : void 0,
7819
+ writeResult,
7820
+ perProject: perProject ?? false
7821
+ };
7822
+ }
7823
+ function validateBenchmarkProject(config) {
7824
+ if (!config.benchmark.enabled) throw new Error("Cannot use the `bench` test-context fixture within a regular test run. Benchmarks are inherently flaky, so Vitest runs them in a dedicated project based on the `benchmark.include` pattern (default `**/*.{bench,benchmark}.?(c|m)[jt]s?(x)`). Move this code to a file matched by `benchmark.include`, and make sure `bench` is destructured from the test context (`test('...', async ({ bench }) => { ... })`) — it is not a top-level export of `vitest`. See https://vitest.dev/guide/benchmarking#stability");
7629
7825
  }
7630
7826
 
7631
7827
  class TestRunner {
@@ -7634,7 +7830,11 @@ class TestRunner {
7634
7830
  moduleRunner;
7635
7831
  cancelRun = false;
7636
7832
  assertionsErrors = /* @__PURE__ */ new WeakMap();
7833
+ benchInstances = /* @__PURE__ */ new WeakMap();
7637
7834
  pool = this.workerState.ctx.pool;
7835
+ /**
7836
+ * @internal
7837
+ */
7638
7838
  _otel;
7639
7839
  viteEnvironment;
7640
7840
  viteModuleRunner;
@@ -7660,7 +7860,7 @@ class TestRunner {
7660
7860
  onCleanupWorkerContext(listener) {
7661
7861
  this.workerState.onCleanup(listener);
7662
7862
  }
7663
- onAfterRunFiles() {
7863
+ onAfterRunFiles(_files) {
7664
7864
  this.snapshotClient.clear();
7665
7865
  this.workerState.current = void 0;
7666
7866
  }
@@ -7707,7 +7907,7 @@ class TestRunner {
7707
7907
  if (suite.mode !== "skip" && "filepath" in suite) await this.snapshotClient.setup(suite.file.filepath, this.workerState.config.snapshotOptions);
7708
7908
  this.workerState.current = suite;
7709
7909
  }
7710
- onBeforeTryTask(test) {
7910
+ onBeforeTryTask(test, _options) {
7711
7911
  clearModuleMocks(this.config);
7712
7912
  this.snapshotClient.clearTest(test.file.filepath, test.id);
7713
7913
  setState({
@@ -7721,6 +7921,7 @@ class TestRunner {
7721
7921
  }, globalThis[GLOBAL_EXPECT]);
7722
7922
  }
7723
7923
  onAfterTryTask(test) {
7924
+ this.benchInstances.get(test)?.[kFinalize]();
7724
7925
  const { assertionCalls, expectedAssertionsNumber, expectedAssertionsNumberErrorGen, isExpectingAssertions, isExpectingAssertionsError } = test.context._local ? test.context.expect.getState() : getState(globalThis[GLOBAL_EXPECT]);
7725
7926
  if (expectedAssertionsNumber !== null && assertionCalls !== expectedAssertionsNumber) throw expectedAssertionsNumberErrorGen();
7726
7927
  if (isExpectingAssertions === true && assertionCalls === 0) throw isExpectingAssertionsError;
@@ -7737,6 +7938,16 @@ class TestRunner {
7737
7938
  Object.defineProperty(context, "_local", { get() {
7738
7939
  return _expect != null;
7739
7940
  } });
7941
+ let _bench;
7942
+ const runnerConfig = this.config;
7943
+ const benchInstances = this.benchInstances;
7944
+ Object.defineProperty(context, "bench", { get() {
7945
+ if (!_bench) {
7946
+ _bench = createBench(context.task, runnerConfig);
7947
+ benchInstances.set(context.task, _bench);
7948
+ }
7949
+ return _bench;
7950
+ } });
7740
7951
  return context;
7741
7952
  }
7742
7953
  getImportDurations() {
@@ -7771,13 +7982,13 @@ class TestRunner {
7771
7982
  static setTestFn = getFn;
7772
7983
  static matchesTags = matchesTags;
7773
7984
  /**
7774
- * @deprecated
7985
+ * @experimental
7986
+ * A function that runs tinybench tasks.
7987
+ * Can be overriden to run tasks in a special environment.
7775
7988
  */
7776
- static getBenchFn = getBenchFn;
7777
- /**
7778
- * @deprecated
7779
- */
7780
- static getBenchOptions = getBenchOptions;
7989
+ static async runBenchmarks(tinybench) {
7990
+ return await tinybench.run();
7991
+ }
7781
7992
  }
7782
7993
  function clearModuleMocks(config) {
7783
7994
  const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = config;
@@ -7792,7 +8003,6 @@ const assertType = function assertType() {};
7792
8003
 
7793
8004
  var index = /*#__PURE__*/Object.freeze({
7794
8005
  __proto__: null,
7795
- BenchmarkRunner: NodeBenchmarkRunner,
7796
8006
  EvaluatedModules: VitestEvaluatedModules,
7797
8007
  Snapshots: Snapshots,
7798
8008
  TestRunner: TestRunner,
@@ -7804,7 +8014,6 @@ var index = /*#__PURE__*/Object.freeze({
7804
8014
  assertType: assertType,
7805
8015
  beforeAll: beforeAll,
7806
8016
  beforeEach: beforeEach,
7807
- bench: bench,
7808
8017
  chai: chai,
7809
8018
  createExpect: createExpect,
7810
8019
  describe: describe,
@@ -7822,4 +8031,4 @@ var index = /*#__PURE__*/Object.freeze({
7822
8031
  vitest: vitest
7823
8032
  });
7824
8033
 
7825
- export { NodeBenchmarkRunner as N, Snapshots as S, TestRunner as T, assert as a, assertType as b, bench as c, createExpect as d, inject as e, vitest as f, globalExpect as g, index as i, should as s, vi as v };
8034
+ export { Snapshots as S, TestRunner as T, assert as a, assertType as b, createExpect as c, inject as d, vitest as e, globalExpect as g, index as i, should as s, vi as v };