vitest 4.0.9 → 4.0.11

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 (58) hide show
  1. package/dist/browser.d.ts +4 -3
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/_commonjsHelpers.D26ty3Ew.js +6 -0
  4. package/dist/chunks/{base.CiIV2DDC.js → base.DiCUKpyF.js} +47 -40
  5. package/dist/chunks/{browser.d.DnU_kh8a.d.ts → browser.d.D-d8eZY4.d.ts} +1 -1
  6. package/dist/chunks/{cac.B_NTJoIH.js → cac.aVhqBj0-.js} +21 -7
  7. package/dist/chunks/{cli-api.D48wY175.js → cli-api.-bIZD4XU.js} +960 -345
  8. package/dist/chunks/{coverage.BUlIqJrL.js → coverage.CtyeYmKM.js} +7 -1
  9. package/dist/chunks/{creator.BzqvXeRE.js → creator.DAmOKTvJ.js} +5 -5
  10. package/dist/chunks/{global.d.BQDgW9Pr.d.ts → global.d.uY4Q0M5z.d.ts} +1 -1
  11. package/dist/chunks/{globals.DBrtKPdh.js → globals.C0izxiX3.js} +3 -3
  12. package/dist/chunks/{index.op2Re5rn.js → index.CMvpbrsJ.js} +1 -1
  13. package/dist/chunks/{index.CPA8jGhR.js → index.CQwQ_SLL.js} +16 -4
  14. package/dist/chunks/{index.z7NPOg2E.js → index.D4KonVSU.js} +1 -1
  15. package/dist/chunks/{index.CGezRSGU.js → index.DBx1AtPJ.js} +3 -2
  16. package/dist/chunks/{index.kotH7DY7.js → index.DWDW6mLz.js} +15 -4
  17. package/dist/chunks/{index.BfmpdV5p.js → index.QWbK7rHY.js} +3 -3
  18. package/dist/chunks/init-forks.DIuGPyId.js +32 -0
  19. package/dist/chunks/{init-threads.C7T0-YMD.js → init-threads.jC_8JdoN.js} +3 -3
  20. package/dist/chunks/{init.BQhNfT0h.js → init.B3IeC_yW.js} +60 -22
  21. package/dist/chunks/{moduleRunner.d.BxT-OjLR.d.ts → moduleRunner.d.B5SW5pMI.d.ts} +9 -1
  22. package/dist/chunks/plugin.d.N8khPRFb.d.ts +38 -0
  23. package/dist/chunks/{reporters.d.BQ0wpUaj.d.ts → reporters.d.DgZLBdyd.d.ts} +109 -48
  24. package/dist/chunks/{setup-common.Dw1XgX0v.js → setup-common.DGHc_BUK.js} +2 -2
  25. package/dist/chunks/{startModuleRunner.DLjmA_wU.js → startModuleRunner.DaBMy1JT.js} +97 -49
  26. package/dist/chunks/{test.w5HLbjmU.js → test.DqQZzsWf.js} +15 -5
  27. package/dist/chunks/traces.BVPrsYso.js +151 -0
  28. package/dist/chunks/{config.d.BTfZNUu9.d.ts → traces.d.B8ukBJqA.d.ts} +36 -1
  29. package/dist/chunks/{vi.CyIUVSoU.js → vi.BiaV1qII.js} +2 -2
  30. package/dist/chunks/{vm.DXN8eCh2.js → vm.BKyGp1KW.js} +12 -8
  31. package/dist/chunks/{worker.d.ZGohxCEd.d.ts → worker.d.B_PZTrCQ.d.ts} +5 -4
  32. package/dist/cli.js +2 -2
  33. package/dist/config.d.ts +8 -7
  34. package/dist/coverage.d.ts +6 -5
  35. package/dist/coverage.js +2 -2
  36. package/dist/environments.js +1 -1
  37. package/dist/index.d.ts +14 -12
  38. package/dist/index.js +4 -4
  39. package/dist/module-evaluator.d.ts +7 -6
  40. package/dist/module-evaluator.js +14 -1
  41. package/dist/module-runner.js +2 -1
  42. package/dist/node.d.ts +11 -10
  43. package/dist/node.js +12 -11
  44. package/dist/reporters.d.ts +6 -5
  45. package/dist/reporters.js +2 -2
  46. package/dist/runners.d.ts +5 -1
  47. package/dist/runners.js +3 -3
  48. package/dist/worker.d.ts +8 -7
  49. package/dist/worker.js +11 -10
  50. package/dist/workers/forks.js +12 -12
  51. package/dist/workers/runVmTests.js +24 -22
  52. package/dist/workers/threads.js +12 -11
  53. package/dist/workers/vmForks.js +6 -6
  54. package/dist/workers/vmThreads.js +6 -5
  55. package/package.json +19 -14
  56. package/dist/chunks/_commonjsHelpers.BFTU3MAI.js +0 -7
  57. package/dist/chunks/init-forks.aqTzCSR2.js +0 -65
  58. package/dist/chunks/plugin.d.DevON6kQ.d.ts +0 -9
@@ -1,20 +1,21 @@
1
- import fs, { promises as promises$1, existsSync, mkdirSync, readFileSync, statSync, readdirSync, writeFileSync } from 'node:fs';
1
+ import fs, { promises, existsSync, mkdirSync, readFileSync, statSync, readdirSync, writeFileSync } from 'node:fs';
2
2
  import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
4
4
  import path, { resolve as resolve$1 } from 'node:path';
5
- import { noop, createDefer, slash, isExternalUrl, unwrapId, nanoid, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
6
- import { a as any, p as prompt } from './index.z7NPOg2E.js';
7
- import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig, d as resolveModule } from './coverage.BUlIqJrL.js';
5
+ import { noop, createDefer, slash, isExternalUrl, unwrapId, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
6
+ import { a as any, p as prompt } from './index.D4KonVSU.js';
7
+ import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig, d as resolveModule } from './coverage.CtyeYmKM.js';
8
8
  import * as vite from 'vite';
9
- import { parseAst, fetchModule, version, searchForWorkspaceRoot, mergeConfig, createServer } from 'vite';
9
+ import { parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer } from 'vite';
10
10
  import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.D_Q9UYh-.js';
11
11
  import * as nodeos from 'node:os';
12
12
  import nodeos__default, { tmpdir } from 'node:os';
13
13
  import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
14
14
  import { SnapshotManager } from '@vitest/snapshot/manager';
15
- import { v as version$1 } from './cac.B_NTJoIH.js';
15
+ import { v as version$1 } from './cac.aVhqBj0-.js';
16
+ import { performance as performance$1 } from 'node:perf_hooks';
16
17
  import { c as createBirpc } from './index.0kCJoeWi.js';
17
- import { p as parse, d as stringify, e as TraceMap, o as originalPositionFor, h as ancestor, i as printError, f as formatProjectName, w as withLabel, j as errorBanner, k as divider, l as Typechecker, m as generateCodeFrame, n as createDefinesScript, R as ReportersMap, B as BlobReporter, r as readBlobs, q as convertTasksToEvents, H as HangingProcessReporter } from './index.kotH7DY7.js';
18
+ import { p as parse, d as stringify, e as TraceMap, o as originalPositionFor, h as ancestor, i as printError, f as formatProjectName, w as withLabel, j as errorBanner, k as divider, l as Typechecker, m as generateCodeFrame, n as createDefinesScript, R as ReportersMap, B as BlobReporter, r as readBlobs, q as convertTasksToEvents, H as HangingProcessReporter } from './index.DWDW6mLz.js';
18
19
  import require$$0$3 from 'events';
19
20
  import require$$1$1 from 'https';
20
21
  import require$$2 from 'http';
@@ -25,15 +26,16 @@ import require$$0$2 from 'stream';
25
26
  import require$$7 from 'url';
26
27
  import require$$0 from 'zlib';
27
28
  import require$$0$1 from 'buffer';
28
- import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
29
+ import { g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
29
30
  import crypto, { createHash } from 'node:crypto';
30
31
  import { rootDir, distDir } from '../path.js';
32
+ import { T as Traces } from './traces.BVPrsYso.js';
31
33
  import createDebug from 'debug';
32
- import { readFile, writeFile, rename, stat, unlink, rm, mkdir, copyFile } from 'node:fs/promises';
34
+ import { rm, readFile, writeFile, rename, stat, unlink, mkdir, copyFile } from 'node:fs/promises';
35
+ import c from 'tinyrainbow';
33
36
  import { VitestModuleEvaluator } from '#module-evaluator';
34
37
  import { ModuleRunner } from 'vite/module-runner';
35
38
  import { Console } from 'node:console';
36
- import c from 'tinyrainbow';
37
39
  import { highlight } from '@vitest/utils/highlight';
38
40
  import { createRequire, isBuiltin, builtinModules } from 'node:module';
39
41
  import url, { fileURLToPath, pathToFileURL } from 'node:url';
@@ -41,7 +43,6 @@ import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
41
43
  import { isatty } from 'node:tty';
42
44
  import EventEmitter$1, { EventEmitter } from 'node:events';
43
45
  import { fork } from 'node:child_process';
44
- import v8 from 'node:v8';
45
46
  import { Worker } from 'node:worker_threads';
46
47
  import pm from 'picomatch';
47
48
  import { glob, isDynamicPattern } from 'tinyglobby';
@@ -51,7 +52,7 @@ import { c as configDefaults } from './defaults.BOqNVLsY.js';
51
52
  import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
52
53
  import { findNearestPackageData } from '@vitest/utils/resolver';
53
54
  import * as esModuleLexer from 'es-module-lexer';
54
- import { a as BenchmarkReportsMap } from './index.op2Re5rn.js';
55
+ import { a as BenchmarkReportsMap } from './index.CMvpbrsJ.js';
55
56
  import assert$1 from 'node:assert';
56
57
  import { serializeValue } from '@vitest/utils/serialize';
57
58
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
@@ -5124,11 +5125,11 @@ function setup(ctx, _server) {
5124
5125
  },
5125
5126
  async readTestFile(id) {
5126
5127
  if (!ctx.state.filesMap.has(id) || !existsSync(id)) return null;
5127
- return promises$1.readFile(id, "utf-8");
5128
+ return promises.readFile(id, "utf-8");
5128
5129
  },
5129
5130
  async saveTestFile(id, content) {
5130
5131
  if (!ctx.state.filesMap.has(id) || !existsSync(id)) throw new Error(`Test file "${id}" was not registered, so it cannot be updated using the API.`);
5131
- return promises$1.writeFile(id, content, "utf-8");
5132
+ return promises.writeFile(id, content, "utf-8");
5132
5133
  },
5133
5134
  async rerun(files, resetTestNamePattern) {
5134
5135
  await ctx.rerunFiles(files, void 0, true, resetTestNamePattern);
@@ -5150,7 +5151,7 @@ function setup(ctx, _server) {
5150
5151
  const result = browser ? await project.browser.vite.transformRequest(id) : await project.vite.transformRequest(id);
5151
5152
  if (result) {
5152
5153
  try {
5153
- result.source = result.source || await promises$1.readFile(id, "utf-8");
5154
+ result.source = result.source || await promises.readFile(id, "utf-8");
5154
5155
  } catch {}
5155
5156
  return result;
5156
5157
  }
@@ -5198,6 +5199,8 @@ function setup(ctx, _server) {
5198
5199
  ctx.reporters.push(new WebSocketReporter(ctx, wss, clients));
5199
5200
  }
5200
5201
  class WebSocketReporter {
5202
+ start = 0;
5203
+ end = 0;
5201
5204
  constructor(ctx, wss, clients) {
5202
5205
  this.ctx = ctx;
5203
5206
  this.wss = wss;
@@ -5211,6 +5214,7 @@ class WebSocketReporter {
5211
5214
  }
5212
5215
  onTestRunStart(specifications) {
5213
5216
  if (this.clients.size === 0) return;
5217
+ this.start = performance$1.now();
5214
5218
  const serializedSpecs = specifications.map((spec) => spec.toJSON());
5215
5219
  this.clients.forEach((client) => {
5216
5220
  client.onSpecsCollected?.(serializedSpecs)?.catch?.(noop);
@@ -5222,18 +5226,33 @@ class WebSocketReporter {
5222
5226
  client.onTestAnnotate?.(testCase.id, annotation)?.catch?.(noop);
5223
5227
  });
5224
5228
  }
5229
+ async onTestCaseArtifactRecord(testCase, artifact) {
5230
+ if (this.clients.size === 0) return;
5231
+ this.clients.forEach((client) => {
5232
+ client.onTestArtifactRecord?.(testCase.id, artifact)?.catch?.(noop);
5233
+ });
5234
+ }
5225
5235
  async onTaskUpdate(packs, events) {
5226
5236
  if (this.clients.size === 0) return;
5227
5237
  this.clients.forEach((client) => {
5228
5238
  client.onTaskUpdate?.(packs, events)?.catch?.(noop);
5229
5239
  });
5230
5240
  }
5241
+ sum(items, cb) {
5242
+ return items.reduce((total, next) => {
5243
+ return total + Math.max(cb(next) || 0, 0);
5244
+ }, 0);
5245
+ }
5231
5246
  onTestRunEnd(testModules, unhandledErrors) {
5232
5247
  if (!this.clients.size) return;
5233
5248
  const files = testModules.map((testModule) => testModule.task);
5234
5249
  const errors = [...unhandledErrors];
5250
+ this.end = performance$1.now();
5251
+ const blobs = this.ctx.state.blobs;
5252
+ // Execution time is either sum of all runs of `--merge-reports` or the current run's time
5253
+ const executionTime = blobs?.executionTimes ? this.sum(blobs.executionTimes, (time) => time) : this.end - this.start;
5235
5254
  this.clients.forEach((client) => {
5236
- client.onFinished?.(files, errors)?.catch?.(noop);
5255
+ client.onFinished?.(files, errors, void 0, executionTime)?.catch?.(noop);
5237
5256
  });
5238
5257
  }
5239
5258
  onFinishedReportCoverage() {
@@ -5488,7 +5507,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
5488
5507
  dynamic: definition.dynamic,
5489
5508
  meta: {},
5490
5509
  timeout: 0,
5491
- annotations: []
5510
+ annotations: [],
5511
+ artifacts: []
5492
5512
  };
5493
5513
  definition.task = task;
5494
5514
  latestSuite.tasks.push(task);
@@ -5633,8 +5653,9 @@ class ResultsCache {
5633
5653
  cachePath = null;
5634
5654
  version;
5635
5655
  root = "/";
5636
- constructor(version) {
5637
- this.version = version;
5656
+ constructor(logger) {
5657
+ this.logger = logger;
5658
+ this.version = Vitest.version;
5638
5659
  }
5639
5660
  getCachePath() {
5640
5661
  return this.cachePath;
@@ -5646,6 +5667,15 @@ class ResultsCache {
5646
5667
  getResults(key) {
5647
5668
  return this.cache.get(key);
5648
5669
  }
5670
+ async clearCache() {
5671
+ if (this.cachePath && existsSync(this.cachePath)) {
5672
+ await rm(this.cachePath, {
5673
+ force: true,
5674
+ recursive: true
5675
+ });
5676
+ this.logger.log("[cache] cleared results cache at", this.cachePath);
5677
+ }
5678
+ }
5649
5679
  async readFromCache() {
5650
5680
  if (!this.cachePath) return;
5651
5681
  if (!fs.existsSync(this.cachePath)) return;
@@ -5698,8 +5728,8 @@ class ResultsCache {
5698
5728
  class VitestCache {
5699
5729
  results;
5700
5730
  stats = new FilesStatsCache();
5701
- constructor(version) {
5702
- this.results = new ResultsCache(version);
5731
+ constructor(logger) {
5732
+ this.results = new ResultsCache(logger);
5703
5733
  }
5704
5734
  getFileTestResults(key) {
5705
5735
  return this.results.getResults(key);
@@ -5712,93 +5742,526 @@ class VitestCache {
5712
5742
  }
5713
5743
  }
5714
5744
 
5715
- const created = /* @__PURE__ */ new Set();
5716
- const promises = /* @__PURE__ */ new Map();
5717
- function createFetchModuleFunction(resolver, tmpDir = join(tmpdir(), nanoid()), dump) {
5718
- return async (url, importer, environment, cacheFs, options) => {
5719
- // We are copy pasting Vite's externalization logic from `fetchModule` because
5720
- // we instead rely on our own `shouldExternalize` method because Vite
5721
- // doesn't support `resolve.external` in non SSR environments (jsdom/happy-dom)
5722
- if (url.startsWith("data:")) return {
5723
- externalize: url,
5724
- type: "builtin"
5725
- };
5726
- if (url === "/@vite/client" || url === "@vite/client")
5727
- // this will be stubbed
5745
+ const debugFs = createDebugger("vitest:cache:fs");
5746
+ const debugMemory = createDebugger("vitest:cache:memory");
5747
+ const cacheComment = "\n//# vitestCache=";
5748
+ const cacheCommentLength = 17;
5749
+ const METADATA_FILE = "_metadata.json";
5750
+ /**
5751
+ * @experimental
5752
+ */
5753
+ class FileSystemModuleCache {
5754
+ /**
5755
+ * Even though it's possible to override the folder of project's caches
5756
+ * We still keep a single metadata file for all projects because
5757
+ * - they can reference files between each other
5758
+ * - lockfile changes are reflected for the whole workspace, not just for a single project
5759
+ */
5760
+ rootCache;
5761
+ metadataFilePath;
5762
+ version = "1.0.0-beta.1";
5763
+ fsCacheRoots = /* @__PURE__ */ new WeakMap();
5764
+ fsEnvironmentHashMap = /* @__PURE__ */ new WeakMap();
5765
+ fsCacheKeyGenerators = /* @__PURE__ */ new Set();
5766
+ // this exists only to avoid the perf. cost of reading a file and generating a hash again
5767
+ // surprisingly, on some machines this has negligible effect
5768
+ fsCacheKeys = /* @__PURE__ */ new WeakMap();
5769
+ constructor(vitest) {
5770
+ this.vitest = vitest;
5771
+ const workspaceRoot = searchForWorkspaceRoot(vitest.vite.config.root);
5772
+ this.rootCache = vitest.config.experimental.fsModuleCachePath || join(workspaceRoot, "node_modules", ".experimental-vitest-cache");
5773
+ this.metadataFilePath = join(this.rootCache, METADATA_FILE);
5774
+ }
5775
+ defineCacheKeyGenerator(callback) {
5776
+ this.fsCacheKeyGenerators.add(callback);
5777
+ }
5778
+ async clearCache(log = true) {
5779
+ const fsCachePaths = this.vitest.projects.map((r) => {
5780
+ return r.config.experimental.fsModuleCachePath || this.rootCache;
5781
+ });
5782
+ const uniquePaths = Array.from(new Set(fsCachePaths));
5783
+ await Promise.all(uniquePaths.map((directory) => rm(directory, {
5784
+ force: true,
5785
+ recursive: true
5786
+ })));
5787
+ if (log) this.vitest.logger.log(`[cache] cleared fs module cache at ${uniquePaths.join(", ")}`);
5788
+ }
5789
+ async getCachedModule(cachedFilePath) {
5790
+ if (!existsSync(cachedFilePath)) {
5791
+ debugFs?.(`${c.red("[empty]")} ${cachedFilePath} doesn't exist, transforming by vite instead`);
5792
+ return;
5793
+ }
5794
+ const code = await readFile(cachedFilePath, "utf-8");
5795
+ const matchIndex = code.lastIndexOf(cacheComment);
5796
+ if (matchIndex === -1) {
5797
+ debugFs?.(`${c.red("[empty]")} ${cachedFilePath} exists, but doesn't have a ${cacheComment} comment, transforming by vite instead`);
5798
+ return;
5799
+ }
5800
+ const meta = this.fromBase64(code.slice(matchIndex + cacheCommentLength));
5801
+ if (meta.externalize) {
5802
+ debugFs?.(`${c.green("[read]")} ${meta.externalize} is externalized inside ${cachedFilePath}`);
5803
+ return {
5804
+ externalize: meta.externalize,
5805
+ type: meta.type
5806
+ };
5807
+ }
5808
+ debugFs?.(`${c.green("[read]")} ${meta.id} is cached in ${cachedFilePath}`);
5728
5809
  return {
5729
- externalize: "/@vite/client",
5730
- type: "module"
5810
+ id: meta.id,
5811
+ url: meta.url,
5812
+ file: meta.file,
5813
+ code,
5814
+ importers: meta.importers,
5815
+ mappings: meta.mappings
5731
5816
  };
5817
+ }
5818
+ async saveCachedModule(cachedFilePath, fetchResult, importers = [], mappings = false) {
5819
+ if ("externalize" in fetchResult) {
5820
+ debugFs?.(`${c.yellow("[write]")} ${fetchResult.externalize} is externalized inside ${cachedFilePath}`);
5821
+ await atomicWriteFile(cachedFilePath, `${cacheComment}${this.toBase64(fetchResult)}`);
5822
+ } else if ("code" in fetchResult) {
5823
+ const result = {
5824
+ file: fetchResult.file,
5825
+ id: fetchResult.id,
5826
+ url: fetchResult.url,
5827
+ importers,
5828
+ mappings
5829
+ };
5830
+ debugFs?.(`${c.yellow("[write]")} ${fetchResult.id} is cached in ${cachedFilePath}`);
5831
+ await atomicWriteFile(cachedFilePath, `${fetchResult.code}${cacheComment}${this.toBase64(result)}`);
5832
+ }
5833
+ }
5834
+ toBase64(obj) {
5835
+ const json = stringify(obj);
5836
+ return Buffer.from(json).toString("base64");
5837
+ }
5838
+ fromBase64(obj) {
5839
+ return parse(Buffer.from(obj, "base64").toString("utf-8"));
5840
+ }
5841
+ invalidateCachePath(environment, id) {
5842
+ debugFs?.(`cache for ${id} in ${environment.name} environment is invalidated`);
5843
+ this.fsCacheKeys.get(environment)?.delete(id);
5844
+ }
5845
+ invalidateAllCachePaths(environment) {
5846
+ debugFs?.(`the ${environment.name} environment cache is invalidated`);
5847
+ this.fsCacheKeys.get(environment)?.clear();
5848
+ }
5849
+ getMemoryCachePath(environment, id) {
5850
+ const result = this.fsCacheKeys.get(environment)?.get(id);
5851
+ if (result != null) debugMemory?.(`${c.green("[read]")} ${id} was cached in ${result}`);
5852
+ else if (result === null) debugMemory?.(`${c.green("[read]")} ${id} was bailed out`);
5853
+ return result;
5854
+ }
5855
+ generateCachePath(vitestConfig, environment, resolver, id, fileContent) {
5856
+ let hashString = "";
5857
+ // bail out if file has import.meta.glob because it depends on other files
5858
+ // TODO: figure out a way to still support it
5859
+ if (fileContent.includes("import.meta.glob(")) {
5860
+ this.saveMemoryCache(environment, id, null);
5861
+ debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out`);
5862
+ return null;
5863
+ }
5864
+ for (const generator of this.fsCacheKeyGenerators) {
5865
+ const result = generator({
5866
+ environment,
5867
+ id,
5868
+ sourceCode: fileContent
5869
+ });
5870
+ if (typeof result === "string") hashString += result;
5871
+ if (result === false) {
5872
+ this.saveMemoryCache(environment, id, null);
5873
+ debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out by a custom generator`);
5874
+ return null;
5875
+ }
5876
+ }
5877
+ const config = environment.config;
5878
+ // coverage provider is dynamic, so we also clear the whole cache if
5879
+ // vitest.enableCoverage/vitest.disableCoverage is called
5880
+ const coverageAffectsCache = String(this.vitest.config.coverage.enabled && this.vitest.coverageProvider?.requiresTransform?.(id));
5881
+ let cacheConfig = this.fsEnvironmentHashMap.get(environment);
5882
+ if (!cacheConfig) {
5883
+ cacheConfig = JSON.stringify({
5884
+ root: config.root,
5885
+ base: config.base,
5886
+ mode: config.mode,
5887
+ consumer: config.consumer,
5888
+ resolve: config.resolve,
5889
+ plugins: config.plugins.map((p) => p.name),
5890
+ configFileDependencies: config.configFileDependencies.map((file) => tryReadFileSync(file)),
5891
+ environment: environment.name,
5892
+ css: vitestConfig.css,
5893
+ resolver: {
5894
+ inline: resolver.options.inline,
5895
+ external: resolver.options.external,
5896
+ inlineFiles: resolver.options.inlineFiles,
5897
+ moduleDirectories: resolver.options.moduleDirectories
5898
+ }
5899
+ }, (_, value) => {
5900
+ if (typeof value === "function" || value instanceof RegExp) return value.toString();
5901
+ return value;
5902
+ });
5903
+ this.fsEnvironmentHashMap.set(environment, cacheConfig);
5904
+ }
5905
+ hashString += id + fileContent + (process.env.NODE_ENV ?? "") + this.version + cacheConfig + coverageAffectsCache;
5906
+ const cacheKey = hash("sha1", hashString, "hex");
5907
+ let cacheRoot = this.fsCacheRoots.get(vitestConfig);
5908
+ if (cacheRoot == null) {
5909
+ cacheRoot = vitestConfig.experimental.fsModuleCachePath || this.rootCache;
5910
+ if (!existsSync(cacheRoot)) mkdirSync(cacheRoot, { recursive: true });
5911
+ }
5912
+ const fsResultPath = join(cacheRoot, cacheKey);
5913
+ debugMemory?.(`${c.yellow("[write]")} ${id} generated a cache in ${fsResultPath}`);
5914
+ this.saveMemoryCache(environment, id, fsResultPath);
5915
+ return fsResultPath;
5916
+ }
5917
+ saveMemoryCache(environment, id, cache) {
5918
+ let environmentKeys = this.fsCacheKeys.get(environment);
5919
+ if (!environmentKeys) {
5920
+ environmentKeys = /* @__PURE__ */ new Map();
5921
+ this.fsCacheKeys.set(environment, environmentKeys);
5922
+ }
5923
+ environmentKeys.set(id, cache);
5924
+ }
5925
+ async readMetadata() {
5926
+ // metadata is shared between every projects in the workspace, so we ignore project's fsModuleCachePath
5927
+ if (!existsSync(this.metadataFilePath)) return;
5928
+ try {
5929
+ const content = await readFile(this.metadataFilePath, "utf-8");
5930
+ return JSON.parse(content);
5931
+ } catch {}
5932
+ }
5933
+ // before vitest starts running tests, we check that the lockfile wasn't updated
5934
+ // if it was, we nuke the previous cache in case a custom plugin was updated
5935
+ // or a new version of vite/vitest is installed
5936
+ // for the same reason we also cache config file content, but that won't catch changes made in external plugins
5937
+ async ensureCacheIntegrity() {
5938
+ if (![this.vitest.getRootProject(), ...this.vitest.projects].some((p) => p.config.experimental.fsModuleCache)) return;
5939
+ const metadata = await this.readMetadata();
5940
+ const currentLockfileHash = getLockfileHash(this.vitest.vite.config.root);
5941
+ // no metadata found, just store a new one, don't reset the cache
5942
+ if (!metadata) {
5943
+ if (!existsSync(this.rootCache)) mkdirSync(this.rootCache, { recursive: true });
5944
+ debugFs?.(`fs metadata file was created with hash ${currentLockfileHash}`);
5945
+ await writeFile(this.metadataFilePath, JSON.stringify({ lockfileHash: currentLockfileHash }, null, 2), "utf-8");
5946
+ return;
5947
+ }
5948
+ // if lockfile didn't change, don't do anything
5949
+ if (metadata.lockfileHash === currentLockfileHash) return;
5950
+ // lockfile changed, let's clear all caches
5951
+ await this.clearCache(false);
5952
+ this.vitest.vite.config.logger.info(`fs cache was cleared because lockfile has changed`, {
5953
+ timestamp: true,
5954
+ environment: c.yellow("[vitest]")
5955
+ });
5956
+ debugFs?.(`fs cache was cleared because lockfile has changed`);
5957
+ }
5958
+ }
5959
+ /**
5960
+ * Performs an atomic write operation using the write-then-rename pattern.
5961
+ *
5962
+ * Why we need this:
5963
+ * - Ensures file integrity by never leaving partially written files on disk
5964
+ * - Prevents other processes from reading incomplete data during writes
5965
+ * - Particularly important for test files where incomplete writes could cause test failures
5966
+ *
5967
+ * The implementation writes to a temporary file first, then renames it to the target path.
5968
+ * This rename operation is atomic on most filesystems (including POSIX-compliant ones),
5969
+ * guaranteeing that other processes will only ever see the complete file.
5970
+ *
5971
+ * Added in https://github.com/vitest-dev/vitest/pull/7531
5972
+ */
5973
+ async function atomicWriteFile(realFilePath, data) {
5974
+ const tmpFilePath = join(dirname(realFilePath), `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
5975
+ try {
5976
+ await writeFile(tmpFilePath, data, "utf-8");
5977
+ await rename(tmpFilePath, realFilePath);
5978
+ } finally {
5979
+ try {
5980
+ if (await stat(tmpFilePath)) await unlink(tmpFilePath);
5981
+ } catch {}
5982
+ }
5983
+ }
5984
+ // lockfile hash resolution taken from vite
5985
+ // since this is experimental, we don't ask to expose it
5986
+ const lockfileFormats = [
5987
+ {
5988
+ path: "node_modules/.package-lock.json",
5989
+ checkPatchesDir: "patches",
5990
+ manager: "npm"
5991
+ },
5992
+ {
5993
+ path: "node_modules/.yarn-state.yml",
5994
+ checkPatchesDir: false,
5995
+ manager: "yarn"
5996
+ },
5997
+ {
5998
+ path: ".pnp.cjs",
5999
+ checkPatchesDir: ".yarn/patches",
6000
+ manager: "yarn"
6001
+ },
6002
+ {
6003
+ path: ".pnp.js",
6004
+ checkPatchesDir: ".yarn/patches",
6005
+ manager: "yarn"
6006
+ },
6007
+ {
6008
+ path: "node_modules/.yarn-integrity",
6009
+ checkPatchesDir: "patches",
6010
+ manager: "yarn"
6011
+ },
6012
+ {
6013
+ path: "node_modules/.pnpm/lock.yaml",
6014
+ checkPatchesDir: false,
6015
+ manager: "pnpm"
6016
+ },
6017
+ {
6018
+ path: ".rush/temp/shrinkwrap-deps.json",
6019
+ checkPatchesDir: false,
6020
+ manager: "pnpm"
6021
+ },
6022
+ {
6023
+ path: "bun.lock",
6024
+ checkPatchesDir: "patches",
6025
+ manager: "bun"
6026
+ },
6027
+ {
6028
+ path: "bun.lockb",
6029
+ checkPatchesDir: "patches",
6030
+ manager: "bun"
6031
+ }
6032
+ ].sort((_, { manager }) => {
6033
+ return process.env.npm_config_user_agent?.startsWith(manager) ? 1 : -1;
6034
+ });
6035
+ const lockfilePaths = lockfileFormats.map((l) => l.path);
6036
+ function getLockfileHash(root) {
6037
+ const lockfilePath = lookupFile(root, lockfilePaths);
6038
+ let content = lockfilePath ? fs.readFileSync(lockfilePath, "utf-8") : "";
6039
+ if (lockfilePath) {
6040
+ const normalizedLockfilePath = lockfilePath.replaceAll("\\", "/");
6041
+ const lockfileFormat = lockfileFormats.find((f) => normalizedLockfilePath.endsWith(f.path));
6042
+ if (lockfileFormat.checkPatchesDir) {
6043
+ const stat = tryStatSync(join(lockfilePath.slice(0, -lockfileFormat.path.length), lockfileFormat.checkPatchesDir));
6044
+ if (stat?.isDirectory()) content += stat.mtimeMs.toString();
6045
+ }
6046
+ }
6047
+ return hash("sha256", content, "hex").substring(0, 8).padEnd(8, "_");
6048
+ }
6049
+ function lookupFile(dir, fileNames) {
6050
+ while (dir) {
6051
+ for (const fileName of fileNames) {
6052
+ const fullPath = join(dir, fileName);
6053
+ if (tryStatSync(fullPath)?.isFile()) return fullPath;
6054
+ }
6055
+ const parentDir = dirname(dir);
6056
+ if (parentDir === dir) return;
6057
+ dir = parentDir;
6058
+ }
6059
+ }
6060
+ function tryReadFileSync(file) {
6061
+ try {
6062
+ return readFileSync(file, "utf-8");
6063
+ } catch {
6064
+ return "";
6065
+ }
6066
+ }
6067
+ function tryStatSync(file) {
6068
+ try {
6069
+ // The "throwIfNoEntry" is a performance optimization for cases where the file does not exist
6070
+ return fs.statSync(file, { throwIfNoEntry: false });
6071
+ } catch {}
6072
+ }
6073
+
6074
+ const saveCachePromises = /* @__PURE__ */ new Map();
6075
+ const readFilePromises = /* @__PURE__ */ new Map();
6076
+ class ModuleFetcher {
6077
+ tmpDirectories = /* @__PURE__ */ new Set();
6078
+ fsCacheEnabled;
6079
+ constructor(resolver, config, fsCache, traces, tmpProjectDir) {
6080
+ this.resolver = resolver;
6081
+ this.config = config;
6082
+ this.fsCache = fsCache;
6083
+ this.traces = traces;
6084
+ this.tmpProjectDir = tmpProjectDir;
6085
+ this.fsCacheEnabled = config.experimental?.fsModuleCache === true;
6086
+ }
6087
+ async fetch(trace, url, importer, environment, makeTmpCopies, options) {
6088
+ if (url.startsWith("data:")) {
6089
+ trace.setAttribute("vitest.module.external", url);
6090
+ return {
6091
+ externalize: url,
6092
+ type: "builtin"
6093
+ };
6094
+ }
6095
+ if (url === "/@vite/client" || url === "@vite/client") {
6096
+ trace.setAttribute("vitest.module.external", url);
6097
+ return {
6098
+ externalize: "/@vite/client",
6099
+ type: "module"
6100
+ };
6101
+ }
5732
6102
  const isFileUrl = url.startsWith("file://");
5733
- if (isExternalUrl(url) && !isFileUrl) return {
5734
- externalize: url,
5735
- type: "network"
5736
- };
5737
- // Vite does the same in `fetchModule`, but we want to externalize modules ourselves,
5738
- // so we do this first to resolve the module and check its `id`. The next call of
5739
- // `ensureEntryFromUrl` inside `fetchModule` is cached and should take no time
5740
- // This also makes it so externalized modules are inside the module graph.
6103
+ if (isExternalUrl(url) && !isFileUrl) {
6104
+ trace.setAttribute("vitest.module.external", url);
6105
+ return {
6106
+ externalize: url,
6107
+ type: "network"
6108
+ };
6109
+ }
5741
6110
  const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url));
5742
6111
  const cached = !!moduleGraphModule.transformResult;
5743
- // if url is already cached, we can just confirm it's also cached on the server
6112
+ if (moduleGraphModule.file) trace.setAttribute("code.file.path", moduleGraphModule.file);
5744
6113
  if (options?.cached && cached) return { cache: true };
5745
- if (moduleGraphModule.id) {
5746
- const externalize = await resolver.shouldExternalize(moduleGraphModule.id);
5747
- if (externalize) return {
5748
- externalize,
5749
- type: "module"
5750
- };
6114
+ const cachePath = await this.getCachePath(environment, moduleGraphModule);
6115
+ // full fs caching is disabled, but we still want to keep tmp files if makeTmpCopies is enabled
6116
+ // this is primarily used by the forks pool to avoid using process.send(bigBuffer)
6117
+ if (cachePath == null) {
6118
+ const result = await this.fetchAndProcess(environment, url, importer, moduleGraphModule, options);
6119
+ this.recordResult(trace, result);
6120
+ if (!makeTmpCopies || !("code" in result)) return result;
6121
+ const transformResult = moduleGraphModule.transformResult;
6122
+ const tmpPath = transformResult && Reflect.get(transformResult, "_vitest_tmp");
6123
+ if (typeof tmpPath === "string") return getCachedResult(result, tmpPath);
6124
+ const tmpDir = join(this.tmpProjectDir, environment.name);
6125
+ if (!this.tmpDirectories.has(tmpDir)) {
6126
+ if (!existsSync(tmpDir)) mkdirSync(tmpDir, { recursive: true });
6127
+ this.tmpDirectories.add(tmpDir);
6128
+ }
6129
+ const tmpFile = join(tmpDir, hash("sha1", result.id, "hex"));
6130
+ return this.cacheResult(result, tmpFile).then((result) => {
6131
+ if (transformResult) Reflect.set(transformResult, "_vitest_tmp", tmpFile);
6132
+ return result;
6133
+ });
5751
6134
  }
5752
- let moduleRunnerModule;
5753
- if (dump?.dumpFolder && dump.readFromDump) {
5754
- const path = resolve(dump?.dumpFolder, url.replace(/[^\w+]/g, "-"));
5755
- if (existsSync(path)) {
5756
- const code = await readFile(path, "utf-8");
5757
- const matchIndex = code.lastIndexOf("\n//");
5758
- if (matchIndex !== -1) {
5759
- const { id, file } = JSON.parse(code.slice(matchIndex + 4));
5760
- moduleRunnerModule = {
5761
- code,
5762
- id,
5763
- url,
5764
- file,
5765
- invalidate: false
5766
- };
5767
- }
6135
+ if (saveCachePromises.has(cachePath)) return saveCachePromises.get(cachePath).then((result) => {
6136
+ this.recordResult(trace, result);
6137
+ return result;
6138
+ });
6139
+ const cachedModule = await this.getCachedModule(cachePath, environment, moduleGraphModule);
6140
+ if (cachedModule) {
6141
+ this.recordResult(trace, cachedModule);
6142
+ return cachedModule;
6143
+ }
6144
+ const result = await this.fetchAndProcess(environment, url, importer, moduleGraphModule, options);
6145
+ const importers = this.getSerializedDependencies(moduleGraphModule);
6146
+ const map = moduleGraphModule.transformResult?.map;
6147
+ const mappings = map && !("version" in map) && map.mappings === "";
6148
+ return this.cacheResult(result, cachePath, importers, !!mappings);
6149
+ }
6150
+ getSerializedDependencies(node) {
6151
+ const dependencies = [];
6152
+ node.importers.forEach((importer) => {
6153
+ if (importer.id) dependencies.push(importer.id);
6154
+ });
6155
+ return dependencies;
6156
+ }
6157
+ recordResult(trace, result) {
6158
+ if ("externalize" in result) trace.setAttributes({
6159
+ "vitest.module.external": result.externalize,
6160
+ "vitest.fetched_module.type": result.type
6161
+ });
6162
+ if ("id" in result) {
6163
+ trace.setAttributes({
6164
+ "vitest.fetched_module.invalidate": result.invalidate,
6165
+ "vitest.fetched_module.id": result.id,
6166
+ "vitest.fetched_module.url": result.url,
6167
+ "vitest.fetched_module.cache": false
6168
+ });
6169
+ if (result.file) trace.setAttribute("code.file.path", result.file);
6170
+ }
6171
+ if ("code" in result) trace.setAttribute("vitest.fetched_module.code_length", result.code.length);
6172
+ }
6173
+ async getCachePath(environment, moduleGraphModule) {
6174
+ if (!this.fsCacheEnabled) return null;
6175
+ const moduleId = moduleGraphModule.id;
6176
+ const memoryCacheKey = this.fsCache.getMemoryCachePath(environment, moduleId);
6177
+ // undefined means there is no key in memory
6178
+ // null means the file should not be cached
6179
+ if (memoryCacheKey !== void 0) return memoryCacheKey;
6180
+ const fileContent = await this.readFileContentToCache(environment, moduleGraphModule);
6181
+ return this.fsCache.generateCachePath(this.config, environment, this.resolver, moduleGraphModule.id, fileContent);
6182
+ }
6183
+ async readFileContentToCache(environment, moduleGraphModule) {
6184
+ if (moduleGraphModule.file && !moduleGraphModule.file.startsWith("\0") && !moduleGraphModule.file.startsWith("virtual:")) {
6185
+ const result = await this.readFileConcurrently(moduleGraphModule.file);
6186
+ if (result != null) return result;
6187
+ }
6188
+ const loadResult = await environment.pluginContainer.load(moduleGraphModule.id);
6189
+ if (typeof loadResult === "string") return loadResult;
6190
+ if (loadResult != null) return loadResult.code;
6191
+ return "";
6192
+ }
6193
+ async getCachedModule(cachePath, environment, moduleGraphModule) {
6194
+ const cachedModule = await this.fsCache.getCachedModule(cachePath);
6195
+ if (cachedModule && "code" in cachedModule) {
6196
+ // keep the module graph in sync
6197
+ if (!moduleGraphModule.transformResult) {
6198
+ let map = extractSourceMap(cachedModule.code);
6199
+ if (map && cachedModule.file) map.file = cachedModule.file;
6200
+ // mappings is a special source map identifier in rollup
6201
+ if (!map && cachedModule.mappings) map = { mappings: "" };
6202
+ moduleGraphModule.transformResult = {
6203
+ code: cachedModule.code,
6204
+ map,
6205
+ ssr: true
6206
+ };
6207
+ // we populate the module graph to make the watch mode work because it relies on importers
6208
+ cachedModule.importers.forEach((importer) => {
6209
+ const environmentNode = environment.moduleGraph.getModuleById(importer);
6210
+ if (environmentNode) moduleGraphModule.importers.add(environmentNode);
6211
+ });
5768
6212
  }
6213
+ return {
6214
+ cached: true,
6215
+ file: cachedModule.file,
6216
+ id: cachedModule.id,
6217
+ tmp: cachePath,
6218
+ url: cachedModule.url,
6219
+ invalidate: false
6220
+ };
5769
6221
  }
5770
- if (!moduleRunnerModule) moduleRunnerModule = await fetchModule(environment, url, importer, {
6222
+ return cachedModule;
6223
+ }
6224
+ async fetchAndProcess(environment, url, importer, moduleGraphModule, options) {
6225
+ const externalize = await this.resolver.shouldExternalize(moduleGraphModule.id);
6226
+ if (externalize) return {
6227
+ externalize,
6228
+ type: "module"
6229
+ };
6230
+ return processResultSource(environment, await fetchModule(environment, url, importer, {
5771
6231
  ...options,
5772
6232
  inlineSourceMap: false
5773
- }).catch(handleRollupError);
5774
- const result = processResultSource(environment, moduleRunnerModule);
5775
- if (dump?.dumpFolder && "code" in result) await writeFile(resolve(dump?.dumpFolder, result.url.replace(/[^\w+]/g, "-")), `${result.code}\n// ${JSON.stringify({
5776
- id: result.id,
5777
- file: result.file
5778
- })}`, "utf-8");
5779
- if (!cacheFs || !("code" in result)) return result;
5780
- const code = result.code;
5781
- const transformResult = result.transformResult;
5782
- if (!transformResult) throw new Error(`"transformResult" in not defined. This is a bug in Vitest.`);
5783
- // to avoid serialising large chunks of code,
5784
- // we store them in a tmp file and read in the test thread
5785
- if ("_vitestTmp" in transformResult) return getCachedResult(result, Reflect.get(transformResult, "_vitestTmp"));
5786
- const dir = join(tmpDir, environment.name);
5787
- const tmp = join(dir, hash("sha1", result.id, "hex"));
5788
- if (!created.has(dir)) {
5789
- mkdirSync(dir, { recursive: true });
5790
- created.add(dir);
5791
- }
5792
- if (promises.has(tmp)) {
5793
- await promises.get(tmp);
5794
- return getCachedResult(result, tmp);
5795
- }
5796
- promises.set(tmp, atomicWriteFile(tmp, code).catch(() => writeFile(tmp, code, "utf-8")).finally(() => {
5797
- Reflect.set(transformResult, "_vitestTmp", tmp);
5798
- promises.delete(tmp);
5799
- }));
5800
- await promises.get(tmp);
5801
- return getCachedResult(result, tmp);
6233
+ }).catch(handleRollupError));
6234
+ }
6235
+ async cacheResult(result, cachePath, importers = [], mappings = false) {
6236
+ const returnResult = "code" in result ? getCachedResult(result, cachePath) : result;
6237
+ if (saveCachePromises.has(cachePath)) {
6238
+ await saveCachePromises.get(cachePath);
6239
+ return returnResult;
6240
+ }
6241
+ const savePromise = this.fsCache.saveCachedModule(cachePath, result, importers, mappings).then(() => result).finally(() => {
6242
+ saveCachePromises.delete(cachePath);
6243
+ });
6244
+ saveCachePromises.set(cachePath, savePromise);
6245
+ await savePromise;
6246
+ return returnResult;
6247
+ }
6248
+ readFileConcurrently(file) {
6249
+ if (!readFilePromises.has(file)) readFilePromises.set(
6250
+ file,
6251
+ // virtual file can have a "file" property
6252
+ readFile(file, "utf-8").catch(() => null).finally(() => {
6253
+ readFilePromises.delete(file);
6254
+ })
6255
+ );
6256
+ return readFilePromises.get(file);
6257
+ }
6258
+ }
6259
+ function createFetchModuleFunction(resolver, config, fsCache, traces, tmpProjectDir) {
6260
+ const fetcher = new ModuleFetcher(resolver, config, fsCache, traces, tmpProjectDir);
6261
+ return async (url, importer, environment, cacheFs, options, otelCarrier) => {
6262
+ await traces.waitInit();
6263
+ const context = otelCarrier ? traces.getContextFromCarrier(otelCarrier) : void 0;
6264
+ return traces.$("vitest.module.transform", context ? { context } : {}, (span) => fetcher.fetch(span, url, importer, environment, cacheFs, options));
5802
6265
  };
5803
6266
  }
5804
6267
  let SOURCEMAPPING_URL = "sourceMa";
@@ -5813,8 +6276,7 @@ function processResultSource(environment, result) {
5813
6276
  inlineSourceMap(node.transformResult);
5814
6277
  return {
5815
6278
  ...result,
5816
- code: node?.transformResult?.code || result.code,
5817
- transformResult: node?.transformResult
6279
+ code: node?.transformResult?.code || result.code
5818
6280
  };
5819
6281
  }
5820
6282
  const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,([A-Za-z0-9+/=]+)$`, "gm");
@@ -5850,6 +6312,20 @@ function getCachedResult(result, tmp) {
5850
6312
  invalidate: result.invalidate
5851
6313
  };
5852
6314
  }
6315
+ const MODULE_RUNNER_SOURCEMAPPING_REGEXP = /* @__PURE__ */ new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json;base64,(.+)`);
6316
+ function extractSourceMap(code) {
6317
+ const pattern = `//# ${SOURCEMAPPING_URL}=data:application/json;base64,`;
6318
+ const lastIndex = code.lastIndexOf(pattern);
6319
+ if (lastIndex === -1) return null;
6320
+ const mapString = MODULE_RUNNER_SOURCEMAPPING_REGEXP.exec(code.slice(lastIndex))?.[1];
6321
+ if (!mapString) return null;
6322
+ const sourceMap = JSON.parse(Buffer.from(mapString, "base64").toString("utf-8"));
6323
+ // remove source map mapping added by "inlineSourceMap" to keep the original behaviour of transformRequest
6324
+ if (sourceMap.mappings.startsWith("AAAA,CAAA;"))
6325
+ // 9 because we want to only remove "AAAA,CAAA", but keep ; at the start
6326
+ sourceMap.mappings = sourceMap.mappings.slice(9);
6327
+ return sourceMap;
6328
+ }
5853
6329
  // serialize rollup error on server to preserve details as a test error
5854
6330
  function handleRollupError(e) {
5855
6331
  if (e instanceof Error && ("plugin" in e || "frame" in e || "id" in e))
@@ -5868,31 +6344,6 @@ function handleRollupError(e) {
5868
6344
  };
5869
6345
  throw e;
5870
6346
  }
5871
- /**
5872
- * Performs an atomic write operation using the write-then-rename pattern.
5873
- *
5874
- * Why we need this:
5875
- * - Ensures file integrity by never leaving partially written files on disk
5876
- * - Prevents other processes from reading incomplete data during writes
5877
- * - Particularly important for test files where incomplete writes could cause test failures
5878
- *
5879
- * The implementation writes to a temporary file first, then renames it to the target path.
5880
- * This rename operation is atomic on most filesystems (including POSIX-compliant ones),
5881
- * guaranteeing that other processes will only ever see the complete file.
5882
- *
5883
- * Added in https://github.com/vitest-dev/vitest/pull/7531
5884
- */
5885
- async function atomicWriteFile(realFilePath, data) {
5886
- const tmpFilePath = join(dirname(realFilePath), `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
5887
- try {
5888
- await writeFile(tmpFilePath, data, "utf-8");
5889
- await rename(tmpFilePath, realFilePath);
5890
- } finally {
5891
- try {
5892
- if (await stat(tmpFilePath)) await unlink(tmpFilePath);
5893
- } catch {}
5894
- }
5895
- }
5896
6347
 
5897
6348
  // this is copy pasted from vite
5898
6349
  function normalizeResolvedIdToUrl(environment, resolvedId) {
@@ -5926,7 +6377,15 @@ class ServerModuleRunner extends ModuleRunner {
5926
6377
  if (name === "getBuiltins") return await environment.hot.handleInvoke(event);
5927
6378
  if (name !== "fetchModule") return { error: /* @__PURE__ */ new Error(`Unknown method: ${name}. Expected "fetchModule".`) };
5928
6379
  try {
5929
- return { result: await fetcher(data[0], data[1], environment, false, data[2]) };
6380
+ const result = await fetcher(data[0], data[1], environment, false, data[2]);
6381
+ if ("tmp" in result) {
6382
+ const code = await readFile(result.tmp);
6383
+ return { result: {
6384
+ ...result,
6385
+ code
6386
+ } };
6387
+ }
6388
+ return { result };
5930
6389
  } catch (error) {
5931
6390
  return { error };
5932
6391
  }
@@ -6214,7 +6673,7 @@ class VitestPackageInstaller {
6214
6673
  if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname$1] })) return true;
6215
6674
  process.stderr.write(c.red(`${c.inverse(c.red(" MISSING DEPENDENCY "))} Cannot find dependency '${dependency}'\n\n`));
6216
6675
  if (!isTTY) return false;
6217
- const { install } = await (await import('./index.z7NPOg2E.js').then(function (n) { return n.i; })).default({
6676
+ const { install } = await (await import('./index.D4KonVSU.js').then(function (n) { return n.i; })).default({
6218
6677
  type: "confirm",
6219
6678
  name: "install",
6220
6679
  message: c.reset(`Do you want to install ${c.green(dependency)}?`)
@@ -6283,7 +6742,7 @@ async function getSpecificationsEnvironments(specifications) {
6283
6742
  // reuse if projects have the same test files
6284
6743
  let code = cache.get(filepath);
6285
6744
  if (!code) {
6286
- code = await promises$1.readFile(filepath, "utf-8");
6745
+ code = await promises.readFile(filepath, "utf-8");
6287
6746
  cache.set(filepath, code);
6288
6747
  }
6289
6748
  // 1. Check for control comments in the file
@@ -6514,7 +6973,7 @@ class BrowserPool {
6514
6973
  return;
6515
6974
  }
6516
6975
  debug?.("[%s] error during %s test run: %s", sessionId, file, error);
6517
- this.reject(error);
6976
+ this.reject(new Error(`Failed to run the test ${file.filepath}.`, { cause: error }));
6518
6977
  });
6519
6978
  }).catch((err) => this.reject(err));
6520
6979
  }
@@ -6541,9 +7000,9 @@ function shouldIgnoreDebugger(provider, browser) {
6541
7000
  return browser !== "chromium";
6542
7001
  }
6543
7002
 
6544
- function createMethodsRPC(project, options = {}) {
7003
+ function createMethodsRPC(project, methodsOptions = {}) {
6545
7004
  const vitest = project.vitest;
6546
- const cacheFs = options.cacheFs ?? false;
7005
+ const cacheFs = methodsOptions.cacheFs ?? false;
6547
7006
  project.vitest.state.metadata[project.name] ??= {
6548
7007
  externalized: {},
6549
7008
  duration: {},
@@ -6552,11 +7011,11 @@ function createMethodsRPC(project, options = {}) {
6552
7011
  if (project.config.dumpDir && !existsSync(project.config.dumpDir)) mkdirSync(project.config.dumpDir, { recursive: true });
6553
7012
  project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir;
6554
7013
  return {
6555
- async fetch(url, importer, environmentName, options) {
7014
+ async fetch(url, importer, environmentName, options, otelCarrier) {
6556
7015
  const environment = project.vite.environments[environmentName];
6557
7016
  if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
6558
7017
  const start = performance.now();
6559
- return await project._fetcher(url, importer, environment, cacheFs, options).then((result) => {
7018
+ return await project._fetcher(url, importer, environment, cacheFs, options, otelCarrier).then((result) => {
6560
7019
  const duration = performance.now() - start;
6561
7020
  project.vitest.state.transformTime += duration;
6562
7021
  const metadata = project.vitest.state.metadata[project.name];
@@ -6597,25 +7056,25 @@ function createMethodsRPC(project, options = {}) {
6597
7056
  return { code: (await environment.transformRequest(url).catch(handleRollupError))?.code };
6598
7057
  },
6599
7058
  async onQueued(file) {
6600
- if (options.collect) vitest.state.collectFiles(project, [file]);
7059
+ if (methodsOptions.collect) vitest.state.collectFiles(project, [file]);
6601
7060
  else await vitest._testRun.enqueued(project, file);
6602
7061
  },
6603
7062
  async onCollected(files) {
6604
- if (options.collect) vitest.state.collectFiles(project, files);
7063
+ if (methodsOptions.collect) vitest.state.collectFiles(project, files);
6605
7064
  else await vitest._testRun.collected(project, files);
6606
7065
  },
6607
7066
  onAfterSuiteRun(meta) {
6608
7067
  vitest.coverageProvider?.onAfterSuiteRun(meta);
6609
7068
  },
6610
- async onTaskAnnotate(testId, annotation) {
6611
- return vitest._testRun.annotate(testId, annotation);
7069
+ async onTaskArtifactRecord(testId, artifact) {
7070
+ return vitest._testRun.recordArtifact(testId, artifact);
6612
7071
  },
6613
7072
  async onTaskUpdate(packs, events) {
6614
- if (options.collect) vitest.state.updateTasks(packs);
7073
+ if (methodsOptions.collect) vitest.state.updateTasks(packs);
6615
7074
  else await vitest._testRun.updated(packs, events);
6616
7075
  },
6617
7076
  async onUserConsoleLog(log) {
6618
- if (options.collect) vitest.state.updateUserLog(log);
7077
+ if (methodsOptions.collect) vitest.state.updateUserLog(log);
6619
7078
  else await vitest._testRun.log(log);
6620
7079
  },
6621
7080
  onUnhandledError(err, type) {
@@ -6648,11 +7107,18 @@ class PoolRunner {
6648
7107
  environment;
6649
7108
  _state = RunnerState.IDLE;
6650
7109
  _operationLock = null;
7110
+ _terminatePromise = createDefer();
6651
7111
  _eventEmitter = new EventEmitter();
7112
+ _offCancel;
6652
7113
  _rpc;
7114
+ _otel = null;
7115
+ _traces;
6653
7116
  get isTerminated() {
6654
7117
  return this._state === RunnerState.STOPPED;
6655
7118
  }
7119
+ waitForTerminated() {
7120
+ return this._terminatePromise;
7121
+ }
6656
7122
  get isStarted() {
6657
7123
  return this._state === RunnerState.STARTED;
6658
7124
  }
@@ -6660,22 +7126,65 @@ class PoolRunner {
6660
7126
  this.worker = worker;
6661
7127
  this.project = options.project;
6662
7128
  this.environment = options.environment;
7129
+ const vitest = this.project.vitest;
7130
+ this._traces = vitest._traces;
7131
+ if (this._traces.isEnabled()) {
7132
+ const { span: workerSpan, context } = this._traces.startContextSpan("vitest.worker");
7133
+ this._otel = {
7134
+ span: workerSpan,
7135
+ workerContext: context,
7136
+ files: []
7137
+ };
7138
+ this._otel.span.setAttributes({
7139
+ "vitest.worker.name": this.worker.name,
7140
+ "vitest.project": this.project.name,
7141
+ "vitest.environment": this.environment.name
7142
+ });
7143
+ }
6663
7144
  this._rpc = createBirpc(createMethodsRPC(this.project, {
6664
7145
  collect: options.method === "collect",
6665
7146
  cacheFs: worker.cacheFs
6666
7147
  }), {
6667
7148
  eventNames: ["onCancel"],
6668
- post: (request) => this.postMessage(request),
7149
+ post: (request) => {
7150
+ if (this._state !== RunnerState.STOPPING && this._state !== RunnerState.STOPPED) this.postMessage(request);
7151
+ },
6669
7152
  on: (callback) => this._eventEmitter.on("rpc", callback),
6670
7153
  timeout: -1
6671
7154
  });
6672
- this.project.vitest.onCancel((reason) => this._rpc.onCancel(reason));
7155
+ this._offCancel = vitest.onCancel((reason) => this._rpc.onCancel(reason));
6673
7156
  }
6674
7157
  postMessage(message) {
6675
7158
  // Only send messages when runner is active (not fully stopped)
6676
7159
  // Allow sending during STOPPING state for the 'stop' message itself
6677
7160
  if (this._state !== RunnerState.STOPPED) return this.worker.send(message);
6678
7161
  }
7162
+ startTracesSpan(name) {
7163
+ const traces = this._traces;
7164
+ if (!this._otel) return traces.startSpan(name);
7165
+ const { span, context } = traces.startContextSpan(name, this._otel.workerContext);
7166
+ this._otel.currentContext = context;
7167
+ const end = span.end.bind(span);
7168
+ span.end = (endTime) => {
7169
+ this._otel.currentContext = void 0;
7170
+ return end(endTime);
7171
+ };
7172
+ return span;
7173
+ }
7174
+ request(method, context) {
7175
+ this._otel?.files.push(...context.files.map((f) => f.filepath));
7176
+ return this.postMessage({
7177
+ __vitest_worker_request__: true,
7178
+ type: method,
7179
+ context,
7180
+ poolId: this.poolId,
7181
+ otelCarrier: this.getOTELCarrier()
7182
+ });
7183
+ }
7184
+ getOTELCarrier() {
7185
+ const activeContext = this._otel?.currentContext || this._otel?.workerContext;
7186
+ return activeContext ? this._traces.getContextCarrier(activeContext) : void 0;
7187
+ }
6679
7188
  async start() {
6680
7189
  // Wait for any ongoing operation to complete
6681
7190
  if (this._operationLock) await this._operationLock;
@@ -6683,15 +7192,21 @@ class PoolRunner {
6683
7192
  if (this._state === RunnerState.STOPPED) throw new Error("[vitest-pool-runner]: Cannot start a stopped runner");
6684
7193
  // Create operation lock to prevent concurrent start/stop
6685
7194
  this._operationLock = createDefer();
7195
+ let startSpan;
6686
7196
  try {
6687
7197
  this._state = RunnerState.STARTING;
6688
- await this.worker.start();
7198
+ await this._traces.$(`vitest.${this.worker.name}.start`, { context: this._otel?.workerContext }, () => this.worker.start());
6689
7199
  // Attach event listeners AFTER starting worker to avoid issues
6690
7200
  // if worker.start() fails
6691
7201
  this.worker.on("error", this.emitWorkerError);
6692
7202
  this.worker.on("exit", this.emitUnexpectedExit);
6693
7203
  this.worker.on("message", this.emitWorkerMessage);
7204
+ startSpan = this.startTracesSpan("vitest.worker.start");
6694
7205
  const startPromise = this.withTimeout(this.waitForStart(), START_TIMEOUT);
7206
+ const globalConfig = this.project.vitest.config.experimental.openTelemetry;
7207
+ const projectConfig = this.project.config.experimental.openTelemetry;
7208
+ const tracesEnabled = projectConfig?.enabled ?? globalConfig?.enabled === true;
7209
+ const tracesSdk = projectConfig?.sdkPath ?? globalConfig?.sdkPath;
6695
7210
  this.postMessage({
6696
7211
  type: "start",
6697
7212
  __vitest_worker_request__: true,
@@ -6703,14 +7218,21 @@ class PoolRunner {
6703
7218
  },
6704
7219
  config: this.project.serializedConfig,
6705
7220
  pool: this.worker.name
7221
+ },
7222
+ traces: {
7223
+ enabled: tracesEnabled,
7224
+ sdkPath: tracesSdk,
7225
+ otelCarrier: this.getOTELCarrier()
6706
7226
  }
6707
7227
  });
6708
7228
  await startPromise;
6709
7229
  this._state = RunnerState.STARTED;
6710
7230
  } catch (error) {
6711
7231
  this._state = RunnerState.IDLE;
7232
+ startSpan?.recordException(error);
6712
7233
  throw error;
6713
7234
  } finally {
7235
+ startSpan?.end();
6714
7236
  this._operationLock.resolve();
6715
7237
  this._operationLock = null;
6716
7238
  }
@@ -6719,7 +7241,9 @@ class PoolRunner {
6719
7241
  // Wait for any ongoing operation to complete
6720
7242
  if (this._operationLock) await this._operationLock;
6721
7243
  if (this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING) return;
7244
+ this._otel?.span.setAttribute("vitest.worker.files", this._otel.files);
6722
7245
  if (this._state === RunnerState.IDLE) {
7246
+ this._otel?.span.end();
6723
7247
  this._state = RunnerState.STOPPED;
6724
7248
  return;
6725
7249
  }
@@ -6729,10 +7253,14 @@ class PoolRunner {
6729
7253
  this._state = RunnerState.STOPPING;
6730
7254
  // Remove exit listener early to avoid "unexpected exit" errors during shutdown
6731
7255
  this.worker.off("exit", this.emitUnexpectedExit);
7256
+ const stopSpan = this.startTracesSpan("vitest.worker.stop");
6732
7257
  await this.withTimeout(new Promise((resolve) => {
6733
7258
  const onStop = (response) => {
6734
7259
  if (response.type === "stopped") {
6735
- if (response.error) this.project.vitest.state.catchError(response.error, "Teardown Error");
7260
+ if (response.error) {
7261
+ stopSpan.recordException(response.error);
7262
+ this.project.vitest.state.catchError(response.error, "Teardown Error");
7263
+ }
6736
7264
  resolve();
6737
7265
  this.off("message", onStop);
6738
7266
  }
@@ -6740,14 +7268,18 @@ class PoolRunner {
6740
7268
  this.on("message", onStop);
6741
7269
  this.postMessage({
6742
7270
  type: "stop",
6743
- __vitest_worker_request__: true
7271
+ __vitest_worker_request__: true,
7272
+ otelCarrier: this.getOTELCarrier()
6744
7273
  });
6745
- }), STOP_TIMEOUT);
7274
+ }), STOP_TIMEOUT).finally(() => {
7275
+ stopSpan.end();
7276
+ });
6746
7277
  this._eventEmitter.removeAllListeners();
7278
+ this._offCancel();
6747
7279
  this._rpc.$close(/* @__PURE__ */ new Error("[vitest-pool-runner]: Pending methods while closing rpc"));
6748
7280
  // Stop the worker process (this sets _fork/_thread to undefined)
6749
7281
  // Worker's event listeners (error, message) are implicitly removed when worker terminates
6750
- await this.worker.stop();
7282
+ await this._traces.$(`vitest.${this.worker.name}.stop`, { context: this._otel?.workerContext }, () => this.worker.stop());
6751
7283
  this._state = RunnerState.STOPPED;
6752
7284
  } catch (error) {
6753
7285
  // Ensure we transition to stopped state even on error
@@ -6756,6 +7288,8 @@ class PoolRunner {
6756
7288
  } finally {
6757
7289
  this._operationLock.resolve();
6758
7290
  this._operationLock = null;
7291
+ this._otel?.span.end();
7292
+ this._terminatePromise.resolve();
6759
7293
  }
6760
7294
  }
6761
7295
  on(event, callback) {
@@ -6835,14 +7369,14 @@ class ForksPoolWorker {
6835
7369
  this.fork.off(event, callback);
6836
7370
  }
6837
7371
  send(message) {
6838
- if ("context" in message && "config" in message.context) message.context.config = wrapSerializableConfig(message.context.config);
6839
- this.fork.send(v8.serialize(message));
7372
+ this.fork.send(message);
6840
7373
  }
6841
7374
  async start() {
6842
7375
  this._fork ||= fork(this.entrypoint, [], {
6843
7376
  env: this.env,
6844
7377
  execArgv: this.execArgv,
6845
- stdio: "pipe"
7378
+ stdio: "pipe",
7379
+ serialization: "advanced"
6846
7380
  });
6847
7381
  if (this._fork.stdout) {
6848
7382
  this.stdout.setMaxListeners(1 + this.stdout.getMaxListeners());
@@ -6880,41 +7414,13 @@ class ForksPoolWorker {
6880
7414
  this._fork = void 0;
6881
7415
  }
6882
7416
  deserialize(data) {
6883
- try {
6884
- return v8.deserialize(Buffer.from(data));
6885
- } catch (error) {
6886
- let stringified = "";
6887
- try {
6888
- stringified = `\nReceived value: ${JSON.stringify(data)}`;
6889
- } catch {}
6890
- throw new Error(`[vitest-pool]: Unexpected call to process.send(). Make sure your test cases are not interfering with process's channel.${stringified}`, { cause: error });
6891
- }
7417
+ return data;
6892
7418
  }
6893
7419
  get fork() {
6894
7420
  if (!this._fork) throw new Error(`The child process was torn down or never initialized. This is a bug in Vitest.`);
6895
7421
  return this._fork;
6896
7422
  }
6897
7423
  }
6898
- /**
6899
- * Prepares `SerializedConfig` for serialization, e.g. `node:v8.serialize`
6900
- * - Unwrapping done in {@link file://./../../../runtime/workers/init-forks.ts}
6901
- */
6902
- function wrapSerializableConfig(config) {
6903
- let testNamePattern = config.testNamePattern;
6904
- let defines = config.defines;
6905
- // v8 serialize does not support regex
6906
- if (testNamePattern && typeof testNamePattern !== "string") testNamePattern = `$$vitest:${testNamePattern.toString()}`;
6907
- // v8 serialize drops properties with undefined value
6908
- if (defines) defines = {
6909
- keys: Object.keys(defines),
6910
- original: defines
6911
- };
6912
- return {
6913
- ...config,
6914
- testNamePattern,
6915
- defines
6916
- };
6917
- }
6918
7424
 
6919
7425
  /** @experimental */
6920
7426
  class ThreadsPoolWorker {
@@ -7215,8 +7721,9 @@ class Pool {
7215
7721
  cancelTask
7216
7722
  };
7217
7723
  this.activeTasks.push(activeTask);
7724
+ // active tasks receive cancel signal and shut down gracefully
7218
7725
  async function cancelTask() {
7219
- await runner.stop();
7726
+ await runner.waitForTerminated();
7220
7727
  resolver.reject(/* @__PURE__ */ new Error("Cancelled"));
7221
7728
  }
7222
7729
  const onFinished = (message) => {
@@ -7237,14 +7744,15 @@ class Pool {
7237
7744
  }
7238
7745
  const poolId = runner.poolId ?? this.getWorkerId();
7239
7746
  runner.poolId = poolId;
7747
+ const span = runner.startTracesSpan(`vitest.worker.${method}`);
7240
7748
  // Start running the test in the worker
7241
- runner.postMessage({
7242
- __vitest_worker_request__: true,
7243
- type: method,
7244
- context: task.context,
7245
- poolId
7749
+ runner.request(method, task.context);
7750
+ await resolver.promise.catch((error) => {
7751
+ span.recordException(error);
7752
+ throw error;
7753
+ }).finally(() => {
7754
+ span.end();
7246
7755
  });
7247
- await resolver.promise;
7248
7756
  const index = this.activeTasks.indexOf(activeTask);
7249
7757
  if (index !== -1) this.activeTasks.splice(index, 1);
7250
7758
  if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
@@ -7680,7 +8188,8 @@ function serializeConfig(project) {
7680
8188
  standalone: config.standalone,
7681
8189
  printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
7682
8190
  benchmark: config.benchmark && { includeSamples: config.benchmark.includeSamples },
7683
- serializedDefines: config.browser.enabled ? "" : project._serializedDefines || ""
8191
+ serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
8192
+ experimental: { fsModuleCache: config.experimental.fsModuleCache ?? false }
7684
8193
  };
7685
8194
  }
7686
8195
 
@@ -8731,13 +9240,19 @@ function WorkspaceVitestPlugin(project, options) {
8731
9240
  return project.vitest.matchesProjectFilter(name);
8732
9241
  })) throw new VitestFilteredOutProjectError();
8733
9242
  }
9243
+ const vitestConfig = { name: {
9244
+ label: name,
9245
+ color
9246
+ } };
9247
+ // always inherit the global `fsModuleCache` value even without `extends: true`
9248
+ if (testConfig.experimental?.fsModuleCache == null && project.vitest.config.experimental?.fsModuleCache !== null) {
9249
+ vitestConfig.experimental ??= {};
9250
+ vitestConfig.experimental.fsModuleCache = project.vitest.config.experimental.fsModuleCache;
9251
+ }
8734
9252
  return {
8735
9253
  base: "/",
8736
9254
  environments: { __vitest__: { dev: {} } },
8737
- test: { name: {
8738
- label: name,
8739
- color
8740
- } }
9255
+ test: vitestConfig
8741
9256
  };
8742
9257
  }
8743
9258
  },
@@ -8822,16 +9337,21 @@ class VitestResolver {
8822
9337
  options;
8823
9338
  externalizeCache = /* @__PURE__ */ new Map();
8824
9339
  constructor(cacheDir, config) {
9340
+ // sorting to make cache consistent
9341
+ const inline = config.server.deps?.inline;
9342
+ if (Array.isArray(inline)) inline.sort();
9343
+ const external = config.server.deps?.external;
9344
+ if (Array.isArray(external)) external.sort();
8825
9345
  this.options = {
8826
- moduleDirectories: config.deps.moduleDirectories,
9346
+ moduleDirectories: config.deps.moduleDirectories?.sort(),
8827
9347
  inlineFiles: config.setupFiles.flatMap((file) => {
8828
9348
  if (file.startsWith("file://")) return file;
8829
9349
  const resolvedId = resolve(file);
8830
9350
  return [resolvedId, pathToFileURL(resolvedId).href];
8831
9351
  }),
8832
9352
  cacheDir,
8833
- inline: config.server.deps?.inline,
8834
- external: config.server.deps?.external
9353
+ inline,
9354
+ external
8835
9355
  };
8836
9356
  }
8837
9357
  shouldExternalize(file) {
@@ -8888,7 +9408,7 @@ async function isValidNodeImport(id) {
8888
9408
  if (/\.(?:\w+-)?esm?(?:-\w+)?\.js$|\/esm?\//.test(id)) return false;
8889
9409
  try {
8890
9410
  await esModuleLexer.init;
8891
- const code = await promises$1.readFile(id, "utf8");
9411
+ const code = await promises.readFile(id, "utf8");
8892
9412
  const [, , , hasModuleSyntax] = esModuleLexer.parse(code);
8893
9413
  return !hasModuleSyntax;
8894
9414
  } catch {
@@ -9154,15 +9674,24 @@ class TestProject {
9154
9674
  * @param filters String filters to match the test files.
9155
9675
  */
9156
9676
  async globTestFiles(filters = []) {
9157
- const dir = this.config.dir || this.config.root;
9158
- const { include, exclude, includeSource } = this.config;
9159
- const typecheck = this.config.typecheck;
9160
- const [testFiles, typecheckTestFiles] = await Promise.all([typecheck.enabled && typecheck.only ? [] : this.globAllTestFiles(include, exclude, includeSource, dir), typecheck.enabled ? this.typecheckFilesList || this.globFiles(typecheck.include, typecheck.exclude, dir) : []]);
9161
- this.typecheckFilesList = typecheckTestFiles;
9162
- return {
9163
- testFiles: this.filterFiles(testFiles, filters, dir),
9164
- typecheckTestFiles: this.filterFiles(typecheckTestFiles, filters, dir)
9165
- };
9677
+ return this.vitest._traces.$("vitest.config.resolve_include_project", async (span) => {
9678
+ const dir = this.config.dir || this.config.root;
9679
+ const { include, exclude, includeSource } = this.config;
9680
+ const typecheck = this.config.typecheck;
9681
+ span.setAttributes({
9682
+ cwd: dir,
9683
+ include,
9684
+ exclude,
9685
+ includeSource,
9686
+ typecheck: typecheck.enabled ? typecheck.include : []
9687
+ });
9688
+ const [testFiles, typecheckTestFiles] = await Promise.all([typecheck.enabled && typecheck.only ? [] : this.globAllTestFiles(include, exclude, includeSource, dir), typecheck.enabled ? this.typecheckFilesList || this.globFiles(typecheck.include, typecheck.exclude, dir) : []]);
9689
+ this.typecheckFilesList = typecheckTestFiles;
9690
+ return {
9691
+ testFiles: this.filterFiles(testFiles, filters, dir),
9692
+ typecheckTestFiles: this.filterFiles(typecheckTestFiles, filters, dir)
9693
+ };
9694
+ });
9166
9695
  }
9167
9696
  async globAllTestFiles(include, exclude, includeSource, cwd) {
9168
9697
  if (this.testFilesList) return this.testFilesList;
@@ -9171,7 +9700,7 @@ class TestProject {
9171
9700
  const files = await this.globFiles(includeSource, exclude, cwd);
9172
9701
  await Promise.all(files.map(async (file) => {
9173
9702
  try {
9174
- const code = await promises$1.readFile(file, "utf-8");
9703
+ const code = await promises.readFile(file, "utf-8");
9175
9704
  if (this.isInSourceTestCode(code)) testFiles.push(file);
9176
9705
  } catch {
9177
9706
  return null;
@@ -9323,10 +9852,7 @@ class TestProject {
9323
9852
  this._resolver = new VitestResolver(server.config.cacheDir, this._config);
9324
9853
  this._vite = server;
9325
9854
  this._serializedDefines = createDefinesScript(server.config.define);
9326
- this._fetcher = createFetchModuleFunction(this._resolver, this.tmpDir, {
9327
- dumpFolder: this.config.dumpDir,
9328
- readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
9329
- });
9855
+ this._fetcher = createFetchModuleFunction(this._resolver, this._config, this.vitest._fsCache, this.vitest._traces, this.tmpDir);
9330
9856
  const environment = server.environments.__vitest__;
9331
9857
  this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
9332
9858
  }
@@ -9394,7 +9920,7 @@ function deduped(cb) {
9394
9920
  }
9395
9921
  async function initializeProject(workspacePath, ctx, options) {
9396
9922
  const project = new TestProject(ctx, options);
9397
- const { configFile,...restOptions } = options;
9923
+ const { configFile, ...restOptions } = options;
9398
9924
  await createViteServer({
9399
9925
  ...restOptions,
9400
9926
  configFile,
@@ -9567,8 +10093,8 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
9567
10093
  });
9568
10094
  return resolvedProjects.filter((project) => !removeProjects.has(project));
9569
10095
  }
9570
- function cloneConfig(project, { browser,...config }) {
9571
- const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name, provider,...overrideConfig } = config;
10096
+ function cloneConfig(project, { browser, ...config }) {
10097
+ const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name, provider, ...overrideConfig } = config;
9572
10098
  const currentConfig = project.config.browser;
9573
10099
  const clonedConfig = deepClone(project.config);
9574
10100
  return mergeConfig({
@@ -9986,6 +10512,14 @@ class TestCase extends ReportedTaskImplementation {
9986
10512
  return [...this.task.annotations];
9987
10513
  }
9988
10514
  /**
10515
+ * @experimental
10516
+ *
10517
+ * Test artifacts recorded via the `recordArtifact` API during the test execution.
10518
+ */
10519
+ artifacts() {
10520
+ return [...this.task.artifacts];
10521
+ }
10522
+ /**
9989
10523
  * Useful information about the test like duration, memory usage, etc.
9990
10524
  * Diagnostic is only available after the test has finished.
9991
10525
  */
@@ -10840,15 +11374,22 @@ class TestRun {
10840
11374
  this.vitest.state.updateUserLog(log);
10841
11375
  await this.vitest.report("onUserConsoleLog", log);
10842
11376
  }
10843
- async annotate(testId, annotation) {
11377
+ async recordArtifact(testId, artifact) {
10844
11378
  const task = this.vitest.state.idMap.get(testId);
10845
11379
  const entity = task && this.vitest.state.getReportedEntity(task);
10846
11380
  assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
10847
- assert$1(entity.type === "test", `Annotation can only be added to a test, instead got ${entity.type}`);
10848
- await this.resolveTestAttachment(entity, annotation);
10849
- entity.task.annotations.push(annotation);
10850
- await this.vitest.report("onTestCaseAnnotate", entity, annotation);
10851
- return annotation;
11381
+ assert$1(entity.type === "test", `Artifacts can only be recorded on a test, instead got ${entity.type}`);
11382
+ // annotations won't resolve as artifacts for backwards compatibility until next major
11383
+ if (artifact.type === "internal:annotation") {
11384
+ await this.resolveTestAttachment(entity, artifact.annotation.attachment, artifact.annotation.message);
11385
+ entity.task.annotations.push(artifact.annotation);
11386
+ await this.vitest.report("onTestCaseAnnotate", entity, artifact.annotation);
11387
+ return artifact;
11388
+ }
11389
+ if (Array.isArray(artifact.attachments)) await Promise.all(artifact.attachments.map((attachment) => this.resolveTestAttachment(entity, attachment)));
11390
+ entity.task.artifacts.push(artifact);
11391
+ await this.vitest.report("onTestCaseArtifactRecord", entity, artifact);
11392
+ return artifact;
10852
11393
  }
10853
11394
  async updated(update, events) {
10854
11395
  this.syncUpdateStacks(update);
@@ -10935,15 +11476,14 @@ class TestRun {
10935
11476
  }
10936
11477
  }
10937
11478
  }
10938
- async resolveTestAttachment(test, annotation) {
11479
+ async resolveTestAttachment(test, attachment, filename) {
10939
11480
  const project = test.project;
10940
- const attachment = annotation.attachment;
10941
11481
  if (!attachment) return attachment;
10942
11482
  const path = attachment.path;
10943
11483
  if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
10944
11484
  const currentPath = resolve(project.config.root, path);
10945
11485
  const hash = createHash("sha1").update(currentPath).digest("hex");
10946
- const newPath = resolve(project.config.attachmentsDir, `${sanitizeFilePath(annotation.message)}-${hash}${extname(currentPath)}`);
11486
+ const newPath = resolve(project.config.attachmentsDir, `${filename ? `${sanitizeFilePath(filename)}-` : ""}${hash}${extname(currentPath)}`);
10947
11487
  if (!existsSync(project.config.attachmentsDir)) await mkdir(project.config.attachmentsDir, { recursive: true });
10948
11488
  await copyFile(currentPath, newPath);
10949
11489
  attachment.path = newPath;
@@ -11166,6 +11706,7 @@ class Vitest {
11166
11706
  /** @internal */ filenamePattern;
11167
11707
  /** @internal */ runningPromise;
11168
11708
  /** @internal */ closingPromise;
11709
+ /** @internal */ cancelPromise;
11169
11710
  /** @internal */ isCancelling = false;
11170
11711
  /** @internal */ coreWorkspaceProject;
11171
11712
  /** @internal */ _browserSessions = new BrowserSessions();
@@ -11175,7 +11716,9 @@ class Vitest {
11175
11716
  /** @internal */ _testRun = void 0;
11176
11717
  /** @internal */ _resolver;
11177
11718
  /** @internal */ _fetcher;
11719
+ /** @internal */ _fsCache;
11178
11720
  /** @internal */ _tmpDir = join(tmpdir(), nanoid());
11721
+ /** @internal */ _traces;
11179
11722
  isFirstRun = true;
11180
11723
  restartsCount = 0;
11181
11724
  specifications;
@@ -11197,7 +11740,7 @@ class Vitest {
11197
11740
  _onRestartListeners = [];
11198
11741
  _onClose = [];
11199
11742
  _onSetServer = [];
11200
- _onCancelListeners = [];
11743
+ _onCancelListeners = /* @__PURE__ */ new Set();
11201
11744
  _onUserTestsRerun = [];
11202
11745
  _onFilterWatchedSpecification = [];
11203
11746
  /**
@@ -11254,15 +11797,19 @@ class Vitest {
11254
11797
  const resolved = resolveConfig(this, options, server.config);
11255
11798
  this._config = resolved;
11256
11799
  this._state = new StateManager({ onUnhandledError: resolved.onUnhandledError });
11257
- this._cache = new VitestCache(this.version);
11800
+ this._cache = new VitestCache(this.logger);
11258
11801
  this._snapshot = new SnapshotManager({ ...resolved.snapshotOptions });
11259
11802
  this._testRun = new TestRun(this);
11803
+ const otelSdkPath = resolved.experimental.openTelemetry?.sdkPath;
11804
+ this._traces = new Traces({
11805
+ enabled: !!resolved.experimental.openTelemetry?.enabled,
11806
+ sdkPath: otelSdkPath,
11807
+ watchMode: resolved.watch
11808
+ });
11260
11809
  if (this.config.watch) this.watcher.registerWatcher();
11261
11810
  this._resolver = new VitestResolver(server.config.cacheDir, resolved);
11262
- this._fetcher = createFetchModuleFunction(this._resolver, this._tmpDir, {
11263
- dumpFolder: this.config.dumpDir,
11264
- readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
11265
- });
11811
+ this._fsCache = new FileSystemModuleCache(this);
11812
+ this._fetcher = createFetchModuleFunction(this._resolver, this._config, this._fsCache, this._traces, this._tmpDir);
11266
11813
  const environment = server.environments.__vitest__;
11267
11814
  this.runner = new ServerModuleRunner(environment, this._fetcher, resolved);
11268
11815
  if (this.config.watch) {
@@ -11295,7 +11842,8 @@ class Vitest {
11295
11842
  return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
11296
11843
  project,
11297
11844
  vitest: this,
11298
- injectTestProjects: this.injectTestProject
11845
+ injectTestProjects: this.injectTestProject,
11846
+ experimental_defineCacheKeyGenerator: (callback) => this._fsCache.defineCacheKeyGenerator(callback)
11299
11847
  }));
11300
11848
  }));
11301
11849
  if (this._cliOptions.browser?.enabled) {
@@ -11313,7 +11861,8 @@ class Vitest {
11313
11861
  if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
11314
11862
  if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
11315
11863
  this.reporters = resolved.mode === "benchmark" ? await createBenchmarkReporters(toArray(resolved.benchmark?.reporters), this.runner) : await createReporters(resolved.reporters, this);
11316
- await Promise.all(this._onSetServer.map((fn) => fn()));
11864
+ await this._fsCache.ensureCacheIntegrity();
11865
+ await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
11317
11866
  }
11318
11867
  /** @internal */
11319
11868
  get coverageProvider() {
@@ -11325,10 +11874,19 @@ class Vitest {
11325
11874
  this.configOverride.coverage.enabled = true;
11326
11875
  await this.createCoverageProvider();
11327
11876
  await this.coverageProvider?.onEnabled?.();
11877
+ // onFileTransform is the only thing that affects hash
11878
+ if (this.coverageProvider?.onFileTransform) this.clearAllCachePaths();
11328
11879
  }
11329
11880
  disableCoverage() {
11330
11881
  this.configOverride.coverage ??= {};
11331
11882
  this.configOverride.coverage.enabled = false;
11883
+ // onFileTransform is the only thing that affects hash
11884
+ if (this.coverageProvider?.onFileTransform) this.clearAllCachePaths();
11885
+ }
11886
+ clearAllCachePaths() {
11887
+ this.projects.forEach(({ vite, browser }) => {
11888
+ [...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach((environment) => this._fsCache.invalidateAllCachePaths(environment));
11889
+ });
11332
11890
  }
11333
11891
  _coverageOverrideCache = /* @__PURE__ */ new WeakMap();
11334
11892
  /** @internal */
@@ -11424,33 +11982,43 @@ class Vitest {
11424
11982
  return this._coverageProvider;
11425
11983
  }
11426
11984
  /**
11985
+ * Deletes all Vitest caches, including `experimental.fsModuleCache`.
11986
+ * @experimental
11987
+ */
11988
+ async experimental_clearCache() {
11989
+ await this.cache.results.clearCache();
11990
+ await this._fsCache.clearCache();
11991
+ }
11992
+ /**
11427
11993
  * Merge reports from multiple runs located in the specified directory (value from `--merge-reports` if not specified).
11428
11994
  */
11429
11995
  async mergeReports(directory) {
11430
- if (this.reporters.some((r) => r instanceof BlobReporter)) throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
11431
- const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
11432
- this.state.blobs = {
11433
- files,
11434
- errors,
11435
- coverages,
11436
- executionTimes
11437
- };
11438
- await this.report("onInit", this);
11439
- const specifications = [];
11440
- for (const file of files) {
11441
- const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool);
11442
- specifications.push(specification);
11443
- }
11444
- await this._testRun.start(specifications).catch(noop);
11445
- for (const file of files) await this._reportFileTask(file);
11446
- this._checkUnhandledErrors(errors);
11447
- await this._testRun.end(specifications, errors).catch(noop);
11448
- await this.initCoverageProvider();
11449
- await this.coverageProvider?.mergeReports?.(coverages);
11450
- return {
11451
- testModules: this.state.getTestModules(),
11452
- unhandledErrors: this.state.getUnhandledErrors()
11453
- };
11996
+ return this._traces.$("vitest.merge_reports", async () => {
11997
+ if (this.reporters.some((r) => r instanceof BlobReporter)) throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
11998
+ const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
11999
+ this.state.blobs = {
12000
+ files,
12001
+ errors,
12002
+ coverages,
12003
+ executionTimes
12004
+ };
12005
+ await this.report("onInit", this);
12006
+ const specifications = [];
12007
+ for (const file of files) {
12008
+ const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool);
12009
+ specifications.push(specification);
12010
+ }
12011
+ await this._testRun.start(specifications).catch(noop);
12012
+ for (const file of files) await this._reportFileTask(file);
12013
+ this._checkUnhandledErrors(errors);
12014
+ await this._testRun.end(specifications, errors).catch(noop);
12015
+ await this.initCoverageProvider();
12016
+ await this.coverageProvider?.mergeReports?.(coverages);
12017
+ return {
12018
+ testModules: this.state.getTestModules(),
12019
+ unhandledErrors: this.state.getUnhandledErrors()
12020
+ };
12021
+ });
11454
12022
  }
11455
12023
  /**
11456
12024
  * Returns the seed, if tests are running in a random order.
@@ -11472,13 +12040,25 @@ class Vitest {
11472
12040
  await this._testRun.updated(packs, events).catch(noop);
11473
12041
  }
11474
12042
  async collect(filters) {
11475
- const files = await this.specifications.getRelevantTestSpecifications(filters);
11476
- // if run with --changed, don't exit if no tests are found
11477
- if (!files.length) return {
11478
- testModules: [],
11479
- unhandledErrors: []
11480
- };
11481
- return this.collectTests(files);
12043
+ return this._traces.$("vitest.collect", async (collectSpan) => {
12044
+ const filenamePattern = filters && filters?.length > 0 ? filters : [];
12045
+ collectSpan.setAttribute("vitest.collect.filters", filenamePattern);
12046
+ const files = await this._traces.$("vitest.config.resolve_include_glob", async () => {
12047
+ const specifications = await this.specifications.getRelevantTestSpecifications(filters);
12048
+ collectSpan.setAttribute("vitest.collect.specifications", specifications.map((s) => {
12049
+ const relativeModuleId = relative(s.project.config.root, s.moduleId);
12050
+ if (s.project.name) return `|${s.project.name}| ${relativeModuleId}`;
12051
+ return relativeModuleId;
12052
+ }));
12053
+ return specifications;
12054
+ });
12055
+ // if run with --changed, don't exit if no tests are found
12056
+ if (!files.length) return {
12057
+ testModules: [],
12058
+ unhandledErrors: []
12059
+ };
12060
+ return this.collectTests(files);
12061
+ });
11482
12062
  }
11483
12063
  /**
11484
12064
  * Returns the list of test files that match the config and filters.
@@ -11496,49 +12076,67 @@ class Vitest {
11496
12076
  * @param filters String filters to match the test files
11497
12077
  */
11498
12078
  async start(filters) {
11499
- try {
11500
- await this.initCoverageProvider();
11501
- await this.coverageProvider?.clean(this._coverageOptions.clean);
11502
- } finally {
11503
- await this.report("onInit", this);
11504
- }
11505
- this.filenamePattern = filters && filters?.length > 0 ? filters : void 0;
11506
- const files = await this.specifications.getRelevantTestSpecifications(filters);
11507
- // if run with --changed, don't exit if no tests are found
11508
- if (!files.length) {
11509
- await this._testRun.start([]);
11510
- const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
11511
- await this._testRun.end([], [], coverage);
11512
- // Report coverage for uncovered files
11513
- await this.reportCoverage(coverage, true);
11514
- if (!this.config.watch || !(this.config.changed || this.config.related?.length)) throw new FilesNotFoundError(this.mode);
11515
- }
11516
- let testModules = {
11517
- testModules: [],
11518
- unhandledErrors: []
11519
- };
11520
- if (files.length) {
11521
- // populate once, update cache on watch
11522
- await this.cache.stats.populateStats(this.config.root, files);
11523
- testModules = await this.runFiles(files, true);
11524
- }
11525
- if (this.config.watch) await this.report("onWatcherStart");
11526
- return testModules;
12079
+ return this._traces.$("vitest.start", async (startSpan) => {
12080
+ startSpan.setAttributes({ config: this.vite.config.configFile });
12081
+ try {
12082
+ await this._traces.$("vitest.coverage.init", async () => {
12083
+ await this.initCoverageProvider();
12084
+ await this.coverageProvider?.clean(this._coverageOptions.clean);
12085
+ });
12086
+ } finally {
12087
+ await this.report("onInit", this);
12088
+ }
12089
+ this.filenamePattern = filters && filters?.length > 0 ? filters : void 0;
12090
+ startSpan.setAttribute("vitest.start.filters", this.filenamePattern || []);
12091
+ const files = await this._traces.$("vitest.config.resolve_include_glob", async () => {
12092
+ const specifications = await this.specifications.getRelevantTestSpecifications(filters);
12093
+ startSpan.setAttribute("vitest.start.specifications", specifications.map((s) => {
12094
+ const relativeModuleId = relative(s.project.config.root, s.moduleId);
12095
+ if (s.project.name) return `|${s.project.name}| ${relativeModuleId}`;
12096
+ return relativeModuleId;
12097
+ }));
12098
+ return specifications;
12099
+ });
12100
+ // if run with --changed, don't exit if no tests are found
12101
+ if (!files.length) {
12102
+ await this._traces.$("vitest.test_run", async () => {
12103
+ await this._testRun.start([]);
12104
+ const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
12105
+ await this._testRun.end([], [], coverage);
12106
+ // Report coverage for uncovered files
12107
+ await this.reportCoverage(coverage, true);
12108
+ });
12109
+ if (!this.config.watch || !(this.config.changed || this.config.related?.length)) throw new FilesNotFoundError(this.mode);
12110
+ }
12111
+ let testModules = {
12112
+ testModules: [],
12113
+ unhandledErrors: []
12114
+ };
12115
+ if (files.length) {
12116
+ // populate once, update cache on watch
12117
+ await this.cache.stats.populateStats(this.config.root, files);
12118
+ testModules = await this.runFiles(files, true);
12119
+ }
12120
+ if (this.config.watch) await this.report("onWatcherStart");
12121
+ return testModules;
12122
+ });
11527
12123
  }
11528
12124
  /**
11529
12125
  * Initialize reporters and the coverage provider. This method doesn't run any tests.
11530
12126
  * If the `--watch` flag is provided, Vitest will still run changed tests even if this method was not called.
11531
12127
  */
11532
12128
  async init() {
11533
- try {
11534
- await this.initCoverageProvider();
11535
- await this.coverageProvider?.clean(this._coverageOptions.clean);
11536
- } finally {
11537
- await this.report("onInit", this);
11538
- }
11539
- // populate test files cache so watch mode can trigger a file rerun
11540
- await this.globTestSpecifications();
11541
- if (this.config.watch) await this.report("onWatcherStart");
12129
+ await this._traces.$("vitest.init", async () => {
12130
+ try {
12131
+ await this.initCoverageProvider();
12132
+ await this.coverageProvider?.clean(this._coverageOptions.clean);
12133
+ } finally {
12134
+ await this.report("onInit", this);
12135
+ }
12136
+ // populate test files cache so watch mode can trigger a file rerun
12137
+ await this.globTestSpecifications();
12138
+ if (this.config.watch) await this.report("onWatcherStart");
12139
+ });
11542
12140
  }
11543
12141
  /**
11544
12142
  * If there is a test run happening, returns a promise that will
@@ -11588,50 +12186,53 @@ class Vitest {
11588
12186
  return result;
11589
12187
  }
11590
12188
  async runFiles(specs, allTestsRun) {
11591
- await this._testRun.start(specs);
11592
- // previous run
11593
- await this.runningPromise;
11594
- this._onCancelListeners = [];
11595
- this.isCancelling = false;
11596
- // schedule the new run
11597
- this.runningPromise = (async () => {
11598
- try {
11599
- if (!this.pool) this.pool = createPool(this);
11600
- const invalidates = Array.from(this.watcher.invalidates);
11601
- this.watcher.invalidates.clear();
11602
- this.snapshot.clear();
11603
- this.state.clearErrors();
11604
- if (!this.isFirstRun && this._coverageOptions.cleanOnRerun) await this.coverageProvider?.clean();
11605
- await this.initializeGlobalSetup(specs);
12189
+ return this._traces.$("vitest.test_run", async () => {
12190
+ await this._testRun.start(specs);
12191
+ // previous run
12192
+ await this.cancelPromise;
12193
+ await this.runningPromise;
12194
+ this._onCancelListeners.clear();
12195
+ this.isCancelling = false;
12196
+ // schedule the new run
12197
+ this.runningPromise = (async () => {
11606
12198
  try {
11607
- await this.pool.runTests(specs, invalidates);
11608
- } catch (err) {
11609
- this.state.catchError(err, "Unhandled Error");
12199
+ if (!this.pool) this.pool = createPool(this);
12200
+ const invalidates = Array.from(this.watcher.invalidates);
12201
+ this.watcher.invalidates.clear();
12202
+ this.snapshot.clear();
12203
+ this.state.clearErrors();
12204
+ if (!this.isFirstRun && this._coverageOptions.cleanOnRerun) await this.coverageProvider?.clean();
12205
+ await this.initializeGlobalSetup(specs);
12206
+ try {
12207
+ await this.pool.runTests(specs, invalidates);
12208
+ } catch (err) {
12209
+ this.state.catchError(err, "Unhandled Error");
12210
+ }
12211
+ const files = this.state.getFiles();
12212
+ this.cache.results.updateResults(files);
12213
+ try {
12214
+ await this.cache.results.writeToCache();
12215
+ } catch {}
12216
+ return {
12217
+ testModules: this.state.getTestModules(),
12218
+ unhandledErrors: this.state.getUnhandledErrors()
12219
+ };
12220
+ } finally {
12221
+ const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
12222
+ const errors = this.state.getUnhandledErrors();
12223
+ this._checkUnhandledErrors(errors);
12224
+ await this._testRun.end(specs, errors, coverage);
12225
+ await this.reportCoverage(coverage, allTestsRun);
11610
12226
  }
11611
- const files = this.state.getFiles();
11612
- this.cache.results.updateResults(files);
11613
- try {
11614
- await this.cache.results.writeToCache();
11615
- } catch {}
11616
- return {
11617
- testModules: this.state.getTestModules(),
11618
- unhandledErrors: this.state.getUnhandledErrors()
11619
- };
11620
- } finally {
11621
- const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
11622
- const errors = this.state.getUnhandledErrors();
11623
- this._checkUnhandledErrors(errors);
11624
- await this._testRun.end(specs, errors, coverage);
11625
- await this.reportCoverage(coverage, allTestsRun);
11626
- }
11627
- })().finally(() => {
11628
- this.runningPromise = void 0;
11629
- this.isFirstRun = false;
11630
- // all subsequent runs will treat this as a fresh run
11631
- this.config.changed = false;
11632
- this.config.related = void 0;
12227
+ })().finally(() => {
12228
+ this.runningPromise = void 0;
12229
+ this.isFirstRun = false;
12230
+ // all subsequent runs will treat this as a fresh run
12231
+ this.config.changed = false;
12232
+ this.config.related = void 0;
12233
+ });
12234
+ return await this.runningPromise;
11633
12235
  });
11634
- return await this.runningPromise;
11635
12236
  }
11636
12237
  async experimental_parseSpecifications(specifications, options) {
11637
12238
  if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
@@ -11656,8 +12257,9 @@ class Vitest {
11656
12257
  const filepaths = specifications.map((spec) => spec.moduleId);
11657
12258
  this.state.collectPaths(filepaths);
11658
12259
  // previous run
12260
+ await this.cancelPromise;
11659
12261
  await this.runningPromise;
11660
- this._onCancelListeners = [];
12262
+ this._onCancelListeners.clear();
11661
12263
  this.isCancelling = false;
11662
12264
  // schedule the new run
11663
12265
  this.runningPromise = (async () => {
@@ -11692,7 +12294,8 @@ class Vitest {
11692
12294
  */
11693
12295
  async cancelCurrentRun(reason) {
11694
12296
  this.isCancelling = true;
11695
- await Promise.all(this._onCancelListeners.splice(0).map((listener) => listener(reason)));
12297
+ this.cancelPromise = Promise.all([...this._onCancelListeners].map((listener) => listener(reason)));
12298
+ await this.cancelPromise.finally(() => this.cancelPromise = void 0);
11696
12299
  await this.runningPromise;
11697
12300
  }
11698
12301
  /** @internal */
@@ -11817,6 +12420,7 @@ class Vitest {
11817
12420
  async scheduleRerun(triggerId) {
11818
12421
  const currentCount = this.restartsCount;
11819
12422
  clearTimeout(this._rerunTimer);
12423
+ await this.cancelPromise;
11820
12424
  await this.runningPromise;
11821
12425
  clearTimeout(this._rerunTimer);
11822
12426
  // server restarted
@@ -11854,8 +12458,14 @@ class Vitest {
11854
12458
  */
11855
12459
  invalidateFile(filepath) {
11856
12460
  this.projects.forEach(({ vite, browser }) => {
11857
- [...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach(({ moduleGraph }) => {
11858
- moduleGraph.getModulesByFile(filepath)?.forEach((module) => moduleGraph.invalidateModule(module));
12461
+ [...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach((environment) => {
12462
+ const { moduleGraph } = environment;
12463
+ const modules = moduleGraph.getModulesByFile(filepath);
12464
+ if (!modules) return;
12465
+ modules.forEach((module) => {
12466
+ moduleGraph.invalidateModule(module);
12467
+ this._fsCache.invalidateCachePath(environment, module.id);
12468
+ });
11859
12469
  });
11860
12470
  });
11861
12471
  }
@@ -11893,11 +12503,12 @@ class Vitest {
11893
12503
  this.pool = void 0;
11894
12504
  })());
11895
12505
  closePromises.push(...this._onClose.map((fn) => fn()));
11896
- return Promise.allSettled(closePromises).then((results) => {
12506
+ await Promise.allSettled(closePromises).then((results) => {
11897
12507
  results.forEach((r) => {
11898
12508
  if (r.status === "rejected") this.logger.error("error during close", r.reason);
11899
12509
  });
11900
12510
  });
12511
+ await this._traces?.finish();
11901
12512
  })();
11902
12513
  return this.closingPromise;
11903
12514
  }
@@ -11950,7 +12561,10 @@ class Vitest {
11950
12561
  * Register a handler that will be called when the test run is cancelled with `vitest.cancelCurrentRun`.
11951
12562
  */
11952
12563
  onCancel(fn) {
11953
- this._onCancelListeners.push(fn);
12564
+ this._onCancelListeners.add(fn);
12565
+ return () => {
12566
+ this._onCancelListeners.delete(fn);
12567
+ };
11954
12568
  }
11955
12569
  /**
11956
12570
  * Register a handler that will be called when the server is closed.
@@ -12091,7 +12705,7 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
12091
12705
  options.api = resolveApiServerConfig(options, defaultPort);
12092
12706
  // we replace every "import.meta.env" with "process.env"
12093
12707
  // to allow reassigning, so we need to put all envs on process.env
12094
- const { PROD, DEV,...envs } = viteConfig.env;
12708
+ const { PROD, DEV, ...envs } = viteConfig.env;
12095
12709
  // process.env can have only string values and will cast string on it if we pass other type,
12096
12710
  // so we are making them truthy
12097
12711
  process.env.PROD ??= PROD ? "1" : "";
@@ -12143,7 +12757,7 @@ async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {
12143
12757
  const root = slash(resolve$1(options.root || process.cwd()));
12144
12758
  const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$1(root, options.config) : any(configFiles, { cwd: root });
12145
12759
  options.config = configPath;
12146
- const { browser: _removeBrowser,...restOptions } = options;
12760
+ const { browser: _removeBrowser, ...restOptions } = options;
12147
12761
  const server = await createViteServer(mergeConfig({
12148
12762
  configFile: configPath,
12149
12763
  configLoader: options.configLoader,
@@ -12471,7 +13085,8 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
12471
13085
  else ctx.start(cliFilters);
12472
13086
  });
12473
13087
  try {
12474
- if (ctx.config.mergeReports) await ctx.mergeReports();
13088
+ if (ctx.config.clearCache) await ctx.experimental_clearCache();
13089
+ else if (ctx.config.mergeReports) await ctx.mergeReports();
12475
13090
  else if (ctx.config.standalone) await ctx.init();
12476
13091
  else await ctx.start(cliFilters);
12477
13092
  } catch (e) {