Skip to content

Commit

Permalink
fix(coverage): use workspace project based source maps
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Oct 20, 2023
1 parent 3974b83 commit 8a43ecf
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 38 deletions.
10 changes: 8 additions & 2 deletions packages/browser/src/client/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ export function createBrowserRunner(original: any, coverageModule: CoverageHandl
async onAfterRunFiles() {
await super.onAfterRun?.()
const coverage = await coverageModule?.takeCoverage?.()
if (coverage)
await rpc().onAfterSuiteRun({ coverage, transformMode: 'web' })

if (coverage) {
await rpc().onAfterSuiteRun({
coverage,
transformMode: 'web',
projectName: this.config.name,
})
}
}

onCollected(files: File[]): unknown {
Expand Down
30 changes: 21 additions & 9 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import _TestExclude from 'test-exclude'
import { COVERAGE_STORE_KEY } from './constants'

type Options = ResolvedCoverageOptions<'istanbul'>
type CoverageByTransformMode = Record<AfterSuiteRunMeta['transformMode'], CoverageMapData[]>
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT

interface TestExclude {
new(opts: {
Expand All @@ -31,6 +33,8 @@ interface TestExclude {
}
}

const DEFAULT_PROJECT = Symbol.for('default-project')

export class IstanbulCoverageProvider extends BaseCoverageProvider implements CoverageProvider {
name = 'istanbul'

Expand All @@ -45,7 +49,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
* 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: Record<AfterSuiteRunMeta['transformMode'], CoverageMapData[]> = { ssr: [], web: [] }
coverages = new Map<ProjectName, CoverageByTransformMode>()

initialize(ctx: Vitest) {
const config: CoverageIstanbulOptions = ctx.config.coverage
Expand Down Expand Up @@ -111,25 +115,34 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
* Note that adding new entries here and requiring on those without
* backwards compatibility is a breaking change.
*/
onAfterSuiteRun({ coverage, transformMode }: AfterSuiteRunMeta) {
onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta) {
if (transformMode !== 'web' && transformMode !== 'ssr')
throw new Error(`Invalid transform mode: ${transformMode}`)

this.coverages[transformMode].push(coverage as CoverageMapData)
let entry = this.coverages.get(projectName || DEFAULT_PROJECT)

if (!entry) {
entry = { web: [], ssr: [] }
this.coverages.set(projectName || DEFAULT_PROJECT, entry)
}

entry[transformMode].push(coverage as CoverageMapData)
}

async clean(clean = true) {
if (clean && existsSync(this.options.reportsDirectory))
await fs.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 })

this.coverages = { ssr: [], web: [] }
this.coverages = new Map()
}

async reportCoverage({ allTestsRun }: ReportContext = {}) {
const coverageMaps = await Promise.all([
mergeAndTransformCoverage(this.coverages.ssr),
mergeAndTransformCoverage(this.coverages.web),
])
const coverageMaps = await Promise.all(
Array.from(this.coverages.values()).map(coverages => [
mergeAndTransformCoverage(coverages.ssr),
mergeAndTransformCoverage(coverages.web),
]).flat(),
)

if (this.options.all && allTestsRun) {
const coveredFiles = coverageMaps.map(map => map.files()).flat()
Expand All @@ -143,7 +156,6 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
const context = libReport.createContext({
dir: this.options.reportsDirectory,
coverageMap,
sourceFinder: libSourceMaps.createSourceMapStore().sourceFinder,
watermarks: this.options.watermarks,
})

Expand Down
53 changes: 30 additions & 23 deletions packages/coverage-v8/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,23 @@ interface TestExclude {
type Options = ResolvedCoverageOptions<'v8'>
type TransformResults = Map<string, FetchResult>
type RawCoverage = Profiler.TakePreciseCoverageReturnType
type CoverageByTransformMode = Record<AfterSuiteRunMeta['transformMode'], RawCoverage[]>
type ProjectName = NonNullable<AfterSuiteRunMeta['projectName']> | typeof DEFAULT_PROJECT

// TODO: vite-node should export this
const WRAPPER_LENGTH = 185

// Note that this needs to match the line ending as well
const VITE_EXPORTS_LINE_PATTERN = /Object\.defineProperty\(__vite_ssr_exports__.*\n/g
const DEFAULT_PROJECT = Symbol.for('default-project')

export class V8CoverageProvider extends BaseCoverageProvider implements CoverageProvider {
name = 'v8'

ctx!: Vitest
options!: Options
testExclude!: InstanceType<TestExclude>
coverages: Record<AfterSuiteRunMeta['transformMode'], RawCoverage[]> = { ssr: [], web: [] }
coverages = new Map<ProjectName, CoverageByTransformMode>()

initialize(ctx: Vitest) {
const config: CoverageV8Options = ctx.config.coverage
Expand Down Expand Up @@ -93,29 +96,38 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
if (clean && existsSync(this.options.reportsDirectory))
await fs.rm(this.options.reportsDirectory, { recursive: true, force: true, maxRetries: 10 })

this.coverages = { ssr: [], web: [] }
this.coverages = new Map()
}

/*
* Coverage and meta information passed from Vitest runners.
* Note that adding new entries here and requiring on those without
* backwards compatibility is a breaking change.
*/
onAfterSuiteRun({ coverage, transformMode }: AfterSuiteRunMeta) {
onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta) {
if (transformMode !== 'web' && transformMode !== 'ssr')
throw new Error(`Invalid transform mode: ${transformMode}`)

this.coverages[transformMode].push(coverage as RawCoverage)
let entry = this.coverages.get(projectName || DEFAULT_PROJECT)

if (!entry) {
entry = { web: [], ssr: [] }
this.coverages.set(projectName || DEFAULT_PROJECT, entry)
}

entry[transformMode].push(coverage as RawCoverage)
}

async reportCoverage({ allTestsRun }: ReportContext = {}) {
if (provider === 'stackblitz')
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([
this.mergeAndTransformCoverage(this.coverages.ssr, 'ssr'),
this.mergeAndTransformCoverage(this.coverages.web, 'web'),
])
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(),
)

if (this.options.all && allTestsRun) {
const coveredFiles = coverageMaps.map(map => map.files()).flat()
Expand All @@ -130,7 +142,6 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
const context = libReport.createContext({
dir: this.options.reportsDirectory,
coverageMap,
sourceFinder: libSourceMaps.createSourceMapStore().sourceFinder,
watermarks: this.options.watermarks,
})

Expand Down Expand Up @@ -177,7 +188,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
}

private async getUntestedFiles(testedFiles: string[]): Promise<Profiler.ScriptCoverage[]> {
const transformResults = normalizeTransformResults([this.ctx.vitenode.fetchCache])
const transformResults = normalizeTransformResults(this.ctx.vitenode.fetchCache)

const includedFiles = await this.testExclude.glob(this.ctx.config.root)
const uncoveredFiles = includedFiles
Expand Down Expand Up @@ -241,12 +252,10 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
}
}

private async mergeAndTransformCoverage(coverages: RawCoverage[], transformMode?: 'web' | 'ssr') {
const fetchCaches = transformMode
? this.ctx.projects.map(project => project.vitenode.fetchCaches[transformMode])
: [this.ctx.vitenode.fetchCache]

const transformResults = normalizeTransformResults(fetchCaches)
private async mergeAndTransformCoverage(coverages: RawCoverage[], projectName?: ProjectName, transformMode?: 'web' | 'ssr') {
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)))
Expand Down Expand Up @@ -314,16 +323,14 @@ function findLongestFunctionLength(functions: Profiler.FunctionCoverage[]) {
}, 0)
}

function normalizeTransformResults(fetchCaches: Map<string, { result: FetchResult }>[]) {
function normalizeTransformResults(fetchCache: Map<string, { result: FetchResult }>) {
const normalized: TransformResults = new Map()

for (const fetchCache of fetchCaches) {
for (const [key, value] of fetchCache.entries()) {
const cleanEntry = cleanUrl(key)
for (const [key, value] of fetchCache.entries()) {
const cleanEntry = cleanUrl(key)

if (!normalized.has(cleanEntry))
normalized.set(cleanEntry, value.result)
}
if (!normalized.has(cleanEntry))
normalized.set(cleanEntry, value.result)
}

return normalized
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/pools/child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath }
invalidates,
environment,
workerId,
projectName: project.getName(),
}
try {
await pool.run(data, { name, channel })
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/pools/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function createThreadsPool(ctx: Vitest, { execArgv, env, workerPath }: Po
invalidates,
environment,
workerId,
projectName: project.getName(),
}
try {
await pool.run(data, { transferList: [workerPort], name })
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/pools/vm-threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env, vmPath }: Pool
invalidates,
environment,
workerId,
projectName: project.getName(),
}
try {
await pool.run(data, { transferList: [workerPort], name })
Expand Down
7 changes: 6 additions & 1 deletion packages/vitest/src/runtime/runners/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ export async function resolveTestRunner(config: ResolvedConfig, executor: Vitest
testRunner.onAfterRunFiles = async (files) => {
const state = getWorkerState()
const coverage = await takeCoverageInsideWorker(config.coverage, executor)
rpc().onAfterSuiteRun({ coverage, transformMode: state.environment.transformMode })
rpc().onAfterSuiteRun({
coverage,
transformMode: state.environment.transformMode,
projectName: state.ctx.projectName,
})

await originalOnAfterRun?.call(testRunner, files)
}

Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/types/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ResolvedTestEnvironment {

export interface ContextRPC {
config: ResolvedConfig
projectName: string
files: string[]
invalidates?: string[]
environment: ContextTestEnvironment
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/types/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ResolveIdFunction = (id: string, importer?: string) => Promise<ViteN
export interface AfterSuiteRunMeta {
coverage?: unknown
transformMode: Environment['transformMode']
projectName?: string
}

export type WorkerRPC = BirpcReturn<RuntimeRPC, RunnerRPC>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ exports[`custom json report 1`] = `
"reportCoverage with {"allTestsRun":true}",
],
"coverageReports": [
"{"coverage":{"customCoverage":"Coverage report passed from workers to main thread"},"transformMode":"ssr"}",
"{"coverage":{"customCoverage":"Coverage report passed from workers to main thread"},"transformMode":"web"}",
"{"coverage":{"customCoverage":"Coverage report passed from workers to main thread"},"transformMode":"ssr","projectName":true}",
"{"coverage":{"customCoverage":"Coverage report passed from workers to main thread"},"transformMode":"web","projectName":true}",
],
"transformedFiles": [
"<process-cwd>/src/Counter/Counter.component.ts",
Expand Down
7 changes: 6 additions & 1 deletion test/coverage-test/custom-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ class CustomCoverageProvider implements CoverageProvider {
this.calls.add('onAfterSuiteRun')

// Keep coverage info separate from calls and ignore its order
this.coverageReports.add(JSON.stringify(meta))
this.coverageReports.add(JSON.stringify({
...meta,

// Project name keeps changing so let's simply check that its present
projectName: meta.projectName && typeof meta.projectName === 'string',
}))
}

reportCoverage(reportContext?: ReportContext) {
Expand Down

0 comments on commit 8a43ecf

Please sign in to comment.