Skip to content

Commit bcb3ba8

Browse files
committed
feat: --merge-reports to support coverage
1 parent 1ec61ce commit bcb3ba8

File tree

4 files changed

+61
-9
lines changed

4 files changed

+61
-9
lines changed

packages/coverage-v8/src/provider.ts

+43-5
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
193193
coverageMap.merge(await transformCoverage(converted))
194194
}
195195

196+
await this.generateReports(coverageMap, allTestsRun)
197+
198+
// In watch mode we need to preserve the previous results if cleanOnRerun is disabled
199+
const keepResults = !this.options.cleanOnRerun && this.ctx.config.watch
200+
201+
if (!keepResults) {
202+
this.coverageFiles = new Map()
203+
await fs.rm(this.coverageFilesDirectory, { recursive: true })
204+
}
205+
}
206+
207+
async generateReports(coverageMap: CoverageMap, allTestsRun: boolean | undefined) {
196208
const context = libReport.createContext({
197209
dir: this.options.reportsDirectory,
198210
coverageMap,
@@ -203,6 +215,11 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
203215
this.ctx.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.name))
204216

205217
for (const reporter of this.options.reporter) {
218+
if (reporter[0] === 'blob') {
219+
await this.createBlobReport(coverageMap, reporter[1])
220+
continue
221+
}
222+
206223
// Type assertion required for custom reporters
207224
reports.create(reporter[0] as Parameters<typeof reports.create>[0], {
208225
skipFull: this.options.skipFull,
@@ -239,14 +256,35 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
239256
})
240257
}
241258
}
259+
}
260+
261+
async mergeReports(path: string) {
262+
const directory = resolve(path, 'coverage')
263+
const files = await fs.readdir(directory)
264+
const coverageMap = libCoverage.createCoverageMap({})
265+
266+
for (const file of files) {
267+
const report = await fs.readFile(resolve(directory, file), 'utf8')
268+
coverageMap.merge(JSON.parse(report))
269+
}
242270

243-
// In watch mode we need to preserve the previous results if cleanOnRerun is disabled
244-
const keepResults = !this.options.cleanOnRerun && this.ctx.config.watch
271+
await this.generateReports(coverageMap, true)
272+
}
245273

246-
if (!keepResults) {
247-
this.coverageFiles = new Map()
248-
await fs.rm(this.coverageFilesDirectory, { recursive: true })
274+
async createBlobReport(coverageMap: CoverageMap, options: Options['reporter'][number][1]) {
275+
let file = 'file' in options && options.file as string
276+
277+
if (!file) {
278+
const shard = this.ctx.config.shard
279+
file = shard
280+
? `.coverage-blob-${shard.index}-${shard.count}.json`
281+
: '.coverage-blob.json'
249282
}
283+
284+
reports.create('json', { file }).execute(libReport.createContext({
285+
dir: this.ctx.config.coverage.reportsDirectory || '.vitest-reports/coverage/',
286+
coverageMap,
287+
}))
250288
}
251289

252290
private async getUntestedFiles(testedFiles: string[]): Promise<RawCoverage> {

packages/vitest/src/node/core.ts

+2
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,8 @@ export class Vitest {
439439
process.exitCode = 1
440440

441441
await this.report('onFinished', files, errors)
442+
await this.initCoverageProvider()
443+
await this.coverageProvider?.mergeReports?.(this.config.mergeReports)
442444
}
443445

444446
async start(filters?: string[]) {

packages/vitest/src/node/reporters/blob.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'
1+
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
22
import { existsSync } from 'node:fs'
33
import { parse, stringify } from 'flatted'
44
import { dirname, resolve } from 'pathe'
@@ -61,11 +61,17 @@ export async function readBlobs(blobsDirectory: string, projectsArray: Workspace
6161
const resolvedDir = resolve(process.cwd(), blobsDirectory)
6262
const blobsFiles = await readdir(resolvedDir)
6363
const promises = blobsFiles.map(async (file) => {
64-
const content = await readFile(resolve(resolvedDir, file), 'utf-8')
64+
const filename = resolve(resolvedDir, file)
65+
const stats = await stat(filename)
66+
if (!stats.isFile())
67+
return null
68+
69+
const content = await readFile(filename, 'utf-8')
6570
const [version, files, errors, moduleKeys] = parse(content) as MergeReport
6671
return { version, files, errors, moduleKeys }
6772
})
68-
const blobs = await Promise.all(promises)
73+
const results = await Promise.all(promises)
74+
const blobs = results.filter((result): result is NonNullable<typeof result> => result != null)
6975

7076
if (!blobs.length)
7177
throw new Error(`vitest.mergeReports() requires at least one blob file paths in the config`)

packages/vitest/src/types/coverage.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TransformResult as ViteTransformResult } from 'vite'
2-
import type { ReportOptions } from 'istanbul-reports'
2+
import type { ReportOptions as IstanbulReportOptions } from 'istanbul-reports'
33
import type { Vitest } from '../node'
44
import type { Arrayable } from './general'
55
import type { AfterSuiteRunMeta } from './worker'
@@ -17,6 +17,8 @@ export interface CoverageProvider {
1717

1818
reportCoverage: (reportContext?: ReportContext) => void | Promise<void>
1919

20+
mergeReports?: (path: string) => void | Promise<void>
21+
2022
onFileTransform?: (
2123
sourceCode: string,
2224
id: string,
@@ -52,6 +54,10 @@ export interface CoverageProviderModule {
5254
stopCoverage?: () => unknown | Promise<unknown>
5355
}
5456

57+
interface ReportOptions extends IstanbulReportOptions {
58+
blob: { file?: string }
59+
}
60+
5561
export type CoverageReporter = keyof ReportOptions | (string & {})
5662

5763
type CoverageReporterWithOptions<ReporterName extends CoverageReporter = CoverageReporter> =

0 commit comments

Comments
 (0)