@vitest/coverage-v8
Advanced tools
Comparing version 1.0.0-beta.6 to 1.0.1
import { V8CoverageProvider } from './provider.js'; | ||
import 'node:inspector'; | ||
import 'vitest/coverage'; | ||
@@ -4,0 +3,0 @@ import 'vitest'; |
@@ -1,2 +0,1 @@ | ||
import { Profiler } from 'node:inspector'; | ||
import { BaseCoverageProvider } from 'vitest/coverage'; | ||
@@ -20,4 +19,4 @@ import { CoverageProvider, AfterSuiteRunMeta, ReportContext, ResolvedCoverageOptions } from 'vitest'; | ||
type Options = ResolvedCoverageOptions<'v8'>; | ||
type RawCoverage = Profiler.TakePreciseCoverageReturnType; | ||
type CoverageByTransformMode = Record<AfterSuiteRunMeta['transformMode'], RawCoverage[]>; | ||
type Filename = string; | ||
type CoverageFilesByTransformMode = Record<AfterSuiteRunMeta['transformMode'], Filename[]>; | ||
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT; | ||
@@ -30,3 +29,5 @@ declare const DEFAULT_PROJECT: unique symbol; | ||
testExclude: InstanceType<TestExclude>; | ||
coverages: Map<ProjectName, CoverageByTransformMode>; | ||
coverageFiles: Map<ProjectName, CoverageFilesByTransformMode>; | ||
coverageFilesDirectory: string; | ||
pendingPromises: Promise<void>[]; | ||
initialize(ctx: Vitest): void; | ||
@@ -39,5 +40,5 @@ resolveOptions(): Options; | ||
private getSources; | ||
private mergeAndTransformCoverage; | ||
private convertCoverage; | ||
} | ||
export { V8CoverageProvider }; |
@@ -14,2 +14,3 @@ import { existsSync, promises, writeFileSync } from 'node:fs'; | ||
import { provider } from 'std-env'; | ||
import createDebug from 'debug'; | ||
import { builtinModules } from 'node:module'; | ||
@@ -174,2 +175,4 @@ import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'; | ||
const DEFAULT_PROJECT = Symbol.for("default-project"); | ||
const debug = createDebug("vitest:coverage"); | ||
let uniqueId = 0; | ||
class V8CoverageProvider extends BaseCoverageProvider { | ||
@@ -180,3 +183,5 @@ name = "v8"; | ||
testExclude; | ||
coverages = /* @__PURE__ */ new Map(); | ||
coverageFiles = /* @__PURE__ */ new Map(); | ||
coverageFilesDirectory; | ||
pendingPromises = []; | ||
initialize(ctx) { | ||
@@ -209,2 +214,3 @@ const config = ctx.config.coverage; | ||
}); | ||
this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp"); | ||
} | ||
@@ -217,3 +223,7 @@ resolveOptions() { | ||
await promises.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 }); | ||
this.coverages = /* @__PURE__ */ new Map(); | ||
if (existsSync(this.coverageFilesDirectory)) | ||
await promises.rm(this.coverageFilesDirectory, { recursive: true, force: true, maxRetries: 10 }); | ||
await promises.mkdir(this.coverageFilesDirectory, { recursive: true }); | ||
this.coverageFiles = /* @__PURE__ */ new Map(); | ||
this.pendingPromises = []; | ||
} | ||
@@ -228,8 +238,11 @@ /* | ||
throw new Error(`Invalid transform mode: ${transformMode}`); | ||
let entry = this.coverages.get(projectName || DEFAULT_PROJECT); | ||
let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT); | ||
if (!entry) { | ||
entry = { web: [], ssr: [] }; | ||
this.coverages.set(projectName || DEFAULT_PROJECT, entry); | ||
this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry); | ||
} | ||
entry[transformMode].push(coverage); | ||
const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`); | ||
entry[transformMode].push(filename); | ||
const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8"); | ||
this.pendingPromises.push(promise); | ||
} | ||
@@ -239,15 +252,32 @@ async reportCoverage({ allTestsRun } = {}) { | ||
this.ctx.logger.log(c.blue(" % ") + c.yellow("@vitest/coverage-v8 does not work on Stackblitz. Report will be empty.")); | ||
const coverageMaps = await Promise.all( | ||
Array.from(this.coverages.entries()).map(([projectName, coverages]) => [ | ||
this.mergeAndTransformCoverage(coverages.ssr, projectName, "ssr"), | ||
this.mergeAndTransformCoverage(coverages.web, projectName, "web") | ||
]).flat() | ||
); | ||
const coverageMap = libCoverage.createCoverageMap({}); | ||
let index = 0; | ||
const total = this.pendingPromises.length; | ||
await Promise.all(this.pendingPromises); | ||
this.pendingPromises = []; | ||
for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) { | ||
for (const [transformMode, filenames] of Object.entries(coveragePerProject)) { | ||
let merged = { result: [] }; | ||
for (const chunk of toSlices(filenames, this.options.processingConcurrency)) { | ||
if (debug.enabled) { | ||
index += chunk.length; | ||
debug("Covered files %d/%d", index, total); | ||
} | ||
await Promise.all(chunk.map(async (filename) => { | ||
const contents = await promises.readFile(filename, "utf-8"); | ||
const coverage = JSON.parse(contents); | ||
merged = mergeProcessCovs([merged, coverage]); | ||
})); | ||
} | ||
const converted = await this.convertCoverage(merged, projectName, transformMode); | ||
const transformedCoverage = await transformCoverage(converted); | ||
coverageMap.merge(transformedCoverage); | ||
} | ||
} | ||
if (this.options.all && allTestsRun) { | ||
const coveredFiles = coverageMaps.map((map) => map.files()).flat(); | ||
const coveredFiles = coverageMap.files(); | ||
const untestedCoverage = await this.getUntestedFiles(coveredFiles); | ||
const untestedCoverageResults = untestedCoverage.map((files) => ({ result: [files] })); | ||
coverageMaps.push(await this.mergeAndTransformCoverage(untestedCoverageResults)); | ||
const converted = await this.convertCoverage(untestedCoverage); | ||
coverageMap.merge(await transformCoverage(converted)); | ||
} | ||
const coverageMap = mergeCoverageMaps(...coverageMaps); | ||
const context = libReport.createContext({ | ||
@@ -291,2 +321,4 @@ dir: this.options.reportsDirectory, | ||
} | ||
this.coverageFiles = /* @__PURE__ */ new Map(); | ||
await promises.rm(this.coverageFilesDirectory, { recursive: true }); | ||
} | ||
@@ -298,20 +330,31 @@ } | ||
const uncoveredFiles = includedFiles.map((file) => pathToFileURL(resolve(this.ctx.config.root, file))).filter((file) => !testedFiles.includes(file.pathname)); | ||
return await Promise.all(uncoveredFiles.map(async (uncoveredFile) => { | ||
const { source } = await this.getSources(uncoveredFile.href, transformResults); | ||
return { | ||
url: uncoveredFile.href, | ||
scriptId: "0", | ||
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps. | ||
functions: [{ | ||
ranges: [{ | ||
startOffset: 0, | ||
endOffset: source.length, | ||
count: 0 | ||
}], | ||
isBlockCoverage: true, | ||
// 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 | ||
functionName: "(empty-report)" | ||
}] | ||
}; | ||
})); | ||
let merged = { result: [] }; | ||
let index = 0; | ||
for (const chunk of toSlices(uncoveredFiles, this.options.processingConcurrency)) { | ||
if (debug.enabled) { | ||
index += chunk.length; | ||
debug("Uncovered files %d/%d", index, uncoveredFiles.length); | ||
} | ||
const coverages = await Promise.all(chunk.map(async (filename) => { | ||
const { source } = await this.getSources(filename.href, transformResults); | ||
const coverage = { | ||
url: filename.href, | ||
scriptId: "0", | ||
// Create a made up function to mark whole file as uncovered. Note that this does not exist in source maps. | ||
functions: [{ | ||
ranges: [{ | ||
startOffset: 0, | ||
endOffset: source.length, | ||
count: 0 | ||
}], | ||
isBlockCoverage: true, | ||
// 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 | ||
functionName: "(empty-report)" | ||
}] | ||
}; | ||
return { result: [coverage] }; | ||
})); | ||
merged = mergeProcessCovs([merged, ...coverages]); | ||
} | ||
return merged; | ||
} | ||
@@ -342,27 +385,29 @@ async getSources(url, transformResults, functions = []) { | ||
} | ||
async mergeAndTransformCoverage(coverages, projectName, transformMode) { | ||
async convertCoverage(coverage, projectName, transformMode) { | ||
const viteNode = this.ctx.projects.find((project) => project.getName() === projectName)?.vitenode || this.ctx.vitenode; | ||
const fetchCache = transformMode ? viteNode.fetchCaches[transformMode] : viteNode.fetchCache; | ||
const transformResults = normalizeTransformResults(fetchCache); | ||
const merged = mergeProcessCovs(coverages); | ||
const scriptCoverages = merged.result.filter((result) => this.testExclude.shouldInstrument(fileURLToPath(result.url))); | ||
const converted = await Promise.all(scriptCoverages.map(async ({ url, functions }) => { | ||
const sources = await this.getSources(url, transformResults, functions); | ||
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0; | ||
const converter = v8ToIstanbul(url, wrapperLength, sources); | ||
await converter.load(); | ||
converter.applyCoverage(functions); | ||
return converter.toIstanbul(); | ||
})); | ||
const mergedCoverage = mergeCoverageMaps(...converted); | ||
const sourceMapStore = libSourceMaps.createSourceMapStore(); | ||
return sourceMapStore.transformCoverage(mergedCoverage); | ||
const scriptCoverages = coverage.result.filter((result) => this.testExclude.shouldInstrument(fileURLToPath(result.url))); | ||
const coverageMap = libCoverage.createCoverageMap({}); | ||
let index = 0; | ||
for (const chunk of toSlices(scriptCoverages, this.options.processingConcurrency)) { | ||
if (debug.enabled) { | ||
index += chunk.length; | ||
debug("Converting %d/%d", index, scriptCoverages.length); | ||
} | ||
await Promise.all(chunk.map(async ({ url, functions }) => { | ||
const sources = await this.getSources(url, transformResults, functions); | ||
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0; | ||
const converter = v8ToIstanbul(url, wrapperLength, sources); | ||
await converter.load(); | ||
converter.applyCoverage(functions); | ||
coverageMap.merge(converter.toIstanbul()); | ||
})); | ||
} | ||
return coverageMap; | ||
} | ||
} | ||
function mergeCoverageMaps(...coverageMaps) { | ||
return coverageMaps.reduce((coverage, previousCoverageMap) => { | ||
const map = libCoverage.createCoverageMap(coverage); | ||
map.merge(previousCoverageMap); | ||
return map; | ||
}, libCoverage.createCoverageMap({})); | ||
async function transformCoverage(coverageMap) { | ||
const sourceMapStore = libSourceMaps.createSourceMapStore(); | ||
return await sourceMapStore.transformCoverage(coverageMap); | ||
} | ||
@@ -401,3 +446,15 @@ function removeViteHelpersFromSourceMaps(source, map) { | ||
} | ||
function toSlices(array, size) { | ||
return array.reduce((chunks, item) => { | ||
const index = Math.max(0, chunks.length - 1); | ||
const lastChunk = chunks[index] || []; | ||
chunks[index] = lastChunk; | ||
if (lastChunk.length >= size) | ||
chunks.push([item]); | ||
else | ||
lastChunk.push(item); | ||
return chunks; | ||
}, []); | ||
} | ||
export { V8CoverageProvider }; |
{ | ||
"name": "@vitest/coverage-v8", | ||
"type": "module", | ||
"version": "1.0.0-beta.6", | ||
"version": "1.0.1", | ||
"description": "V8 coverage provider for Vitest", | ||
@@ -40,3 +40,3 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
"peerDependencies": { | ||
"vitest": "^1.0.0-0" | ||
"vitest": "^1.0.0" | ||
}, | ||
@@ -46,2 +46,3 @@ "dependencies": { | ||
"@bcoe/v8-coverage": "^0.2.3", | ||
"debug": "^4.3.4", | ||
"istanbul-lib-coverage": "^3.2.2", | ||
@@ -59,2 +60,3 @@ "istanbul-lib-report": "^3.0.1", | ||
"devDependencies": { | ||
"@types/debug": "^4.1.12", | ||
"@types/istanbul-lib-coverage": "^2.0.6", | ||
@@ -65,4 +67,4 @@ "@types/istanbul-lib-report": "^3.0.3", | ||
"pathe": "^1.1.1", | ||
"vitest": "1.0.0-beta.6", | ||
"vite-node": "1.0.0-beta.6" | ||
"vite-node": "1.0.1", | ||
"vitest": "1.0.1" | ||
}, | ||
@@ -69,0 +71,0 @@ "scripts": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
23417
544
0
14
8
+ Addeddebug@^4.3.4