vitest 4.0.10 → 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.
- package/dist/browser.d.ts +4 -3
- package/dist/browser.js +1 -1
- package/dist/chunks/{base.BFVArrYW.js → base.DiCUKpyF.js} +46 -39
- package/dist/chunks/{browser.d.DnU_kh8a.d.ts → browser.d.D-d8eZY4.d.ts} +1 -1
- package/dist/chunks/{cac.D9CYcNPM.js → cac.aVhqBj0-.js} +21 -7
- package/dist/chunks/{cli-api.RnIE1JbW.js → cli-api.-bIZD4XU.js} +910 -297
- package/dist/chunks/{coverage.BUlIqJrL.js → coverage.CtyeYmKM.js} +7 -1
- package/dist/chunks/{creator.DU9qFjsW.js → creator.DAmOKTvJ.js} +3 -3
- package/dist/chunks/{global.d.BQDgW9Pr.d.ts → global.d.uY4Q0M5z.d.ts} +1 -1
- package/dist/chunks/{globals.NLOzC_A5.js → globals.C0izxiX3.js} +1 -1
- package/dist/chunks/{index.B8lJfb0J.js → index.CMvpbrsJ.js} +1 -1
- package/dist/chunks/{index.Dua7TZg_.js → index.CQwQ_SLL.js} +16 -4
- package/dist/chunks/{index.DZ-mI_Nm.js → index.DBx1AtPJ.js} +2 -1
- package/dist/chunks/{index.BYek7GgP.js → index.DWDW6mLz.js} +10 -1
- package/dist/chunks/{init-forks.BZSlxfwV.js → init-forks.DIuGPyId.js} +5 -5
- package/dist/chunks/{init-threads.CwE2n-Bv.js → init-threads.jC_8JdoN.js} +3 -3
- package/dist/chunks/{init.Cz2kTB9a.js → init.B3IeC_yW.js} +59 -21
- package/dist/chunks/{moduleRunner.d.BxT-OjLR.d.ts → moduleRunner.d.B5SW5pMI.d.ts} +9 -1
- package/dist/chunks/plugin.d.N8khPRFb.d.ts +38 -0
- package/dist/chunks/{reporters.d.keG-yFSu.d.ts → reporters.d.DgZLBdyd.d.ts} +107 -47
- package/dist/chunks/{setup-common.BOzbXE3x.js → setup-common.DGHc_BUK.js} +1 -1
- package/dist/chunks/{startModuleRunner.DLjmA_wU.js → startModuleRunner.DaBMy1JT.js} +97 -49
- package/dist/chunks/{test.BPErLMrw.js → test.DqQZzsWf.js} +14 -4
- package/dist/chunks/traces.BVPrsYso.js +151 -0
- package/dist/chunks/{config.d.BTfZNUu9.d.ts → traces.d.B8ukBJqA.d.ts} +36 -1
- package/dist/chunks/{vm.wSHjz-et.js → vm.BKyGp1KW.js} +12 -8
- package/dist/chunks/{worker.d.ZGohxCEd.d.ts → worker.d.B_PZTrCQ.d.ts} +5 -4
- package/dist/cli.js +2 -2
- package/dist/config.d.ts +8 -7
- package/dist/coverage.d.ts +6 -5
- package/dist/coverage.js +2 -2
- package/dist/index.d.ts +12 -10
- package/dist/index.js +2 -2
- package/dist/module-evaluator.d.ts +7 -6
- package/dist/module-evaluator.js +14 -1
- package/dist/module-runner.js +2 -1
- package/dist/node.d.ts +11 -10
- package/dist/node.js +8 -7
- package/dist/reporters.d.ts +6 -5
- package/dist/reporters.js +2 -2
- package/dist/runners.d.ts +5 -1
- package/dist/runners.js +1 -1
- package/dist/worker.d.ts +8 -7
- package/dist/worker.js +8 -7
- package/dist/workers/forks.js +9 -8
- package/dist/workers/runVmTests.js +22 -20
- package/dist/workers/threads.js +9 -8
- package/dist/workers/vmForks.js +5 -4
- package/dist/workers/vmThreads.js +5 -4
- package/package.json +17 -12
- package/dist/chunks/plugin.d.C6KrdvNG.d.ts +0 -9
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import fs, { promises
|
|
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,
|
|
5
|
+
import { noop, createDefer, slash, isExternalUrl, unwrapId, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
|
|
6
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.
|
|
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,
|
|
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.
|
|
15
|
+
import { v as version$1 } from './cac.aVhqBj0-.js';
|
|
16
16
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
17
17
|
import { c as createBirpc } from './index.0kCJoeWi.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.
|
|
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';
|
|
19
19
|
import require$$0$3 from 'events';
|
|
20
20
|
import require$$1$1 from 'https';
|
|
21
21
|
import require$$2 from 'http';
|
|
@@ -29,12 +29,13 @@ import require$$0$1 from 'buffer';
|
|
|
29
29
|
import { g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
|
|
30
30
|
import crypto, { createHash } from 'node:crypto';
|
|
31
31
|
import { rootDir, distDir } from '../path.js';
|
|
32
|
+
import { T as Traces } from './traces.BVPrsYso.js';
|
|
32
33
|
import createDebug from 'debug';
|
|
33
|
-
import { readFile, writeFile, rename, stat, unlink,
|
|
34
|
+
import { rm, readFile, writeFile, rename, stat, unlink, mkdir, copyFile } from 'node:fs/promises';
|
|
35
|
+
import c from 'tinyrainbow';
|
|
34
36
|
import { VitestModuleEvaluator } from '#module-evaluator';
|
|
35
37
|
import { ModuleRunner } from 'vite/module-runner';
|
|
36
38
|
import { Console } from 'node:console';
|
|
37
|
-
import c from 'tinyrainbow';
|
|
38
39
|
import { highlight } from '@vitest/utils/highlight';
|
|
39
40
|
import { createRequire, isBuiltin, builtinModules } from 'node:module';
|
|
40
41
|
import url, { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
5154
|
+
result.source = result.source || await promises.readFile(id, "utf-8");
|
|
5154
5155
|
} catch {}
|
|
5155
5156
|
return result;
|
|
5156
5157
|
}
|
|
@@ -5225,6 +5226,12 @@ class WebSocketReporter {
|
|
|
5225
5226
|
client.onTestAnnotate?.(testCase.id, annotation)?.catch?.(noop);
|
|
5226
5227
|
});
|
|
5227
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
|
+
}
|
|
5228
5235
|
async onTaskUpdate(packs, events) {
|
|
5229
5236
|
if (this.clients.size === 0) return;
|
|
5230
5237
|
this.clients.forEach((client) => {
|
|
@@ -5500,7 +5507,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5500
5507
|
dynamic: definition.dynamic,
|
|
5501
5508
|
meta: {},
|
|
5502
5509
|
timeout: 0,
|
|
5503
|
-
annotations: []
|
|
5510
|
+
annotations: [],
|
|
5511
|
+
artifacts: []
|
|
5504
5512
|
};
|
|
5505
5513
|
definition.task = task;
|
|
5506
5514
|
latestSuite.tasks.push(task);
|
|
@@ -5645,8 +5653,9 @@ class ResultsCache {
|
|
|
5645
5653
|
cachePath = null;
|
|
5646
5654
|
version;
|
|
5647
5655
|
root = "/";
|
|
5648
|
-
constructor(
|
|
5649
|
-
this.
|
|
5656
|
+
constructor(logger) {
|
|
5657
|
+
this.logger = logger;
|
|
5658
|
+
this.version = Vitest.version;
|
|
5650
5659
|
}
|
|
5651
5660
|
getCachePath() {
|
|
5652
5661
|
return this.cachePath;
|
|
@@ -5658,6 +5667,15 @@ class ResultsCache {
|
|
|
5658
5667
|
getResults(key) {
|
|
5659
5668
|
return this.cache.get(key);
|
|
5660
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
|
+
}
|
|
5661
5679
|
async readFromCache() {
|
|
5662
5680
|
if (!this.cachePath) return;
|
|
5663
5681
|
if (!fs.existsSync(this.cachePath)) return;
|
|
@@ -5710,8 +5728,8 @@ class ResultsCache {
|
|
|
5710
5728
|
class VitestCache {
|
|
5711
5729
|
results;
|
|
5712
5730
|
stats = new FilesStatsCache();
|
|
5713
|
-
constructor(
|
|
5714
|
-
this.results = new ResultsCache(
|
|
5731
|
+
constructor(logger) {
|
|
5732
|
+
this.results = new ResultsCache(logger);
|
|
5715
5733
|
}
|
|
5716
5734
|
getFileTestResults(key) {
|
|
5717
5735
|
return this.results.getResults(key);
|
|
@@ -5724,93 +5742,526 @@ class VitestCache {
|
|
|
5724
5742
|
}
|
|
5725
5743
|
}
|
|
5726
5744
|
|
|
5727
|
-
const
|
|
5728
|
-
const
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
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}`);
|
|
5740
5809
|
return {
|
|
5741
|
-
|
|
5742
|
-
|
|
5810
|
+
id: meta.id,
|
|
5811
|
+
url: meta.url,
|
|
5812
|
+
file: meta.file,
|
|
5813
|
+
code,
|
|
5814
|
+
importers: meta.importers,
|
|
5815
|
+
mappings: meta.mappings
|
|
5743
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
|
+
}
|
|
5744
6102
|
const isFileUrl = url.startsWith("file://");
|
|
5745
|
-
if (isExternalUrl(url) && !isFileUrl)
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
// 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
|
+
}
|
|
5753
6110
|
const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url));
|
|
5754
6111
|
const cached = !!moduleGraphModule.transformResult;
|
|
5755
|
-
|
|
6112
|
+
if (moduleGraphModule.file) trace.setAttribute("code.file.path", moduleGraphModule.file);
|
|
5756
6113
|
if (options?.cached && cached) return { cache: true };
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
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
|
+
});
|
|
5763
6134
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
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
|
+
});
|
|
5780
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
|
+
};
|
|
5781
6221
|
}
|
|
5782
|
-
|
|
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, {
|
|
5783
6231
|
...options,
|
|
5784
6232
|
inlineSourceMap: false
|
|
5785
|
-
}).catch(handleRollupError);
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
const
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
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));
|
|
5814
6265
|
};
|
|
5815
6266
|
}
|
|
5816
6267
|
let SOURCEMAPPING_URL = "sourceMa";
|
|
@@ -5825,8 +6276,7 @@ function processResultSource(environment, result) {
|
|
|
5825
6276
|
inlineSourceMap(node.transformResult);
|
|
5826
6277
|
return {
|
|
5827
6278
|
...result,
|
|
5828
|
-
code: node?.transformResult?.code || result.code
|
|
5829
|
-
transformResult: node?.transformResult
|
|
6279
|
+
code: node?.transformResult?.code || result.code
|
|
5830
6280
|
};
|
|
5831
6281
|
}
|
|
5832
6282
|
const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,([A-Za-z0-9+/=]+)$`, "gm");
|
|
@@ -5862,6 +6312,20 @@ function getCachedResult(result, tmp) {
|
|
|
5862
6312
|
invalidate: result.invalidate
|
|
5863
6313
|
};
|
|
5864
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
|
+
}
|
|
5865
6329
|
// serialize rollup error on server to preserve details as a test error
|
|
5866
6330
|
function handleRollupError(e) {
|
|
5867
6331
|
if (e instanceof Error && ("plugin" in e || "frame" in e || "id" in e))
|
|
@@ -5880,31 +6344,6 @@ function handleRollupError(e) {
|
|
|
5880
6344
|
};
|
|
5881
6345
|
throw e;
|
|
5882
6346
|
}
|
|
5883
|
-
/**
|
|
5884
|
-
* Performs an atomic write operation using the write-then-rename pattern.
|
|
5885
|
-
*
|
|
5886
|
-
* Why we need this:
|
|
5887
|
-
* - Ensures file integrity by never leaving partially written files on disk
|
|
5888
|
-
* - Prevents other processes from reading incomplete data during writes
|
|
5889
|
-
* - Particularly important for test files where incomplete writes could cause test failures
|
|
5890
|
-
*
|
|
5891
|
-
* The implementation writes to a temporary file first, then renames it to the target path.
|
|
5892
|
-
* This rename operation is atomic on most filesystems (including POSIX-compliant ones),
|
|
5893
|
-
* guaranteeing that other processes will only ever see the complete file.
|
|
5894
|
-
*
|
|
5895
|
-
* Added in https://github.com/vitest-dev/vitest/pull/7531
|
|
5896
|
-
*/
|
|
5897
|
-
async function atomicWriteFile(realFilePath, data) {
|
|
5898
|
-
const tmpFilePath = join(dirname(realFilePath), `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
5899
|
-
try {
|
|
5900
|
-
await writeFile(tmpFilePath, data, "utf-8");
|
|
5901
|
-
await rename(tmpFilePath, realFilePath);
|
|
5902
|
-
} finally {
|
|
5903
|
-
try {
|
|
5904
|
-
if (await stat(tmpFilePath)) await unlink(tmpFilePath);
|
|
5905
|
-
} catch {}
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
6347
|
|
|
5909
6348
|
// this is copy pasted from vite
|
|
5910
6349
|
function normalizeResolvedIdToUrl(environment, resolvedId) {
|
|
@@ -5938,7 +6377,15 @@ class ServerModuleRunner extends ModuleRunner {
|
|
|
5938
6377
|
if (name === "getBuiltins") return await environment.hot.handleInvoke(event);
|
|
5939
6378
|
if (name !== "fetchModule") return { error: /* @__PURE__ */ new Error(`Unknown method: ${name}. Expected "fetchModule".`) };
|
|
5940
6379
|
try {
|
|
5941
|
-
|
|
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 };
|
|
5942
6389
|
} catch (error) {
|
|
5943
6390
|
return { error };
|
|
5944
6391
|
}
|
|
@@ -6295,7 +6742,7 @@ async function getSpecificationsEnvironments(specifications) {
|
|
|
6295
6742
|
// reuse if projects have the same test files
|
|
6296
6743
|
let code = cache.get(filepath);
|
|
6297
6744
|
if (!code) {
|
|
6298
|
-
code = await promises
|
|
6745
|
+
code = await promises.readFile(filepath, "utf-8");
|
|
6299
6746
|
cache.set(filepath, code);
|
|
6300
6747
|
}
|
|
6301
6748
|
// 1. Check for control comments in the file
|
|
@@ -6553,9 +7000,9 @@ function shouldIgnoreDebugger(provider, browser) {
|
|
|
6553
7000
|
return browser !== "chromium";
|
|
6554
7001
|
}
|
|
6555
7002
|
|
|
6556
|
-
function createMethodsRPC(project,
|
|
7003
|
+
function createMethodsRPC(project, methodsOptions = {}) {
|
|
6557
7004
|
const vitest = project.vitest;
|
|
6558
|
-
const cacheFs =
|
|
7005
|
+
const cacheFs = methodsOptions.cacheFs ?? false;
|
|
6559
7006
|
project.vitest.state.metadata[project.name] ??= {
|
|
6560
7007
|
externalized: {},
|
|
6561
7008
|
duration: {},
|
|
@@ -6564,11 +7011,11 @@ function createMethodsRPC(project, options = {}) {
|
|
|
6564
7011
|
if (project.config.dumpDir && !existsSync(project.config.dumpDir)) mkdirSync(project.config.dumpDir, { recursive: true });
|
|
6565
7012
|
project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir;
|
|
6566
7013
|
return {
|
|
6567
|
-
async fetch(url, importer, environmentName, options) {
|
|
7014
|
+
async fetch(url, importer, environmentName, options, otelCarrier) {
|
|
6568
7015
|
const environment = project.vite.environments[environmentName];
|
|
6569
7016
|
if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
|
|
6570
7017
|
const start = performance.now();
|
|
6571
|
-
return await project._fetcher(url, importer, environment, cacheFs, options).then((result) => {
|
|
7018
|
+
return await project._fetcher(url, importer, environment, cacheFs, options, otelCarrier).then((result) => {
|
|
6572
7019
|
const duration = performance.now() - start;
|
|
6573
7020
|
project.vitest.state.transformTime += duration;
|
|
6574
7021
|
const metadata = project.vitest.state.metadata[project.name];
|
|
@@ -6609,25 +7056,25 @@ function createMethodsRPC(project, options = {}) {
|
|
|
6609
7056
|
return { code: (await environment.transformRequest(url).catch(handleRollupError))?.code };
|
|
6610
7057
|
},
|
|
6611
7058
|
async onQueued(file) {
|
|
6612
|
-
if (
|
|
7059
|
+
if (methodsOptions.collect) vitest.state.collectFiles(project, [file]);
|
|
6613
7060
|
else await vitest._testRun.enqueued(project, file);
|
|
6614
7061
|
},
|
|
6615
7062
|
async onCollected(files) {
|
|
6616
|
-
if (
|
|
7063
|
+
if (methodsOptions.collect) vitest.state.collectFiles(project, files);
|
|
6617
7064
|
else await vitest._testRun.collected(project, files);
|
|
6618
7065
|
},
|
|
6619
7066
|
onAfterSuiteRun(meta) {
|
|
6620
7067
|
vitest.coverageProvider?.onAfterSuiteRun(meta);
|
|
6621
7068
|
},
|
|
6622
|
-
async
|
|
6623
|
-
return vitest._testRun.
|
|
7069
|
+
async onTaskArtifactRecord(testId, artifact) {
|
|
7070
|
+
return vitest._testRun.recordArtifact(testId, artifact);
|
|
6624
7071
|
},
|
|
6625
7072
|
async onTaskUpdate(packs, events) {
|
|
6626
|
-
if (
|
|
7073
|
+
if (methodsOptions.collect) vitest.state.updateTasks(packs);
|
|
6627
7074
|
else await vitest._testRun.updated(packs, events);
|
|
6628
7075
|
},
|
|
6629
7076
|
async onUserConsoleLog(log) {
|
|
6630
|
-
if (
|
|
7077
|
+
if (methodsOptions.collect) vitest.state.updateUserLog(log);
|
|
6631
7078
|
else await vitest._testRun.log(log);
|
|
6632
7079
|
},
|
|
6633
7080
|
onUnhandledError(err, type) {
|
|
@@ -6664,6 +7111,8 @@ class PoolRunner {
|
|
|
6664
7111
|
_eventEmitter = new EventEmitter();
|
|
6665
7112
|
_offCancel;
|
|
6666
7113
|
_rpc;
|
|
7114
|
+
_otel = null;
|
|
7115
|
+
_traces;
|
|
6667
7116
|
get isTerminated() {
|
|
6668
7117
|
return this._state === RunnerState.STOPPED;
|
|
6669
7118
|
}
|
|
@@ -6677,6 +7126,21 @@ class PoolRunner {
|
|
|
6677
7126
|
this.worker = worker;
|
|
6678
7127
|
this.project = options.project;
|
|
6679
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
|
+
}
|
|
6680
7144
|
this._rpc = createBirpc(createMethodsRPC(this.project, {
|
|
6681
7145
|
collect: options.method === "collect",
|
|
6682
7146
|
cacheFs: worker.cacheFs
|
|
@@ -6688,13 +7152,39 @@ class PoolRunner {
|
|
|
6688
7152
|
on: (callback) => this._eventEmitter.on("rpc", callback),
|
|
6689
7153
|
timeout: -1
|
|
6690
7154
|
});
|
|
6691
|
-
this._offCancel =
|
|
7155
|
+
this._offCancel = vitest.onCancel((reason) => this._rpc.onCancel(reason));
|
|
6692
7156
|
}
|
|
6693
7157
|
postMessage(message) {
|
|
6694
7158
|
// Only send messages when runner is active (not fully stopped)
|
|
6695
7159
|
// Allow sending during STOPPING state for the 'stop' message itself
|
|
6696
7160
|
if (this._state !== RunnerState.STOPPED) return this.worker.send(message);
|
|
6697
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
|
+
}
|
|
6698
7188
|
async start() {
|
|
6699
7189
|
// Wait for any ongoing operation to complete
|
|
6700
7190
|
if (this._operationLock) await this._operationLock;
|
|
@@ -6702,15 +7192,21 @@ class PoolRunner {
|
|
|
6702
7192
|
if (this._state === RunnerState.STOPPED) throw new Error("[vitest-pool-runner]: Cannot start a stopped runner");
|
|
6703
7193
|
// Create operation lock to prevent concurrent start/stop
|
|
6704
7194
|
this._operationLock = createDefer();
|
|
7195
|
+
let startSpan;
|
|
6705
7196
|
try {
|
|
6706
7197
|
this._state = RunnerState.STARTING;
|
|
6707
|
-
await this.worker.start();
|
|
7198
|
+
await this._traces.$(`vitest.${this.worker.name}.start`, { context: this._otel?.workerContext }, () => this.worker.start());
|
|
6708
7199
|
// Attach event listeners AFTER starting worker to avoid issues
|
|
6709
7200
|
// if worker.start() fails
|
|
6710
7201
|
this.worker.on("error", this.emitWorkerError);
|
|
6711
7202
|
this.worker.on("exit", this.emitUnexpectedExit);
|
|
6712
7203
|
this.worker.on("message", this.emitWorkerMessage);
|
|
7204
|
+
startSpan = this.startTracesSpan("vitest.worker.start");
|
|
6713
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;
|
|
6714
7210
|
this.postMessage({
|
|
6715
7211
|
type: "start",
|
|
6716
7212
|
__vitest_worker_request__: true,
|
|
@@ -6722,14 +7218,21 @@ class PoolRunner {
|
|
|
6722
7218
|
},
|
|
6723
7219
|
config: this.project.serializedConfig,
|
|
6724
7220
|
pool: this.worker.name
|
|
7221
|
+
},
|
|
7222
|
+
traces: {
|
|
7223
|
+
enabled: tracesEnabled,
|
|
7224
|
+
sdkPath: tracesSdk,
|
|
7225
|
+
otelCarrier: this.getOTELCarrier()
|
|
6725
7226
|
}
|
|
6726
7227
|
});
|
|
6727
7228
|
await startPromise;
|
|
6728
7229
|
this._state = RunnerState.STARTED;
|
|
6729
7230
|
} catch (error) {
|
|
6730
7231
|
this._state = RunnerState.IDLE;
|
|
7232
|
+
startSpan?.recordException(error);
|
|
6731
7233
|
throw error;
|
|
6732
7234
|
} finally {
|
|
7235
|
+
startSpan?.end();
|
|
6733
7236
|
this._operationLock.resolve();
|
|
6734
7237
|
this._operationLock = null;
|
|
6735
7238
|
}
|
|
@@ -6738,7 +7241,9 @@ class PoolRunner {
|
|
|
6738
7241
|
// Wait for any ongoing operation to complete
|
|
6739
7242
|
if (this._operationLock) await this._operationLock;
|
|
6740
7243
|
if (this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING) return;
|
|
7244
|
+
this._otel?.span.setAttribute("vitest.worker.files", this._otel.files);
|
|
6741
7245
|
if (this._state === RunnerState.IDLE) {
|
|
7246
|
+
this._otel?.span.end();
|
|
6742
7247
|
this._state = RunnerState.STOPPED;
|
|
6743
7248
|
return;
|
|
6744
7249
|
}
|
|
@@ -6748,10 +7253,14 @@ class PoolRunner {
|
|
|
6748
7253
|
this._state = RunnerState.STOPPING;
|
|
6749
7254
|
// Remove exit listener early to avoid "unexpected exit" errors during shutdown
|
|
6750
7255
|
this.worker.off("exit", this.emitUnexpectedExit);
|
|
7256
|
+
const stopSpan = this.startTracesSpan("vitest.worker.stop");
|
|
6751
7257
|
await this.withTimeout(new Promise((resolve) => {
|
|
6752
7258
|
const onStop = (response) => {
|
|
6753
7259
|
if (response.type === "stopped") {
|
|
6754
|
-
if (response.error)
|
|
7260
|
+
if (response.error) {
|
|
7261
|
+
stopSpan.recordException(response.error);
|
|
7262
|
+
this.project.vitest.state.catchError(response.error, "Teardown Error");
|
|
7263
|
+
}
|
|
6755
7264
|
resolve();
|
|
6756
7265
|
this.off("message", onStop);
|
|
6757
7266
|
}
|
|
@@ -6759,15 +7268,18 @@ class PoolRunner {
|
|
|
6759
7268
|
this.on("message", onStop);
|
|
6760
7269
|
this.postMessage({
|
|
6761
7270
|
type: "stop",
|
|
6762
|
-
__vitest_worker_request__: true
|
|
7271
|
+
__vitest_worker_request__: true,
|
|
7272
|
+
otelCarrier: this.getOTELCarrier()
|
|
6763
7273
|
});
|
|
6764
|
-
}), STOP_TIMEOUT)
|
|
7274
|
+
}), STOP_TIMEOUT).finally(() => {
|
|
7275
|
+
stopSpan.end();
|
|
7276
|
+
});
|
|
6765
7277
|
this._eventEmitter.removeAllListeners();
|
|
6766
7278
|
this._offCancel();
|
|
6767
7279
|
this._rpc.$close(/* @__PURE__ */ new Error("[vitest-pool-runner]: Pending methods while closing rpc"));
|
|
6768
7280
|
// Stop the worker process (this sets _fork/_thread to undefined)
|
|
6769
7281
|
// Worker's event listeners (error, message) are implicitly removed when worker terminates
|
|
6770
|
-
await this.worker.stop();
|
|
7282
|
+
await this._traces.$(`vitest.${this.worker.name}.stop`, { context: this._otel?.workerContext }, () => this.worker.stop());
|
|
6771
7283
|
this._state = RunnerState.STOPPED;
|
|
6772
7284
|
} catch (error) {
|
|
6773
7285
|
// Ensure we transition to stopped state even on error
|
|
@@ -6776,6 +7288,7 @@ class PoolRunner {
|
|
|
6776
7288
|
} finally {
|
|
6777
7289
|
this._operationLock.resolve();
|
|
6778
7290
|
this._operationLock = null;
|
|
7291
|
+
this._otel?.span.end();
|
|
6779
7292
|
this._terminatePromise.resolve();
|
|
6780
7293
|
}
|
|
6781
7294
|
}
|
|
@@ -7231,14 +7744,15 @@ class Pool {
|
|
|
7231
7744
|
}
|
|
7232
7745
|
const poolId = runner.poolId ?? this.getWorkerId();
|
|
7233
7746
|
runner.poolId = poolId;
|
|
7747
|
+
const span = runner.startTracesSpan(`vitest.worker.${method}`);
|
|
7234
7748
|
// Start running the test in the worker
|
|
7235
|
-
runner.
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7749
|
+
runner.request(method, task.context);
|
|
7750
|
+
await resolver.promise.catch((error) => {
|
|
7751
|
+
span.recordException(error);
|
|
7752
|
+
throw error;
|
|
7753
|
+
}).finally(() => {
|
|
7754
|
+
span.end();
|
|
7240
7755
|
});
|
|
7241
|
-
await resolver.promise;
|
|
7242
7756
|
const index = this.activeTasks.indexOf(activeTask);
|
|
7243
7757
|
if (index !== -1) this.activeTasks.splice(index, 1);
|
|
7244
7758
|
if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
|
|
@@ -7674,7 +8188,8 @@ function serializeConfig(project) {
|
|
|
7674
8188
|
standalone: config.standalone,
|
|
7675
8189
|
printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
|
|
7676
8190
|
benchmark: config.benchmark && { includeSamples: config.benchmark.includeSamples },
|
|
7677
|
-
serializedDefines: config.browser.enabled ? "" : project._serializedDefines || ""
|
|
8191
|
+
serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
|
|
8192
|
+
experimental: { fsModuleCache: config.experimental.fsModuleCache ?? false }
|
|
7678
8193
|
};
|
|
7679
8194
|
}
|
|
7680
8195
|
|
|
@@ -8725,13 +9240,19 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
8725
9240
|
return project.vitest.matchesProjectFilter(name);
|
|
8726
9241
|
})) throw new VitestFilteredOutProjectError();
|
|
8727
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
|
+
}
|
|
8728
9252
|
return {
|
|
8729
9253
|
base: "/",
|
|
8730
9254
|
environments: { __vitest__: { dev: {} } },
|
|
8731
|
-
test:
|
|
8732
|
-
label: name,
|
|
8733
|
-
color
|
|
8734
|
-
} }
|
|
9255
|
+
test: vitestConfig
|
|
8735
9256
|
};
|
|
8736
9257
|
}
|
|
8737
9258
|
},
|
|
@@ -8816,16 +9337,21 @@ class VitestResolver {
|
|
|
8816
9337
|
options;
|
|
8817
9338
|
externalizeCache = /* @__PURE__ */ new Map();
|
|
8818
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();
|
|
8819
9345
|
this.options = {
|
|
8820
|
-
moduleDirectories: config.deps.moduleDirectories,
|
|
9346
|
+
moduleDirectories: config.deps.moduleDirectories?.sort(),
|
|
8821
9347
|
inlineFiles: config.setupFiles.flatMap((file) => {
|
|
8822
9348
|
if (file.startsWith("file://")) return file;
|
|
8823
9349
|
const resolvedId = resolve(file);
|
|
8824
9350
|
return [resolvedId, pathToFileURL(resolvedId).href];
|
|
8825
9351
|
}),
|
|
8826
9352
|
cacheDir,
|
|
8827
|
-
inline
|
|
8828
|
-
external
|
|
9353
|
+
inline,
|
|
9354
|
+
external
|
|
8829
9355
|
};
|
|
8830
9356
|
}
|
|
8831
9357
|
shouldExternalize(file) {
|
|
@@ -8882,7 +9408,7 @@ async function isValidNodeImport(id) {
|
|
|
8882
9408
|
if (/\.(?:\w+-)?esm?(?:-\w+)?\.js$|\/esm?\//.test(id)) return false;
|
|
8883
9409
|
try {
|
|
8884
9410
|
await esModuleLexer.init;
|
|
8885
|
-
const code = await promises
|
|
9411
|
+
const code = await promises.readFile(id, "utf8");
|
|
8886
9412
|
const [, , , hasModuleSyntax] = esModuleLexer.parse(code);
|
|
8887
9413
|
return !hasModuleSyntax;
|
|
8888
9414
|
} catch {
|
|
@@ -9148,15 +9674,24 @@ class TestProject {
|
|
|
9148
9674
|
* @param filters String filters to match the test files.
|
|
9149
9675
|
*/
|
|
9150
9676
|
async globTestFiles(filters = []) {
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
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
|
+
});
|
|
9160
9695
|
}
|
|
9161
9696
|
async globAllTestFiles(include, exclude, includeSource, cwd) {
|
|
9162
9697
|
if (this.testFilesList) return this.testFilesList;
|
|
@@ -9165,7 +9700,7 @@ class TestProject {
|
|
|
9165
9700
|
const files = await this.globFiles(includeSource, exclude, cwd);
|
|
9166
9701
|
await Promise.all(files.map(async (file) => {
|
|
9167
9702
|
try {
|
|
9168
|
-
const code = await promises
|
|
9703
|
+
const code = await promises.readFile(file, "utf-8");
|
|
9169
9704
|
if (this.isInSourceTestCode(code)) testFiles.push(file);
|
|
9170
9705
|
} catch {
|
|
9171
9706
|
return null;
|
|
@@ -9317,10 +9852,7 @@ class TestProject {
|
|
|
9317
9852
|
this._resolver = new VitestResolver(server.config.cacheDir, this._config);
|
|
9318
9853
|
this._vite = server;
|
|
9319
9854
|
this._serializedDefines = createDefinesScript(server.config.define);
|
|
9320
|
-
this._fetcher = createFetchModuleFunction(this._resolver, this.
|
|
9321
|
-
dumpFolder: this.config.dumpDir,
|
|
9322
|
-
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
|
|
9323
|
-
});
|
|
9855
|
+
this._fetcher = createFetchModuleFunction(this._resolver, this._config, this.vitest._fsCache, this.vitest._traces, this.tmpDir);
|
|
9324
9856
|
const environment = server.environments.__vitest__;
|
|
9325
9857
|
this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
|
|
9326
9858
|
}
|
|
@@ -9980,6 +10512,14 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
9980
10512
|
return [...this.task.annotations];
|
|
9981
10513
|
}
|
|
9982
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
|
+
/**
|
|
9983
10523
|
* Useful information about the test like duration, memory usage, etc.
|
|
9984
10524
|
* Diagnostic is only available after the test has finished.
|
|
9985
10525
|
*/
|
|
@@ -10834,15 +11374,22 @@ class TestRun {
|
|
|
10834
11374
|
this.vitest.state.updateUserLog(log);
|
|
10835
11375
|
await this.vitest.report("onUserConsoleLog", log);
|
|
10836
11376
|
}
|
|
10837
|
-
async
|
|
11377
|
+
async recordArtifact(testId, artifact) {
|
|
10838
11378
|
const task = this.vitest.state.idMap.get(testId);
|
|
10839
11379
|
const entity = task && this.vitest.state.getReportedEntity(task);
|
|
10840
11380
|
assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
|
|
10841
|
-
assert$1(entity.type === "test", `
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
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;
|
|
10846
11393
|
}
|
|
10847
11394
|
async updated(update, events) {
|
|
10848
11395
|
this.syncUpdateStacks(update);
|
|
@@ -10929,15 +11476,14 @@ class TestRun {
|
|
|
10929
11476
|
}
|
|
10930
11477
|
}
|
|
10931
11478
|
}
|
|
10932
|
-
async resolveTestAttachment(test,
|
|
11479
|
+
async resolveTestAttachment(test, attachment, filename) {
|
|
10933
11480
|
const project = test.project;
|
|
10934
|
-
const attachment = annotation.attachment;
|
|
10935
11481
|
if (!attachment) return attachment;
|
|
10936
11482
|
const path = attachment.path;
|
|
10937
11483
|
if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
|
|
10938
11484
|
const currentPath = resolve(project.config.root, path);
|
|
10939
11485
|
const hash = createHash("sha1").update(currentPath).digest("hex");
|
|
10940
|
-
const newPath = resolve(project.config.attachmentsDir, `${sanitizeFilePath(
|
|
11486
|
+
const newPath = resolve(project.config.attachmentsDir, `${filename ? `${sanitizeFilePath(filename)}-` : ""}${hash}${extname(currentPath)}`);
|
|
10941
11487
|
if (!existsSync(project.config.attachmentsDir)) await mkdir(project.config.attachmentsDir, { recursive: true });
|
|
10942
11488
|
await copyFile(currentPath, newPath);
|
|
10943
11489
|
attachment.path = newPath;
|
|
@@ -11170,7 +11716,9 @@ class Vitest {
|
|
|
11170
11716
|
/** @internal */ _testRun = void 0;
|
|
11171
11717
|
/** @internal */ _resolver;
|
|
11172
11718
|
/** @internal */ _fetcher;
|
|
11719
|
+
/** @internal */ _fsCache;
|
|
11173
11720
|
/** @internal */ _tmpDir = join(tmpdir(), nanoid());
|
|
11721
|
+
/** @internal */ _traces;
|
|
11174
11722
|
isFirstRun = true;
|
|
11175
11723
|
restartsCount = 0;
|
|
11176
11724
|
specifications;
|
|
@@ -11249,15 +11797,19 @@ class Vitest {
|
|
|
11249
11797
|
const resolved = resolveConfig(this, options, server.config);
|
|
11250
11798
|
this._config = resolved;
|
|
11251
11799
|
this._state = new StateManager({ onUnhandledError: resolved.onUnhandledError });
|
|
11252
|
-
this._cache = new VitestCache(this.
|
|
11800
|
+
this._cache = new VitestCache(this.logger);
|
|
11253
11801
|
this._snapshot = new SnapshotManager({ ...resolved.snapshotOptions });
|
|
11254
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
|
+
});
|
|
11255
11809
|
if (this.config.watch) this.watcher.registerWatcher();
|
|
11256
11810
|
this._resolver = new VitestResolver(server.config.cacheDir, resolved);
|
|
11257
|
-
this.
|
|
11258
|
-
|
|
11259
|
-
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
|
|
11260
|
-
});
|
|
11811
|
+
this._fsCache = new FileSystemModuleCache(this);
|
|
11812
|
+
this._fetcher = createFetchModuleFunction(this._resolver, this._config, this._fsCache, this._traces, this._tmpDir);
|
|
11261
11813
|
const environment = server.environments.__vitest__;
|
|
11262
11814
|
this.runner = new ServerModuleRunner(environment, this._fetcher, resolved);
|
|
11263
11815
|
if (this.config.watch) {
|
|
@@ -11290,7 +11842,8 @@ class Vitest {
|
|
|
11290
11842
|
return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
|
|
11291
11843
|
project,
|
|
11292
11844
|
vitest: this,
|
|
11293
|
-
injectTestProjects: this.injectTestProject
|
|
11845
|
+
injectTestProjects: this.injectTestProject,
|
|
11846
|
+
experimental_defineCacheKeyGenerator: (callback) => this._fsCache.defineCacheKeyGenerator(callback)
|
|
11294
11847
|
}));
|
|
11295
11848
|
}));
|
|
11296
11849
|
if (this._cliOptions.browser?.enabled) {
|
|
@@ -11308,7 +11861,8 @@ class Vitest {
|
|
|
11308
11861
|
if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
|
|
11309
11862
|
if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
|
|
11310
11863
|
this.reporters = resolved.mode === "benchmark" ? await createBenchmarkReporters(toArray(resolved.benchmark?.reporters), this.runner) : await createReporters(resolved.reporters, this);
|
|
11311
|
-
await
|
|
11864
|
+
await this._fsCache.ensureCacheIntegrity();
|
|
11865
|
+
await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
|
|
11312
11866
|
}
|
|
11313
11867
|
/** @internal */
|
|
11314
11868
|
get coverageProvider() {
|
|
@@ -11320,10 +11874,19 @@ class Vitest {
|
|
|
11320
11874
|
this.configOverride.coverage.enabled = true;
|
|
11321
11875
|
await this.createCoverageProvider();
|
|
11322
11876
|
await this.coverageProvider?.onEnabled?.();
|
|
11877
|
+
// onFileTransform is the only thing that affects hash
|
|
11878
|
+
if (this.coverageProvider?.onFileTransform) this.clearAllCachePaths();
|
|
11323
11879
|
}
|
|
11324
11880
|
disableCoverage() {
|
|
11325
11881
|
this.configOverride.coverage ??= {};
|
|
11326
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
|
+
});
|
|
11327
11890
|
}
|
|
11328
11891
|
_coverageOverrideCache = /* @__PURE__ */ new WeakMap();
|
|
11329
11892
|
/** @internal */
|
|
@@ -11419,33 +11982,43 @@ class Vitest {
|
|
|
11419
11982
|
return this._coverageProvider;
|
|
11420
11983
|
}
|
|
11421
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
|
+
/**
|
|
11422
11993
|
* Merge reports from multiple runs located in the specified directory (value from `--merge-reports` if not specified).
|
|
11423
11994
|
*/
|
|
11424
11995
|
async mergeReports(directory) {
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
const
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
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
|
+
});
|
|
11449
12022
|
}
|
|
11450
12023
|
/**
|
|
11451
12024
|
* Returns the seed, if tests are running in a random order.
|
|
@@ -11467,13 +12040,25 @@ class Vitest {
|
|
|
11467
12040
|
await this._testRun.updated(packs, events).catch(noop);
|
|
11468
12041
|
}
|
|
11469
12042
|
async collect(filters) {
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
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
|
+
});
|
|
11477
12062
|
}
|
|
11478
12063
|
/**
|
|
11479
12064
|
* Returns the list of test files that match the config and filters.
|
|
@@ -11491,49 +12076,67 @@ class Vitest {
|
|
|
11491
12076
|
* @param filters String filters to match the test files
|
|
11492
12077
|
*/
|
|
11493
12078
|
async start(filters) {
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
await this.
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
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
|
+
});
|
|
11522
12123
|
}
|
|
11523
12124
|
/**
|
|
11524
12125
|
* Initialize reporters and the coverage provider. This method doesn't run any tests.
|
|
11525
12126
|
* If the `--watch` flag is provided, Vitest will still run changed tests even if this method was not called.
|
|
11526
12127
|
*/
|
|
11527
12128
|
async init() {
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
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
|
+
});
|
|
11537
12140
|
}
|
|
11538
12141
|
/**
|
|
11539
12142
|
* If there is a test run happening, returns a promise that will
|
|
@@ -11583,51 +12186,53 @@ class Vitest {
|
|
|
11583
12186
|
return result;
|
|
11584
12187
|
}
|
|
11585
12188
|
async runFiles(specs, allTestsRun) {
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
if (!this.pool) this.pool = createPool(this);
|
|
11596
|
-
const invalidates = Array.from(this.watcher.invalidates);
|
|
11597
|
-
this.watcher.invalidates.clear();
|
|
11598
|
-
this.snapshot.clear();
|
|
11599
|
-
this.state.clearErrors();
|
|
11600
|
-
if (!this.isFirstRun && this._coverageOptions.cleanOnRerun) await this.coverageProvider?.clean();
|
|
11601
|
-
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 () => {
|
|
11602
12198
|
try {
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
this.
|
|
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);
|
|
11606
12226
|
}
|
|
11607
|
-
|
|
11608
|
-
this.
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
};
|
|
11616
|
-
} finally {
|
|
11617
|
-
const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
|
|
11618
|
-
const errors = this.state.getUnhandledErrors();
|
|
11619
|
-
this._checkUnhandledErrors(errors);
|
|
11620
|
-
await this._testRun.end(specs, errors, coverage);
|
|
11621
|
-
await this.reportCoverage(coverage, allTestsRun);
|
|
11622
|
-
}
|
|
11623
|
-
})().finally(() => {
|
|
11624
|
-
this.runningPromise = void 0;
|
|
11625
|
-
this.isFirstRun = false;
|
|
11626
|
-
// all subsequent runs will treat this as a fresh run
|
|
11627
|
-
this.config.changed = false;
|
|
11628
|
-
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;
|
|
11629
12235
|
});
|
|
11630
|
-
return await this.runningPromise;
|
|
11631
12236
|
}
|
|
11632
12237
|
async experimental_parseSpecifications(specifications, options) {
|
|
11633
12238
|
if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
|
|
@@ -11853,8 +12458,14 @@ class Vitest {
|
|
|
11853
12458
|
*/
|
|
11854
12459
|
invalidateFile(filepath) {
|
|
11855
12460
|
this.projects.forEach(({ vite, browser }) => {
|
|
11856
|
-
[...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach((
|
|
11857
|
-
moduleGraph
|
|
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
|
+
});
|
|
11858
12469
|
});
|
|
11859
12470
|
});
|
|
11860
12471
|
}
|
|
@@ -11892,11 +12503,12 @@ class Vitest {
|
|
|
11892
12503
|
this.pool = void 0;
|
|
11893
12504
|
})());
|
|
11894
12505
|
closePromises.push(...this._onClose.map((fn) => fn()));
|
|
11895
|
-
|
|
12506
|
+
await Promise.allSettled(closePromises).then((results) => {
|
|
11896
12507
|
results.forEach((r) => {
|
|
11897
12508
|
if (r.status === "rejected") this.logger.error("error during close", r.reason);
|
|
11898
12509
|
});
|
|
11899
12510
|
});
|
|
12511
|
+
await this._traces?.finish();
|
|
11900
12512
|
})();
|
|
11901
12513
|
return this.closingPromise;
|
|
11902
12514
|
}
|
|
@@ -12473,7 +13085,8 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
|
|
|
12473
13085
|
else ctx.start(cliFilters);
|
|
12474
13086
|
});
|
|
12475
13087
|
try {
|
|
12476
|
-
if (ctx.config.
|
|
13088
|
+
if (ctx.config.clearCache) await ctx.experimental_clearCache();
|
|
13089
|
+
else if (ctx.config.mergeReports) await ctx.mergeReports();
|
|
12477
13090
|
else if (ctx.config.standalone) await ctx.init();
|
|
12478
13091
|
else await ctx.start(cliFilters);
|
|
12479
13092
|
} catch (e) {
|