Skip to content

Commit

Permalink
refactor(coverage): move common parts to vitest package (#5236)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio authored Feb 20, 2024
1 parent 09c1c76 commit c692f76
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 120 deletions.
61 changes: 4 additions & 57 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, Repo
import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config'
import { BaseCoverageProvider } from 'vitest/coverage'
import c from 'picocolors'
import type { ProxifiedModule } from 'magicast'
import { parseModule } from 'magicast'
import createDebug from 'debug'
import libReport from 'istanbul-lib-report'
Expand Down Expand Up @@ -172,7 +171,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
for (const filenames of [coveragePerProject.ssr, coveragePerProject.web]) {
const coverageMapByTransformMode = libCoverage.createCoverageMap({})

for (const chunk of toSlices(filenames, this.options.processingConcurrency)) {
for (const chunk of this.toSlices(filenames, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length
debug('Covered files %d/%d', index, total)
Expand Down Expand Up @@ -206,7 +205,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
watermarks: this.options.watermarks,
})

if (hasTerminalReporter(this.options.reporter))
if (this.hasTerminalReporter(this.options.reporter))
this.ctx.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.name))

for (const reporter of this.options.reporter) {
Expand Down Expand Up @@ -240,10 +239,8 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
this.updateThresholds({
thresholds: resolvedThresholds,
perFile: this.options.thresholds.perFile,
configurationFile: {
write: () => writeFileSync(configFilePath, configModule.generate().code, 'utf-8'),
read: () => resolveConfig(configModule),
},
configurationFile: configModule,
onUpdate: () => writeFileSync(configFilePath, configModule.generate().code, 'utf-8'),
})
}
}
Expand Down Expand Up @@ -338,53 +335,3 @@ function isEmptyCoverageRange(range: libCoverage.Range) {
|| range.end.column === undefined
)
}

function hasTerminalReporter(reporters: Options['reporter']) {
return reporters.some(([reporter]) =>
reporter === 'text'
|| reporter === 'text-summary'
|| reporter === 'text-lcov'
|| reporter === 'teamcity')
}

function toSlices<T>(array: T[], size: number): T[][] {
return array.reduce<T[][]>((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
}, [])
}

function resolveConfig(configModule: ProxifiedModule<any>) {
const mod = configModule.exports.default

try {
// Check for "export default { test: {...} }"
if (mod.$type === 'object')
return mod

if (mod.$type === 'function-call') {
// "export default defineConfig({ test: {...} })"
if (mod.$args[0].$type === 'object')
return mod.$args[0]

// "export default defineConfig(() => ({ test: {...} }))"
if (mod.$args[0].$type === 'arrow-function-expression' && mod.$args[0].$body.$type === 'object')
return mod.$args[0].$body
}
}
catch (error) {
// Reduce magicast's verbose errors to readable ones
throw new Error(error instanceof Error ? error.message : String(error))
}

throw new Error('Failed to update coverage thresholds. Configuration file is too complex.')
}
65 changes: 6 additions & 59 deletions packages/coverage-v8/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { CoverageMap } from 'istanbul-lib-coverage'
import libCoverage from 'istanbul-lib-coverage'
import libSourceMaps from 'istanbul-lib-source-maps'
import MagicString from 'magic-string'
import type { ProxifiedModule } from 'magicast'
import { parseModule } from 'magicast'
import remapping from '@ampproject/remapping'
import { normalize, resolve } from 'pathe'
Expand Down Expand Up @@ -162,7 +161,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
for (const [transformMode, filenames] of Object.entries(coveragePerProject) as [AfterSuiteRunMeta['transformMode'], Filename[]][]) {
let merged: RawCoverage = { result: [] }

for (const chunk of toSlices(filenames, this.options.processingConcurrency)) {
for (const chunk of this.toSlices(filenames, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length
debug('Covered files %d/%d', index, total)
Expand Down Expand Up @@ -198,7 +197,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
watermarks: this.options.watermarks,
})

if (hasTerminalReporter(this.options.reporter))
if (this.hasTerminalReporter(this.options.reporter))
this.ctx.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.name))

for (const reporter of this.options.reporter) {
Expand Down Expand Up @@ -232,10 +231,8 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
this.updateThresholds({
thresholds: resolvedThresholds,
perFile: this.options.thresholds.perFile,
configurationFile: {
write: () => writeFileSync(configFilePath, configModule.generate().code, 'utf-8'),
read: () => resolveConfig(configModule),
},
configurationFile: configModule,
onUpdate: () => writeFileSync(configFilePath, configModule.generate().code, 'utf-8'),
})
}
}
Expand All @@ -255,7 +252,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
let merged: RawCoverage = { result: [] }
let index = 0

for (const chunk of toSlices(uncoveredFiles, this.options.processingConcurrency)) {
for (const chunk of this.toSlices(uncoveredFiles, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length
debug('Uncovered files %d/%d', index, uncoveredFiles.length)
Expand Down Expand Up @@ -334,7 +331,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
const coverageMap = libCoverage.createCoverageMap({})
let index = 0

for (const chunk of toSlices(scriptCoverages, this.options.processingConcurrency)) {
for (const chunk of this.toSlices(scriptCoverages, this.options.processingConcurrency)) {
if (debug.enabled) {
index += chunk.length
debug('Converting %d/%d', index, scriptCoverages.length)
Expand Down Expand Up @@ -410,53 +407,3 @@ function normalizeTransformResults(fetchCache: Map<string, { result: FetchResult

return normalized
}

function hasTerminalReporter(reporters: Options['reporter']) {
return reporters.some(([reporter]) =>
reporter === 'text'
|| reporter === 'text-summary'
|| reporter === 'text-lcov'
|| reporter === 'teamcity')
}

function toSlices<T>(array: T[], size: number): T[][] {
return array.reduce<T[][]>((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
}, [])
}

function resolveConfig(configModule: ProxifiedModule<any>) {
const mod = configModule.exports.default

try {
// Check for "export default { test: {...} }"
if (mod.$type === 'object')
return mod

if (mod.$type === 'function-call') {
// "export default defineConfig({ test: {...} })"
if (mod.$args[0].$type === 'object')
return mod.$args[0]

// "export default defineConfig(() => ({ test: {...} }))"
if (mod.$args[0].$type === 'arrow-function-expression' && mod.$args[0].$body.$type === 'object')
return mod.$args[0].$body
}
}
catch (error) {
// Reduce magicast's verbose errors to readable ones
throw new Error(error instanceof Error ? error.message : String(error))
}

throw new Error('Failed to update coverage thresholds. Configuration file is too complex.')
}
59 changes: 55 additions & 4 deletions packages/vitest/src/utils/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export class BaseCoverageProvider {
/**
* Check if current coverage is above configured thresholds and bump the thresholds if needed
*/
updateThresholds({ thresholds: allThresholds, perFile, configurationFile }: {
updateThresholds({ thresholds: allThresholds, perFile, configurationFile, onUpdate }: {
thresholds: ResolvedThreshold[]
perFile?: boolean
configurationFile: { read: () => unknown; write: () => void }
configurationFile: unknown // ProxifiedModule from magicast
onUpdate: () => void
}) {
let updatedThresholds = false

const config = configurationFile.read()
const config = resolveConfig(configurationFile)
assertConfigurationModule(config)

for (const { coverageMap, thresholds, name } of allThresholds) {
Expand Down Expand Up @@ -63,7 +64,7 @@ export class BaseCoverageProvider {
if (updatedThresholds) {
// eslint-disable-next-line no-console
console.log('Updating thresholds to configuration file. You may want to push with updated coverage thresholds.')
configurationFile.write()
onUpdate()
}
}

Expand Down Expand Up @@ -200,6 +201,30 @@ export class BaseCoverageProvider {

return resolvedReporters
}

hasTerminalReporter(reporters: ResolvedCoverageOptions['reporter']) {
return reporters.some(([reporter]) =>
reporter === 'text'
|| reporter === 'text-summary'
|| reporter === 'text-lcov'
|| reporter === 'teamcity')
}

toSlices<T>(array: T[], size: number): T[][] {
return array.reduce<T[][]>((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
}, [])
}
}

/**
Expand Down Expand Up @@ -228,3 +253,29 @@ function assertConfigurationModule(config: unknown): asserts config is { test: {
throw new Error(`Unable to parse thresholds from configuration file: ${message}`)
}
}

function resolveConfig(configModule: any) {
const mod = configModule.exports.default

try {
// Check for "export default { test: {...} }"
if (mod.$type === 'object')
return mod

if (mod.$type === 'function-call') {
// "export default defineConfig({ test: {...} })"
if (mod.$args[0].$type === 'object')
return mod.$args[0]

// "export default defineConfig(() => ({ test: {...} }))"
if (mod.$args[0].$type === 'arrow-function-expression' && mod.$args[0].$body.$type === 'object')
return mod.$args[0].$body
}
}
catch (error) {
// Reduce magicast's verbose errors to readable ones
throw new Error(error instanceof Error ? error.message : String(error))
}

throw new Error('Failed to update coverage thresholds. Configuration file is too complex.')
}

0 comments on commit c692f76

Please sign in to comment.