-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ed42879
commit f9cd8d3
Showing
4 changed files
with
1,335 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,5 +12,11 @@ | |
"typescript": "^5.2.2", | ||
"vite": "^5.0.2", | ||
"vitest": "1.0.0-beta.5" | ||
}, | ||
"pnpm": { | ||
"patchedDependencies": { | ||
"[email protected]": "patches/[email protected]", | ||
"@vitest/[email protected]": "patches/@[email protected]" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
diff --git a/dist/provider.d.ts b/dist/provider.d.ts | ||
index fe3836e882d3b12bfbd270af3a28bc541c52da12..1f8268d03ff0e6104c0b05b3e071fbdc74623870 100644 | ||
--- a/dist/provider.d.ts | ||
+++ b/dist/provider.d.ts | ||
@@ -1,10 +1,11 @@ | ||
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; | ||
interface TestExclude { | ||
new (opts: { | ||
@@ -26,13 +27,9 @@ declare class IstanbulCoverageProvider extends BaseCoverageProvider implements C | ||
options: Options; | ||
instrumenter: Instrumenter; | ||
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; | ||
resolveOptions(): Options; | ||
onFileTransform(sourceCode: string, id: string, pluginCtx: any): { | ||
@@ -42,7 +39,7 @@ declare class IstanbulCoverageProvider extends BaseCoverageProvider implements C | ||
onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta): void; | ||
clean(clean?: boolean): Promise<void>; | ||
reportCoverage({ allTestsRun }?: ReportContext): Promise<void>; | ||
- getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMapData>; | ||
+ getCoverageMapForUncoveredFiles(coveredFiles: string[]): Promise<CoverageMap>; | ||
} | ||
|
||
export { IstanbulCoverageProvider }; | ||
diff --git a/dist/provider.js b/dist/provider.js | ||
index 0477bcd634d57857e8df1a9303aebf2a6f6f2469..7a5ea928ebe4d67d6f1475b9934cee4358864863 100644 | ||
--- a/dist/provider.js | ||
+++ b/dist/provider.js | ||
@@ -1,8 +1,9 @@ | ||
-import { existsSync, promises, writeFileSync } from 'node:fs'; | ||
+import { promises, existsSync, writeFileSync } from 'node:fs'; | ||
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'; | ||
import { BaseCoverageProvider } from 'vitest/coverage'; | ||
import c from 'picocolors'; | ||
import { parseModule } from 'magicast'; | ||
+import createDebug from 'debug'; | ||
import libReport from 'istanbul-lib-report'; | ||
import reports from 'istanbul-reports'; | ||
import libCoverage from 'istanbul-lib-coverage'; | ||
@@ -106,19 +107,17 @@ const isAbsolute = function(p) { | ||
}; | ||
|
||
const DEFAULT_PROJECT = Symbol.for("default-project"); | ||
+const debug = createDebug("vitest:coverage"); | ||
+let uniqueId = 0; | ||
class IstanbulCoverageProvider extends BaseCoverageProvider { | ||
name = "istanbul"; | ||
ctx; | ||
options; | ||
instrumenter; | ||
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) { | ||
const config = ctx.config.coverage; | ||
this.ctx = ctx; | ||
@@ -157,6 +156,7 @@ class IstanbulCoverageProvider extends BaseCoverageProvider { | ||
extension: this.options.extension, | ||
relativePath: !this.options.allowExternal | ||
}); | ||
+ this.coverageFilesDirectory = resolve(this.options.reportsDirectory, ".tmp"); | ||
} | ||
resolveOptions() { | ||
return this.options; | ||
@@ -176,33 +176,58 @@ class IstanbulCoverageProvider extends BaseCoverageProvider { | ||
* backwards compatibility is a breaking change. | ||
*/ | ||
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); | ||
} | ||
async clean(clean = true) { | ||
if (clean && existsSync(this.options.reportsDirectory)) | ||
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({ | ||
dir: this.options.reportsDirectory, | ||
coverageMap, | ||
@@ -242,43 +267,28 @@ class IstanbulCoverageProvider extends BaseCoverageProvider { | ||
}); | ||
} | ||
} | ||
+ await promises.rm(this.coverageFilesDirectory, { recursive: true }); | ||
+ this.coverageFiles = /* @__PURE__ */ new Map(); | ||
} | ||
async getCoverageMapForUncoveredFiles(coveredFiles) { | ||
const includedFiles = await this.testExclude.glob(this.ctx.config.root); | ||
const uncoveredFiles = includedFiles.map((file) => resolve(this.ctx.config.root, file)).filter((file) => !coveredFiles.includes(file)); | ||
- const transformResults = await Promise.all(uncoveredFiles.map(async (filename) => { | ||
- const transformResult = await this.ctx.vitenode.transformRequest(filename); | ||
- return { transformResult, filename }; | ||
- })); | ||
const coverageMap = libCoverage.createCoverageMap({}); | ||
- for (const { transformResult, filename } of transformResults) { | ||
- const sourceMap = transformResult?.map; | ||
- if (sourceMap) { | ||
- this.instrumenter.instrumentSync( | ||
- transformResult.code, | ||
- filename, | ||
- sourceMap | ||
- ); | ||
- const lastCoverage = this.instrumenter.lastFileCoverage(); | ||
- if (lastCoverage) | ||
- coverageMap.addFileCoverage(lastCoverage); | ||
- } | ||
+ 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); | ||
+ const lastCoverage = this.instrumenter.lastFileCoverage(); | ||
+ coverageMap.addFileCoverage(lastCoverage); | ||
} | ||
- 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); | ||
-} | ||
-function mergeCoverageMaps(...coverageMaps) { | ||
- return coverageMaps.reduce((coverage, previousCoverageMap) => { | ||
- const map = libCoverage.createCoverageMap(coverage); | ||
- map.merge(previousCoverageMap); | ||
- return map; | ||
- }, libCoverage.createCoverageMap({})); | ||
+ return await sourceMapStore.transformCoverage(coverageMap); | ||
} | ||
function removeQueryParameters(filename) { | ||
return filename.split("?")[0]; | ||
@@ -307,5 +317,17 @@ function isEmptyCoverageRange(range) { | ||
function hasTerminalReporter(reporters) { | ||
return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity"); | ||
} | ||
+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 }; | ||
diff --git a/package.json b/package.json | ||
index ade70d1005555d6badc43e3ed2c723410bacd589..7a3bc75d3ff12ec12853552ff194ee3a54d3ce3b 100644 | ||
--- a/package.json | ||
+++ b/package.json | ||
@@ -36,10 +36,16 @@ | ||
"files": [ | ||
"dist" | ||
], | ||
+ "scripts": { | ||
+ "build": "rimraf dist && rollup -c", | ||
+ "dev": "rollup -c --watch --watch.include 'src/**'", | ||
+ "prepublishOnly": "pnpm build" | ||
+ }, | ||
"peerDependencies": { | ||
"vitest": "^1.0.0-0" | ||
}, | ||
"dependencies": { | ||
+ "debug": "^4.3.4", | ||
"istanbul-lib-coverage": "^3.2.2", | ||
"istanbul-lib-instrument": "^6.0.1", | ||
"istanbul-lib-report": "^3.0.1", | ||
@@ -50,16 +56,13 @@ | ||
"test-exclude": "^6.0.0" | ||
}, | ||
"devDependencies": { | ||
+ "@types/debug": "^4.1.12", | ||
"@types/istanbul-lib-coverage": "^2.0.6", | ||
"@types/istanbul-lib-instrument": "^1.7.7", | ||
"@types/istanbul-lib-report": "^3.0.3", | ||
"@types/istanbul-lib-source-maps": "^4.0.4", | ||
"@types/istanbul-reports": "^3.0.4", | ||
"pathe": "^1.1.1", | ||
- "vitest": "1.0.0-beta.5" | ||
- }, | ||
- "scripts": { | ||
- "build": "rimraf dist && rollup -c", | ||
- "dev": "rollup -c --watch --watch.include 'src/**'" | ||
+ "vitest": "workspace:*" | ||
} | ||
} |
Oops, something went wrong.