@vitest/coverage-v8 1.0.0-beta.5 → 1.0.0
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/index.d.ts +0 -1
- package/dist/provider.d.ts +6 -5
- package/dist/provider.js +110 -53
- package/package.json +7 -5
package/dist/index.d.ts
CHANGED
package/dist/provider.d.ts
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import { Profiler } from 'node:inspector';
|
2
1
|
import { BaseCoverageProvider } from 'vitest/coverage';
|
3
2
|
import { CoverageProvider, AfterSuiteRunMeta, ReportContext, ResolvedCoverageOptions } from 'vitest';
|
4
3
|
import { Vitest } from 'vitest/node';
|
@@ -17,8 +16,8 @@ interface TestExclude {
|
|
17
16
|
};
|
18
17
|
}
|
19
18
|
type Options = ResolvedCoverageOptions<'v8'>;
|
20
|
-
type
|
21
|
-
type
|
19
|
+
type Filename = string;
|
20
|
+
type CoverageFilesByTransformMode = Record<AfterSuiteRunMeta['transformMode'], Filename[]>;
|
22
21
|
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT;
|
23
22
|
declare const DEFAULT_PROJECT: unique symbol;
|
24
23
|
declare class V8CoverageProvider extends BaseCoverageProvider implements CoverageProvider {
|
@@ -26,7 +25,9 @@ declare class V8CoverageProvider extends BaseCoverageProvider implements Coverag
|
|
26
25
|
ctx: Vitest;
|
27
26
|
options: Options;
|
28
27
|
testExclude: InstanceType<TestExclude>;
|
29
|
-
|
28
|
+
coverageFiles: Map<ProjectName, CoverageFilesByTransformMode>;
|
29
|
+
coverageFilesDirectory: string;
|
30
|
+
pendingPromises: Promise<void>[];
|
30
31
|
initialize(ctx: Vitest): void;
|
31
32
|
resolveOptions(): Options;
|
32
33
|
clean(clean?: boolean): Promise<void>;
|
@@ -34,7 +35,7 @@ declare class V8CoverageProvider extends BaseCoverageProvider implements Coverag
|
|
34
35
|
reportCoverage({ allTestsRun }?: ReportContext): Promise<void>;
|
35
36
|
private getUntestedFiles;
|
36
37
|
private getSources;
|
37
|
-
private
|
38
|
+
private convertCoverage;
|
38
39
|
}
|
39
40
|
|
40
41
|
export { V8CoverageProvider };
|
package/dist/provider.js
CHANGED
@@ -11,6 +11,7 @@ import { parseModule } from 'magicast';
|
|
11
11
|
import remapping from '@ampproject/remapping';
|
12
12
|
import c from 'picocolors';
|
13
13
|
import { provider } from 'std-env';
|
14
|
+
import createDebug from 'debug';
|
14
15
|
import { builtinModules } from 'node:module';
|
15
16
|
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config';
|
16
17
|
import { BaseCoverageProvider } from 'vitest/coverage';
|
@@ -170,12 +171,16 @@ function cleanUrl(url) {
|
|
170
171
|
const WRAPPER_LENGTH = 185;
|
171
172
|
const VITE_EXPORTS_LINE_PATTERN = /Object\.defineProperty\(__vite_ssr_exports__.*\n/g;
|
172
173
|
const DEFAULT_PROJECT = Symbol.for("default-project");
|
174
|
+
const debug = createDebug("vitest:coverage");
|
175
|
+
let uniqueId = 0;
|
173
176
|
class V8CoverageProvider extends BaseCoverageProvider {
|
174
177
|
name = "v8";
|
175
178
|
ctx;
|
176
179
|
options;
|
177
180
|
testExclude;
|
178
|
-
|
181
|
+
coverageFiles = /* @__PURE__ */ new Map();
|
182
|
+
coverageFilesDirectory;
|
183
|
+
pendingPromises = [];
|
179
184
|
initialize(ctx) {
|
180
185
|
const config = ctx.config.coverage;
|
181
186
|
this.ctx = ctx;
|
@@ -203,6 +208,7 @@ class V8CoverageProvider extends BaseCoverageProvider {
|
|
203
208
|
extension: this.options.extension,
|
204
209
|
relativePath: !this.options.allowExternal
|
205
210
|
});
|
211
|
+
this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp");
|
206
212
|
}
|
207
213
|
resolveOptions() {
|
208
214
|
return this.options;
|
@@ -210,7 +216,11 @@ class V8CoverageProvider extends BaseCoverageProvider {
|
|
210
216
|
async clean(clean = true) {
|
211
217
|
if (clean && existsSync(this.options.reportsDirectory))
|
212
218
|
await promises.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 });
|
213
|
-
this.
|
219
|
+
if (existsSync(this.coverageFilesDirectory))
|
220
|
+
await promises.rm(this.coverageFilesDirectory, { recursive: true, force: true, maxRetries: 10 });
|
221
|
+
await promises.mkdir(this.coverageFilesDirectory, { recursive: true });
|
222
|
+
this.coverageFiles = /* @__PURE__ */ new Map();
|
223
|
+
this.pendingPromises = [];
|
214
224
|
}
|
215
225
|
/*
|
216
226
|
* Coverage and meta information passed from Vitest runners.
|
@@ -220,29 +230,49 @@ class V8CoverageProvider extends BaseCoverageProvider {
|
|
220
230
|
onAfterSuiteRun({ coverage, transformMode, projectName }) {
|
221
231
|
if (transformMode !== "web" && transformMode !== "ssr")
|
222
232
|
throw new Error(`Invalid transform mode: ${transformMode}`);
|
223
|
-
let entry = this.
|
233
|
+
let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT);
|
224
234
|
if (!entry) {
|
225
235
|
entry = { web: [], ssr: [] };
|
226
|
-
this.
|
236
|
+
this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry);
|
227
237
|
}
|
228
|
-
|
238
|
+
const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
|
239
|
+
entry[transformMode].push(filename);
|
240
|
+
const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8");
|
241
|
+
this.pendingPromises.push(promise);
|
229
242
|
}
|
230
243
|
async reportCoverage({ allTestsRun } = {}) {
|
231
244
|
if (provider === "stackblitz")
|
232
245
|
this.ctx.logger.log(c.blue(" % ") + c.yellow("@vitest/coverage-v8 does not work on Stackblitz. Report will be empty."));
|
233
|
-
const
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
)
|
246
|
+
const coverageMap = libCoverage.createCoverageMap({});
|
247
|
+
let index = 0;
|
248
|
+
const total = this.pendingPromises.length;
|
249
|
+
await Promise.all(this.pendingPromises);
|
250
|
+
this.pendingPromises = [];
|
251
|
+
for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) {
|
252
|
+
for (const [transformMode, filenames] of Object.entries(coveragePerProject)) {
|
253
|
+
let merged = { result: [] };
|
254
|
+
for (const chunk of toSlices(filenames, this.options.processingConcurrency)) {
|
255
|
+
if (debug.enabled) {
|
256
|
+
index += chunk.length;
|
257
|
+
debug("Covered files %d/%d", index, total);
|
258
|
+
}
|
259
|
+
await Promise.all(chunk.map(async (filename) => {
|
260
|
+
const contents = await promises.readFile(filename, "utf-8");
|
261
|
+
const coverage = JSON.parse(contents);
|
262
|
+
merged = mergeProcessCovs([merged, coverage]);
|
263
|
+
}));
|
264
|
+
}
|
265
|
+
const converted = await this.convertCoverage(merged, projectName, transformMode);
|
266
|
+
const transformedCoverage = await transformCoverage(converted);
|
267
|
+
coverageMap.merge(transformedCoverage);
|
268
|
+
}
|
269
|
+
}
|
239
270
|
if (this.options.all && allTestsRun) {
|
240
|
-
const coveredFiles =
|
271
|
+
const coveredFiles = coverageMap.files();
|
241
272
|
const untestedCoverage = await this.getUntestedFiles(coveredFiles);
|
242
|
-
const
|
243
|
-
|
273
|
+
const converted = await this.convertCoverage(untestedCoverage);
|
274
|
+
coverageMap.merge(await transformCoverage(converted));
|
244
275
|
}
|
245
|
-
const coverageMap = mergeCoverageMaps(...coverageMaps);
|
246
276
|
const context = libReport.createContext({
|
247
277
|
dir: this.options.reportsDirectory,
|
248
278
|
coverageMap,
|
@@ -281,30 +311,43 @@ class V8CoverageProvider extends BaseCoverageProvider {
|
|
281
311
|
}
|
282
312
|
});
|
283
313
|
}
|
314
|
+
this.coverageFiles = /* @__PURE__ */ new Map();
|
315
|
+
await promises.rm(this.coverageFilesDirectory, { recursive: true });
|
284
316
|
}
|
285
317
|
}
|
286
318
|
async getUntestedFiles(testedFiles) {
|
287
319
|
const transformResults = normalizeTransformResults(this.ctx.vitenode.fetchCache);
|
288
320
|
const includedFiles = await this.testExclude.glob(this.ctx.config.root);
|
289
321
|
const uncoveredFiles = includedFiles.map((file) => pathToFileURL(resolve(this.ctx.config.root, file))).filter((file) => !testedFiles.includes(file.pathname));
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
322
|
+
let merged = { result: [] };
|
323
|
+
let index = 0;
|
324
|
+
for (const chunk of toSlices(uncoveredFiles, this.options.processingConcurrency)) {
|
325
|
+
if (debug.enabled) {
|
326
|
+
index += chunk.length;
|
327
|
+
debug("Uncovered files %d/%d", index, uncoveredFiles.length);
|
328
|
+
}
|
329
|
+
const coverages = await Promise.all(chunk.map(async (filename) => {
|
330
|
+
const { source } = await this.getSources(filename.href, transformResults);
|
331
|
+
const coverage = {
|
332
|
+
url: filename.href,
|
333
|
+
scriptId: "0",
|
334
|
+
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps.
|
335
|
+
functions: [{
|
336
|
+
ranges: [{
|
337
|
+
startOffset: 0,
|
338
|
+
endOffset: source.length,
|
339
|
+
count: 0
|
340
|
+
}],
|
341
|
+
isBlockCoverage: true,
|
342
|
+
// This is magical value that indicates an empty report: https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/v8-to-istanbul.js#LL131C40-L131C40
|
343
|
+
functionName: "(empty-report)"
|
344
|
+
}]
|
345
|
+
};
|
346
|
+
return { result: [coverage] };
|
347
|
+
}));
|
348
|
+
merged = mergeProcessCovs([merged, ...coverages]);
|
349
|
+
}
|
350
|
+
return merged;
|
308
351
|
}
|
309
352
|
async getSources(url, transformResults, functions = []) {
|
310
353
|
const filePath = normalize(fileURLToPath(url));
|
@@ -330,31 +373,33 @@ class V8CoverageProvider extends BaseCoverageProvider {
|
|
330
373
|
}
|
331
374
|
};
|
332
375
|
}
|
333
|
-
async
|
376
|
+
async convertCoverage(coverage, projectName, transformMode) {
|
334
377
|
const viteNode = this.ctx.projects.find((project) => project.getName() === projectName)?.vitenode || this.ctx.vitenode;
|
335
378
|
const fetchCache = transformMode ? viteNode.fetchCaches[transformMode] : viteNode.fetchCache;
|
336
379
|
const transformResults = normalizeTransformResults(fetchCache);
|
337
|
-
const
|
338
|
-
const
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
380
|
+
const scriptCoverages = coverage.result.filter((result) => this.testExclude.shouldInstrument(fileURLToPath(result.url)));
|
381
|
+
const coverageMap = libCoverage.createCoverageMap({});
|
382
|
+
let index = 0;
|
383
|
+
for (const chunk of toSlices(scriptCoverages, this.options.processingConcurrency)) {
|
384
|
+
if (debug.enabled) {
|
385
|
+
index += chunk.length;
|
386
|
+
debug("Converting %d/%d", index, scriptCoverages.length);
|
387
|
+
}
|
388
|
+
await Promise.all(chunk.map(async ({ url, functions }) => {
|
389
|
+
const sources = await this.getSources(url, transformResults, functions);
|
390
|
+
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0;
|
391
|
+
const converter = v8ToIstanbul(url, wrapperLength, sources);
|
392
|
+
await converter.load();
|
393
|
+
converter.applyCoverage(functions);
|
394
|
+
coverageMap.merge(converter.toIstanbul());
|
395
|
+
}));
|
396
|
+
}
|
397
|
+
return coverageMap;
|
350
398
|
}
|
351
399
|
}
|
352
|
-
function
|
353
|
-
|
354
|
-
|
355
|
-
map.merge(previousCoverageMap);
|
356
|
-
return map;
|
357
|
-
}, libCoverage.createCoverageMap({}));
|
400
|
+
async function transformCoverage(coverageMap) {
|
401
|
+
const sourceMapStore = libSourceMaps.createSourceMapStore();
|
402
|
+
return await sourceMapStore.transformCoverage(coverageMap);
|
358
403
|
}
|
359
404
|
function removeViteHelpersFromSourceMaps(source, map) {
|
360
405
|
if (!source || !source.match(VITE_EXPORTS_LINE_PATTERN))
|
@@ -388,5 +433,17 @@ function normalizeTransformResults(fetchCache) {
|
|
388
433
|
function hasTerminalReporter(reporters) {
|
389
434
|
return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity");
|
390
435
|
}
|
436
|
+
function toSlices(array, size) {
|
437
|
+
return array.reduce((chunks, item) => {
|
438
|
+
const index = Math.max(0, chunks.length - 1);
|
439
|
+
const lastChunk = chunks[index] || [];
|
440
|
+
chunks[index] = lastChunk;
|
441
|
+
if (lastChunk.length >= size)
|
442
|
+
chunks.push([item]);
|
443
|
+
else
|
444
|
+
lastChunk.push(item);
|
445
|
+
return chunks;
|
446
|
+
}, []);
|
447
|
+
}
|
391
448
|
|
392
449
|
export { V8CoverageProvider };
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitest/coverage-v8",
|
3
3
|
"type": "module",
|
4
|
-
"version": "1.0.0
|
4
|
+
"version": "1.0.0",
|
5
5
|
"description": "V8 coverage provider for Vitest",
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
7
7
|
"license": "MIT",
|
@@ -42,6 +42,7 @@
|
|
42
42
|
"dependencies": {
|
43
43
|
"@ampproject/remapping": "^2.2.1",
|
44
44
|
"@bcoe/v8-coverage": "^0.2.3",
|
45
|
+
"debug": "^4.3.4",
|
45
46
|
"istanbul-lib-coverage": "^3.2.2",
|
46
47
|
"istanbul-lib-report": "^3.0.1",
|
47
48
|
"istanbul-lib-source-maps": "^4.0.1",
|
@@ -49,18 +50,19 @@
|
|
49
50
|
"magic-string": "^0.30.5",
|
50
51
|
"magicast": "^0.3.2",
|
51
52
|
"picocolors": "^1.0.0",
|
52
|
-
"std-env": "^3.
|
53
|
+
"std-env": "^3.5.0",
|
53
54
|
"test-exclude": "^6.0.0",
|
54
|
-
"v8-to-istanbul": "^9.
|
55
|
+
"v8-to-istanbul": "^9.2.0"
|
55
56
|
},
|
56
57
|
"devDependencies": {
|
58
|
+
"@types/debug": "^4.1.12",
|
57
59
|
"@types/istanbul-lib-coverage": "^2.0.6",
|
58
60
|
"@types/istanbul-lib-report": "^3.0.3",
|
59
61
|
"@types/istanbul-lib-source-maps": "^4.0.4",
|
60
62
|
"@types/istanbul-reports": "^3.0.4",
|
61
63
|
"pathe": "^1.1.1",
|
62
|
-
"vite-node": "1.0.0
|
63
|
-
"vitest": "1.0.0
|
64
|
+
"vite-node": "1.0.0",
|
65
|
+
"vitest": "1.0.0"
|
64
66
|
},
|
65
67
|
"scripts": {
|
66
68
|
"build": "rimraf dist && rollup -c",
|