@vitest/coverage-istanbul
Advanced tools
Comparing version 1.0.0-beta.6 to 1.0.0
import { CoverageProvider, Vitest, AfterSuiteRunMeta, ReportContext, ResolvedCoverageOptions } from 'vitest'; | ||
import { BaseCoverageProvider } from 'vitest/coverage'; | ||
import { CoverageMapData } from 'istanbul-lib-coverage'; | ||
import { CoverageMap } from 'istanbul-lib-coverage'; | ||
import { Instrumenter } from 'istanbul-lib-instrument'; | ||
type Options = ResolvedCoverageOptions<'istanbul'>; | ||
type CoverageByTransformMode = Record<AfterSuiteRunMeta['transformMode'], CoverageMapData[]>; | ||
type Filename = string; | ||
type CoverageFilesByTransformMode = Record<AfterSuiteRunMeta['transformMode'], Filename[]>; | ||
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT; | ||
@@ -29,9 +30,5 @@ interface TestExclude { | ||
testExclude: InstanceType<TestExclude>; | ||
/** | ||
* Coverage objects collected from workers. | ||
* Some istanbul utilizers write these into file system instead of storing in memory. | ||
* If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun` | ||
* and read them back when merging coverage objects in `onAfterAllFilesRun`. | ||
*/ | ||
coverages: Map<ProjectName, CoverageByTransformMode>; | ||
coverageFiles: Map<ProjectName, CoverageFilesByTransformMode>; | ||
coverageFilesDirectory: string; | ||
pendingPromises: Promise<void>[]; | ||
initialize(ctx: Vitest): void; | ||
@@ -46,5 +43,5 @@ resolveOptions(): Options; | ||
reportCoverage({ allTestsRun }?: ReportContext): Promise<void>; | ||
getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMapData>; | ||
getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMap>; | ||
} | ||
export { IstanbulCoverageProvider }; |
@@ -1,2 +0,2 @@ | ||
import { existsSync, promises, writeFileSync } from 'node:fs'; | ||
import { promises, existsSync, writeFileSync } from 'node:fs'; | ||
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'; | ||
@@ -6,2 +6,3 @@ import { BaseCoverageProvider } from 'vitest/coverage'; | ||
import { parseModule } from 'magicast'; | ||
import createDebug from 'debug'; | ||
import libReport from 'istanbul-lib-report'; | ||
@@ -110,2 +111,4 @@ import reports from 'istanbul-reports'; | ||
const DEFAULT_PROJECT = Symbol.for("default-project"); | ||
const debug = createDebug("vitest:coverage"); | ||
let uniqueId = 0; | ||
class IstanbulCoverageProvider extends BaseCoverageProvider { | ||
@@ -117,9 +120,5 @@ name = "istanbul"; | ||
testExclude; | ||
/** | ||
* Coverage objects collected from workers. | ||
* Some istanbul utilizers write these into file system instead of storing in memory. | ||
* If storing in memory causes issues, we can simply write these into fs in `onAfterSuiteRun` | ||
* and read them back when merging coverage objects in `onAfterAllFilesRun`. | ||
*/ | ||
coverages = /* @__PURE__ */ new Map(); | ||
coverageFiles = /* @__PURE__ */ new Map(); | ||
coverageFilesDirectory; | ||
pendingPromises = []; | ||
initialize(ctx) { | ||
@@ -163,2 +162,3 @@ const config = ctx.config.coverage; | ||
}); | ||
this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp"); | ||
} | ||
@@ -183,10 +183,15 @@ resolveOptions() { | ||
onAfterSuiteRun({ coverage, transformMode, projectName }) { | ||
if (!coverage) | ||
return; | ||
if (transformMode !== "web" && transformMode !== "ssr") | ||
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); | ||
} | ||
@@ -196,17 +201,37 @@ async clean(clean = true) { | ||
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 = []; | ||
} | ||
async reportCoverage({ allTestsRun } = {}) { | ||
const coverageMaps = await Promise.all( | ||
Array.from(this.coverages.values()).map((coverages) => [ | ||
mergeAndTransformCoverage(coverages.ssr), | ||
mergeAndTransformCoverage(coverages.web) | ||
]).flat() | ||
); | ||
const coverageMap = libCoverage.createCoverageMap({}); | ||
let index = 0; | ||
const total = this.pendingPromises.length; | ||
await Promise.all(this.pendingPromises); | ||
this.pendingPromises = []; | ||
for (const coveragePerProject of this.coverageFiles.values()) { | ||
for (const filenames of [coveragePerProject.ssr, coveragePerProject.web]) { | ||
const coverageMapByTransformMode = libCoverage.createCoverageMap({}); | ||
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); | ||
coverageMapByTransformMode.merge(coverage); | ||
})); | ||
} | ||
const transformedCoverage = await transformCoverage(coverageMapByTransformMode); | ||
coverageMap.merge(transformedCoverage); | ||
} | ||
} | ||
if (this.options.all && allTestsRun) { | ||
const coveredFiles = coverageMaps.map((map) => map.files()).flat(); | ||
const coveredFiles = coverageMap.files(); | ||
const uncoveredCoverage = await this.getCoverageMapForUncoveredFiles(coveredFiles); | ||
coverageMaps.push(await mergeAndTransformCoverage([uncoveredCoverage])); | ||
coverageMap.merge(await transformCoverage(uncoveredCoverage)); | ||
} | ||
const coverageMap = mergeCoverageMaps(...coverageMaps); | ||
const context = libReport.createContext({ | ||
@@ -251,2 +276,4 @@ dir: this.options.reportsDirectory, | ||
} | ||
await promises.rm(this.coverageFilesDirectory, { recursive: true }); | ||
this.coverageFiles = /* @__PURE__ */ new Map(); | ||
} | ||
@@ -257,3 +284,6 @@ async getCoverageMapForUncoveredFiles(coveredFiles) { | ||
const coverageMap = libCoverage.createCoverageMap({}); | ||
for (const filename of uncoveredFiles) { | ||
for (const [index, filename] of uncoveredFiles.entries()) { | ||
debug("Uncovered file %s %d/%d", filename, index, uncoveredFiles.length); | ||
if (this.ctx.vitenode.fetchCache.has(filename)) | ||
this.ctx.vitenode.fetchCache.delete(filename); | ||
await this.ctx.vitenode.transformRequest(filename); | ||
@@ -263,18 +293,10 @@ const lastCoverage = this.instrumenter.lastFileCoverage(); | ||
} | ||
return coverageMap.data; | ||
return coverageMap; | ||
} | ||
} | ||
async function mergeAndTransformCoverage(coverages) { | ||
const mergedCoverage = mergeCoverageMaps(...coverages); | ||
includeImplicitElseBranches(mergedCoverage); | ||
async function transformCoverage(coverageMap) { | ||
includeImplicitElseBranches(coverageMap); | ||
const sourceMapStore = libSourceMaps.createSourceMapStore(); | ||
return await sourceMapStore.transformCoverage(mergedCoverage); | ||
return await sourceMapStore.transformCoverage(coverageMap); | ||
} | ||
function mergeCoverageMaps(...coverageMaps) { | ||
return coverageMaps.reduce((coverage, previousCoverageMap) => { | ||
const map = libCoverage.createCoverageMap(coverage); | ||
map.merge(previousCoverageMap); | ||
return map; | ||
}, libCoverage.createCoverageMap({})); | ||
} | ||
function removeQueryParameters(filename) { | ||
@@ -307,3 +329,15 @@ return filename.split("?")[0]; | ||
} | ||
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 { IstanbulCoverageProvider }; |
{ | ||
"name": "@vitest/coverage-istanbul", | ||
"type": "module", | ||
"version": "1.0.0-beta.6", | ||
"version": "1.0.0", | ||
"description": "Istanbul coverage provider for Vitest", | ||
@@ -43,2 +43,3 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
"dependencies": { | ||
"debug": "^4.3.4", | ||
"istanbul-lib-coverage": "^3.2.2", | ||
@@ -54,2 +55,3 @@ "istanbul-lib-instrument": "^6.0.1", | ||
"devDependencies": { | ||
"@types/debug": "^4.1.12", | ||
"@types/istanbul-lib-coverage": "^2.0.6", | ||
@@ -61,3 +63,3 @@ "@types/istanbul-lib-instrument": "^1.7.7", | ||
"pathe": "^1.1.1", | ||
"vitest": "1.0.0-beta.6" | ||
"vitest": "1.0.0" | ||
}, | ||
@@ -64,0 +66,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
18931
403
0
10
8
+ Addeddebug@^4.3.4