Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Nov 19, 2023
1 parent e61b302 commit 7ba562a
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 104 deletions.
12 changes: 9 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": false
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},

// Silent the stylistic rules in you IDE, but still auto fix them
Expand Down Expand Up @@ -42,5 +42,11 @@
"json",
"jsonc",
"yaml"
]
],
"[javascript][typescript][yaml]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
}
}
}
103 changes: 61 additions & 42 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import c from 'picocolors'
import { parseModule } from 'magicast'
import libReport from 'istanbul-lib-report'
import reports from 'istanbul-reports'
import type { CoverageMap, CoverageMapData } from 'istanbul-lib-coverage'
import type { CoverageMap } from 'istanbul-lib-coverage'
import libCoverage from 'istanbul-lib-coverage'
import libSourceMaps from 'istanbul-lib-source-maps'
import { type Instrumenter, createInstrumenter } from 'istanbul-lib-instrument'
Expand All @@ -17,7 +17,8 @@ import _TestExclude from 'test-exclude'
import { COVERAGE_STORE_KEY } from './constants'

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 {
Expand All @@ -44,13 +45,9 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
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 = new Map<ProjectName, CoverageByTransformMode>()
coverageFiles = new Map<ProjectName, CoverageFilesByTransformMode>()
coverageFilesDirectory!: string
pendingPromises: Promise<void>[] = []

initialize(ctx: Vitest) {
const config: CoverageIstanbulOptions = ctx.config.coverage
Expand Down Expand Up @@ -96,6 +93,8 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
extension: this.options.extension,
relativePath: !this.options.allowExternal,
})

this.coverageFilesDirectory = resolve(this.options.reportsDirectory, '.reports')
}

resolveOptions() {
Expand All @@ -121,43 +120,71 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
* backwards compatibility is a breaking change.
*/
onAfterSuiteRun({ coverage, transformMode, projectName }: AfterSuiteRunMeta) {
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 as CoverageMapData)
const filename = resolve(this.coverageFilesDirectory, `coverage-${transformMode}-${1 + entry[transformMode].length}.json`)
entry[transformMode].push(filename)

const promise = fs.writeFile(filename, JSON.stringify(coverage), 'utf-8')
this.pendingPromises.push(promise)
}

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

this.coverages = new Map()
if (existsSync(this.coverageFilesDirectory))
await fs.rm(this.coverageFilesDirectory, { recursive: true, force: true, maxRetries: 10 })

await fs.mkdir(this.coverageFilesDirectory, { recursive: true })

this.coverageFiles = new Map()
this.pendingPromises = []
}

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

const coverageMap = libCoverage.createCoverageMap({})

for (const coveragePerProject of this.coverageFiles.values()) {
for (const filenames of [coveragePerProject.ssr, coveragePerProject.web]) {
const coverageMapByTransformMode = libCoverage.createCoverageMap({})

// TODO: Some parallelization here
for (const filename of filenames) {
const contents = await fs.readFile(filename, 'utf-8')
const coverage = JSON.parse(contents) as CoverageMap

coverageMapByTransformMode.merge(coverage)
}

// Source maps can change based on projectName and transform mode.
// Coverage transform re-uses source maps so we need to separate transforms from each other.
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,
Expand Down Expand Up @@ -206,6 +233,10 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
})
}
}

await fs.rmdir(this.coverageFilesDirectory, { recursive: true })
this.coverageFiles = new Map()
this.pendingPromises = []
}

async getCoverageMapForUncoveredFiles(coveredFiles: string[]) {
Expand All @@ -216,14 +247,11 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
.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) {
// TODO: Some parallelization here
for (const filename of uncoveredFiles) {
const transformResult = await this.ctx.vitenode.transformRequest(filename)
const sourceMap = transformResult?.map

if (sourceMap) {
Expand All @@ -239,24 +267,15 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
}
}

return coverageMap.data
return coverageMap
}
}

async function mergeAndTransformCoverage(coverages: CoverageMapData[]) {
const mergedCoverage = mergeCoverageMaps(...coverages)
includeImplicitElseBranches(mergedCoverage)
async function transformCoverage(coverageMap: CoverageMap) {
includeImplicitElseBranches(coverageMap)

const sourceMapStore = libSourceMaps.createSourceMapStore()
return await sourceMapStore.transformCoverage(mergedCoverage)
}

function mergeCoverageMaps(...coverageMaps: (CoverageMap | CoverageMapData)[]) {
return coverageMaps.reduce<CoverageMap>((coverage, previousCoverageMap) => {
const map = libCoverage.createCoverageMap(coverage)
map.merge(previousCoverageMap)
return map
}, libCoverage.createCoverageMap({}))
return await sourceMapStore.transformCoverage(coverageMap)
}

/**
Expand Down
Loading

0 comments on commit 7ba562a

Please sign in to comment.