forked from vitest-dev/vitest
-
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.
refactor: create interface for coverage logic
- Enables vitest-dev#1252
- Loading branch information
1 parent
a9dbbfc
commit 480666f
Showing
9 changed files
with
167 additions
and
118 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
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,13 @@ | ||
import { Vitest } from '../../node' | ||
import { ResolvedCoverageOptions } from '../../types'; | ||
|
||
export interface BaseCoverageReporter { | ||
resolveOptions(): ResolvedCoverageOptions | ||
|
||
// TODO: Maybe this could be just a constructor? | ||
initialize(ctx: Vitest): Promise<void> | void; | ||
|
||
clean(clean?: boolean): Promise<void> | void; | ||
|
||
report(): Promise<void> | void; | ||
} |
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 |
---|---|---|
@@ -1,115 +1,132 @@ | ||
import { existsSync, promises as fs } from 'fs' | ||
import { createRequire } from 'module' | ||
import { pathToFileURL } from 'url' | ||
import type { Profiler } from 'inspector' | ||
import { resolve } from 'pathe' | ||
import type { Profiler } from 'inspector' | ||
import type { RawSourceMap } from 'vite-node' | ||
import type { Vitest } from '../../node' | ||
|
||
import { toArray } from '../../utils' | ||
import type { C8Options, ResolvedC8Options } from '../../types' | ||
import { configDefaults } from '../../defaults' | ||
|
||
export function resolveC8Options(options: C8Options, root: string): ResolvedC8Options { | ||
const resolved: ResolvedC8Options = { | ||
...configDefaults.coverage, | ||
...options as any, | ||
} | ||
|
||
resolved.reporter = toArray(resolved.reporter) | ||
resolved.reportsDirectory = resolve(root, resolved.reportsDirectory) | ||
resolved.tempDirectory = process.env.NODE_V8_COVERAGE || resolve(resolved.reportsDirectory, 'tmp') | ||
|
||
return resolved as ResolvedC8Options | ||
} | ||
|
||
export async function cleanCoverage(options: ResolvedC8Options, clean = true) { | ||
if (clean && existsSync(options.reportsDirectory)) | ||
await fs.rm(options.reportsDirectory, { recursive: true, force: true }) | ||
|
||
if (!existsSync(options.tempDirectory)) | ||
await fs.mkdir(options.tempDirectory, { recursive: true }) | ||
} | ||
import type { BaseCoverageReporter } from './base' | ||
import type { C8Options, ResolvedCoverageOptions } from '../../types' | ||
import type { Vitest } from '../../node' | ||
|
||
const require = createRequire(import.meta.url) | ||
|
||
// Flush coverage to disk | ||
export function takeCoverage() { | ||
const v8 = require('v8') | ||
if (v8.takeCoverage == null) | ||
console.warn('[Vitest] takeCoverage is not available in this NodeJs version.\nCoverage could be incomplete. Update to NodeJs 14.18.') | ||
else | ||
v8.takeCoverage() | ||
} | ||
export class C8Reporter implements BaseCoverageReporter { | ||
ctx!: Vitest; | ||
options!: ResolvedCoverageOptions & { provider: "c8" } | ||
|
||
export async function reportCoverage(ctx: Vitest) { | ||
takeCoverage() | ||
initialize(ctx: Vitest) { | ||
this.ctx = ctx; | ||
this.options = resolveC8Options(ctx.config.coverage, ctx.config.root) | ||
} | ||
|
||
const createReport = require('c8/lib/report') | ||
const report = createReport(ctx.config.coverage) | ||
resolveOptions() { | ||
return this.options; | ||
} | ||
|
||
// add source maps | ||
const sourceMapMeta: Record<string, { map: RawSourceMap; source: string | undefined }> = {} | ||
await Promise.all(Array | ||
.from(ctx.vitenode.fetchCache.entries()) | ||
.filter(i => !i[0].includes('/node_modules/')) | ||
.map(async ([file, { result }]) => { | ||
const map = result.map | ||
if (!map) | ||
return | ||
async clean(clean = true) { | ||
if (clean && existsSync(this.options.reportsDirectory)) | ||
await fs.rm(this.options.reportsDirectory, { recursive: true, force: true }) | ||
|
||
const url = pathToFileURL(file).href | ||
if (!existsSync(this.options.tempDirectory)) | ||
await fs.mkdir(this.options.tempDirectory, { recursive: true }) | ||
} | ||
|
||
let code: string | undefined | ||
try { | ||
code = (await fs.readFile(file)).toString() | ||
} | ||
catch {} | ||
|
||
// Vite does not report full path in sourcemap sources | ||
// so use an actual file path | ||
const sources = [url] | ||
|
||
sourceMapMeta[url] = { | ||
source: result.code, | ||
map: { | ||
sourcesContent: code ? [code] : undefined, | ||
...map, | ||
sources, | ||
async report() { | ||
takeCoverage() | ||
|
||
const createReport = require('c8/lib/report') | ||
const report = createReport(this.ctx.config.coverage) | ||
|
||
// add source maps | ||
const sourceMapMeta: Record<string, { map: RawSourceMap; source: string | undefined }> = {} | ||
await Promise.all(Array | ||
.from(this.ctx.vitenode.fetchCache.entries()) | ||
.filter(i => !i[0].includes('/node_modules/')) | ||
.map(async ([file, { result }]) => { | ||
const map = result.map | ||
if (!map) | ||
return | ||
|
||
const url = pathToFileURL(file).href | ||
|
||
let code: string | undefined | ||
try { | ||
code = (await fs.readFile(file)).toString() | ||
} | ||
catch {} | ||
|
||
// Vite does not report full path in sourcemap sources | ||
// so use an actual file path | ||
const sources = [url] | ||
|
||
sourceMapMeta[url] = { | ||
source: result.code, | ||
map: { | ||
sourcesContent: code ? [code] : undefined, | ||
...map, | ||
sources, | ||
}, | ||
} | ||
})) | ||
|
||
// This is a magic number. It corresponds to the amount of code | ||
// that we add in packages/vite-node/src/client.ts:114 (vm.runInThisContext) | ||
// TODO: Include our transformations in sourcemaps | ||
const offset = 224 | ||
|
||
report._getSourceMap = (coverage: Profiler.ScriptCoverage) => { | ||
const path = pathToFileURL(coverage.url).href | ||
const data = sourceMapMeta[path] | ||
|
||
if (!data) | ||
return {} | ||
|
||
return { | ||
sourceMap: { | ||
sourcemap: data.map, | ||
}, | ||
source: Array(offset).fill('.').join('') + data.source, | ||
} | ||
})) | ||
|
||
// This is a magic number. It corresponds to the amount of code | ||
// that we add in packages/vite-node/src/client.ts:114 (vm.runInThisContext) | ||
// TODO: Include our transformations in sourcemaps | ||
const offset = 224 | ||
} | ||
|
||
report._getSourceMap = (coverage: Profiler.ScriptCoverage) => { | ||
const path = pathToFileURL(coverage.url).href | ||
const data = sourceMapMeta[path] | ||
await report.run() | ||
|
||
if (!data) | ||
return {} | ||
if (this.ctx.config.coverage.enabled && this.ctx.config.coverage.provider === 'c8') { | ||
if (this.ctx.config.coverage['100']) { | ||
this.ctx.config.coverage.lines = 100 | ||
this.ctx.config.coverage.functions = 100 | ||
this.ctx.config.coverage.branches = 100 | ||
this.ctx.config.coverage.statements = 100 | ||
} | ||
|
||
return { | ||
sourceMap: { | ||
sourcemap: data.map, | ||
}, | ||
source: Array(offset).fill('.').join('') + data.source, | ||
const { checkCoverages } = require('c8/lib/commands/check-coverage') | ||
await checkCoverages(this.ctx.config.coverage, report) | ||
} | ||
|
||
} | ||
} | ||
|
||
await report.run() | ||
function resolveC8Options(options: C8Options, root: string) { | ||
const resolved = { | ||
...configDefaults.coverage, | ||
...options as any, | ||
} | ||
|
||
if (ctx.config.coverage.enabled) { | ||
if (ctx.config.coverage['100']) { | ||
ctx.config.coverage.lines = 100 | ||
ctx.config.coverage.functions = 100 | ||
ctx.config.coverage.branches = 100 | ||
ctx.config.coverage.statements = 100 | ||
} | ||
resolved.reporter = toArray(resolved.reporter) | ||
resolved.reportsDirectory = resolve(root, resolved.reportsDirectory) | ||
resolved.tempDirectory = process.env.NODE_V8_COVERAGE || resolve(resolved.reportsDirectory, 'tmp') | ||
|
||
const { checkCoverages } = require('c8/lib/commands/check-coverage') | ||
await checkCoverages(ctx.config.coverage, report) | ||
} | ||
return resolved | ||
} | ||
|
||
// Flush coverage to disk | ||
function takeCoverage() { | ||
const v8 = require('v8') | ||
if (v8.takeCoverage == null) | ||
console.warn('[Vitest] takeCoverage is not available in this NodeJs version.\nCoverage could be incomplete. Update to NodeJs 14.18.') | ||
else | ||
v8.takeCoverage() | ||
} |
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
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
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
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
Oops, something went wrong.