mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
feat!(coverage): v8 to support only AST based remapping (#8064)
This commit is contained in:
parent
41a111c35b
commit
176133ed0c
@ -1628,51 +1628,11 @@ Sets thresholds to 100 for files matching the glob pattern.
|
||||
}
|
||||
```
|
||||
|
||||
#### coverage.ignoreEmptyLines
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `true` (`false` in v1)
|
||||
- **Available for providers:** `'v8'`
|
||||
- **CLI:** `--coverage.ignoreEmptyLines=<boolean>`
|
||||
|
||||
Ignore empty lines, comments and other non-runtime code, e.g. Typescript types. Requires `experimentalAstAwareRemapping: false`.
|
||||
|
||||
This option works only if the used compiler removes comments and other non-runtime code from the transpiled code.
|
||||
By default Vite uses ESBuild which removes comments and Typescript types from `.ts`, `.tsx` and `.jsx` files.
|
||||
|
||||
If you want to apply ESBuild to other files as well, define them in [`esbuild` options](https://vitejs.dev/config/shared-options.html#esbuild):
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
esbuild: {
|
||||
// Transpile all files with ESBuild to remove comments from code coverage.
|
||||
// Required for `test.coverage.ignoreEmptyLines` to work:
|
||||
include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.ts', '**/*.tsx'],
|
||||
},
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
ignoreEmptyLines: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
#### coverage.experimentalAstAwareRemapping
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `false`
|
||||
- **Available for providers:** `'v8'`
|
||||
- **CLI:** `--coverage.experimentalAstAwareRemapping=<boolean>`
|
||||
|
||||
Remap coverage with experimental AST based analysis. Provides more accurate results compared to default mode.
|
||||
|
||||
#### coverage.ignoreClassMethods
|
||||
|
||||
- **Type:** `string[]`
|
||||
- **Default:** `[]`
|
||||
- **Available for providers:** `'istanbul'`
|
||||
- **Available for providers:** `'v8' | 'istanbul'`
|
||||
- **CLI:** `--coverage.ignoreClassMethods=<method>`
|
||||
|
||||
Set to array of class method names to ignore for coverage.
|
||||
|
||||
@ -336,9 +336,8 @@ Please refer to the type definition for more details.
|
||||
|
||||
Both coverage providers have their own ways how to ignore code from coverage reports:
|
||||
|
||||
- [`v8`](https://github.com/istanbuljs/v8-to-istanbul#ignoring-uncovered-lines)
|
||||
- [`v8`](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code)
|
||||
- [`istanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines)
|
||||
- `v8` with [`experimentalAstAwareRemapping: true`](https://vitest.dev/config/#coverage-experimentalAstAwareRemapping) see [ast-v8-to-istanbul | Ignoring code](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code)
|
||||
|
||||
When using TypeScript the source codes are transpiled using `esbuild`, which strips all comments from the source codes ([esbuild#516](https://github.com/evanw/esbuild/issues/516)).
|
||||
Comments which are considered as [legal comments](https://esbuild.github.io/api/#legal-comments) are preserved.
|
||||
|
||||
@ -21,6 +21,21 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
### V8 Code Coverage Major Changes
|
||||
|
||||
Vitest's V8 code coverage provider is now using more accurate coverage result remapping logic.
|
||||
It is expected for users to see changes in their coverage reports when updating from Vitest v3.
|
||||
|
||||
In the past Vitest used [`v8-to-istanbul`](https://github.com/istanbuljs/v8-to-istanbul) for remapping V8 coverage results into your source files.
|
||||
This method wasn't very accurate and provided plenty of false positives in the coverage reports.
|
||||
We've now developed a new package that utilizes AST based analysis for the V8 coverage.
|
||||
This allows V8 reports to be as accurate as `@vitest/coverage-istanbul` reports.
|
||||
|
||||
- Coverage ignore hints have updated. See [Coverage | Ignoring Code](/guide/coverage.html#ignoring-code).
|
||||
- `coverage.ignoreEmptyLines` is removed. Lines without runtime code are no longer included in reports.
|
||||
- `coverage.experimentalAstAwareRemapping` is removed. This option is now enabled by default, and is the only supported remapping method.
|
||||
- `coverage.ignoreClassMethods` is now supported by V8 provider too.
|
||||
|
||||
### Removed options `coverage.all` and `coverage.extensions`
|
||||
|
||||
In previous versions Vitest included all uncovered files in coverage report by default.
|
||||
|
||||
@ -86,7 +86,6 @@
|
||||
"@sinonjs/fake-timers@14.0.0": "patches/@sinonjs__fake-timers@14.0.0.patch",
|
||||
"cac@6.7.14": "patches/cac@6.7.14.patch",
|
||||
"@types/sinonjs__fake-timers@8.1.5": "patches/@types__sinonjs__fake-timers@8.1.5.patch",
|
||||
"v8-to-istanbul@9.3.0": "patches/v8-to-istanbul@9.3.0.patch",
|
||||
"acorn@8.11.3": "patches/acorn@8.11.3.patch"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
"@types/istanbul-reports": "catalog:",
|
||||
"@vitest/browser": "workspace:*",
|
||||
"pathe": "catalog:",
|
||||
"v8-to-istanbul": "^9.3.0",
|
||||
"vite-node": "workspace:*",
|
||||
"vitest": "workspace:*"
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import type { AfterSuiteRunMeta } from 'vitest'
|
||||
import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, TestProject, Vitest } from 'vitest/node'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import remapping from '@ampproject/remapping'
|
||||
// @ts-expect-error -- untyped
|
||||
import { mergeProcessCovs } from '@bcoe/v8-coverage'
|
||||
import astV8ToIstanbul from 'ast-v8-to-istanbul'
|
||||
@ -15,12 +14,10 @@ import libCoverage from 'istanbul-lib-coverage'
|
||||
import libReport from 'istanbul-lib-report'
|
||||
import libSourceMaps from 'istanbul-lib-source-maps'
|
||||
import reports from 'istanbul-reports'
|
||||
import MagicString from 'magic-string'
|
||||
import { parseModule } from 'magicast'
|
||||
import { normalize } from 'pathe'
|
||||
import { provider } from 'std-env'
|
||||
import c from 'tinyrainbow'
|
||||
import v8ToIstanbul from 'v8-to-istanbul'
|
||||
import { cleanUrl } from 'vite-node/utils'
|
||||
|
||||
import { BaseCoverageProvider } from 'vitest/coverage'
|
||||
@ -34,11 +31,6 @@ export interface ScriptCoverageWithOffset extends Profiler.ScriptCoverage {
|
||||
type TransformResults = Map<string, FetchResult>
|
||||
interface RawCoverage { result: ScriptCoverageWithOffset[] }
|
||||
|
||||
// Note that this needs to match the line ending as well
|
||||
const VITE_EXPORTS_LINE_PATTERN
|
||||
= /Object\.defineProperty\(__vite_ssr_exports__.*\n/g
|
||||
const DECORATOR_METADATA_PATTERN
|
||||
= /_ts_metadata\("design:paramtypes", \[[^\]]*\]\),*/g
|
||||
const FILE_PROTOCOL = 'file://'
|
||||
|
||||
const debug = createDebug('vitest:coverage')
|
||||
@ -188,22 +180,11 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
|
||||
transform,
|
||||
)
|
||||
|
||||
coverageMap.merge(await this.v8ToIstanbul(
|
||||
coverageMap.merge(await this.remapCoverage(
|
||||
url.href,
|
||||
0,
|
||||
sources,
|
||||
[{
|
||||
ranges: [
|
||||
{
|
||||
startOffset: 0,
|
||||
endOffset: sources.originalSource.length,
|
||||
count: 0,
|
||||
},
|
||||
],
|
||||
isBlockCoverage: true,
|
||||
// This is magical value that indicates an empty report: https://github.com/istanbuljs/v8-to-istanbul/blob/fca5e6a9e6ef38a9cdc3a178d5a6cf9ef82e6cab/lib/v8-to-istanbul.js#LL131C40-L131C40
|
||||
functionName: '(empty-report)',
|
||||
}],
|
||||
[],
|
||||
))
|
||||
|
||||
if (debug.enabled) {
|
||||
@ -219,118 +200,109 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
|
||||
return coverageMap
|
||||
}
|
||||
|
||||
private async v8ToIstanbul(filename: string, wrapperLength: number, sources: Awaited<ReturnType<typeof this.getSources>>, functions: Profiler.FunctionCoverage[]) {
|
||||
if (this.options.experimentalAstAwareRemapping) {
|
||||
let ast
|
||||
try {
|
||||
ast = await parseAstAsync(sources.source)
|
||||
}
|
||||
catch (error) {
|
||||
this.ctx.logger.error(`Failed to parse ${filename}. Excluding it from coverage.\n`, error)
|
||||
return {}
|
||||
}
|
||||
|
||||
return await astV8ToIstanbul({
|
||||
code: sources.source,
|
||||
sourceMap: sources.sourceMap?.sourcemap,
|
||||
ast,
|
||||
coverage: { functions, url: filename },
|
||||
ignoreClassMethods: this.options.ignoreClassMethods,
|
||||
wrapperLength,
|
||||
ignoreNode: (node, type) => {
|
||||
// SSR transformed imports
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'VariableDeclarator'
|
||||
&& node.id.type === 'Identifier'
|
||||
&& node.id.name.startsWith('__vite_ssr_import_')
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// SSR transformed exports vite@>6.3.5
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'AssignmentExpression'
|
||||
&& node.expression.left.type === 'MemberExpression'
|
||||
&& node.expression.left.object.type === 'Identifier'
|
||||
&& node.expression.left.object.name === '__vite_ssr_exports__'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// SSR transformed exports vite@^6.3.5
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'VariableDeclarator'
|
||||
&& node.id.type === 'Identifier'
|
||||
&& node.id.name === '__vite_ssr_export_default__'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// in-source test with "if (import.meta.vitest)"
|
||||
if (
|
||||
(type === 'branch' || type === 'statement')
|
||||
&& node.type === 'IfStatement'
|
||||
&& node.test.type === 'MemberExpression'
|
||||
&& node.test.property.type === 'Identifier'
|
||||
&& node.test.property.name === 'vitest'
|
||||
) {
|
||||
// SSR
|
||||
if (
|
||||
node.test.object.type === 'Identifier'
|
||||
&& node.test.object.name === '__vite_ssr_import_meta__'
|
||||
) {
|
||||
return 'ignore-this-and-nested-nodes'
|
||||
}
|
||||
|
||||
// Web
|
||||
if (
|
||||
node.test.object.type === 'MetaProperty'
|
||||
&& node.test.object.meta.name === 'import'
|
||||
&& node.test.object.property.name === 'meta'
|
||||
) {
|
||||
return 'ignore-this-and-nested-nodes'
|
||||
}
|
||||
}
|
||||
|
||||
// Browser mode's "import.meta.env ="
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'AssignmentExpression'
|
||||
&& node.expression.left.type === 'MemberExpression'
|
||||
&& node.expression.left.object.type === 'MetaProperty'
|
||||
&& node.expression.left.object.meta.name === 'import'
|
||||
&& node.expression.left.object.property.name === 'meta'
|
||||
&& node.expression.left.property.type === 'Identifier'
|
||||
&& node.expression.left.property.name === 'env') {
|
||||
return true
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const converter = v8ToIstanbul(
|
||||
filename,
|
||||
wrapperLength,
|
||||
sources,
|
||||
undefined,
|
||||
this.options.ignoreEmptyLines,
|
||||
)
|
||||
await converter.load()
|
||||
private async remapCoverage(filename: string, wrapperLength: number, result: Awaited<ReturnType<typeof this.getSources>>, functions: Profiler.FunctionCoverage[]) {
|
||||
let ast
|
||||
|
||||
try {
|
||||
converter.applyCoverage(functions)
|
||||
ast = await parseAstAsync(result.code)
|
||||
}
|
||||
catch (error) {
|
||||
this.ctx.logger.error(`Failed to convert coverage for ${filename}.\n`, error)
|
||||
this.ctx.logger.error(`Failed to parse ${filename}. Excluding it from coverage.\n`, error)
|
||||
return {}
|
||||
}
|
||||
|
||||
return converter.toIstanbul()
|
||||
return await astV8ToIstanbul({
|
||||
code: result.code,
|
||||
sourceMap: result.map,
|
||||
ast,
|
||||
coverage: { functions, url: filename },
|
||||
ignoreClassMethods: this.options.ignoreClassMethods,
|
||||
wrapperLength,
|
||||
ignoreNode: (node, type) => {
|
||||
// SSR transformed imports
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'VariableDeclarator'
|
||||
&& node.id.type === 'Identifier'
|
||||
&& node.id.name.startsWith('__vite_ssr_import_')
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// SSR transformed exports vite@>6.3.5
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'AssignmentExpression'
|
||||
&& node.expression.left.type === 'MemberExpression'
|
||||
&& node.expression.left.object.type === 'Identifier'
|
||||
&& node.expression.left.object.name === '__vite_ssr_exports__'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// SSR transformed exports vite@^6.3.5
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'VariableDeclarator'
|
||||
&& node.id.type === 'Identifier'
|
||||
&& node.id.name === '__vite_ssr_export_default__'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// in-source test with "if (import.meta.vitest)"
|
||||
if (
|
||||
(type === 'branch' || type === 'statement')
|
||||
&& node.type === 'IfStatement'
|
||||
&& node.test.type === 'MemberExpression'
|
||||
&& node.test.property.type === 'Identifier'
|
||||
&& node.test.property.name === 'vitest'
|
||||
) {
|
||||
// SSR
|
||||
if (
|
||||
node.test.object.type === 'Identifier'
|
||||
&& node.test.object.name === '__vite_ssr_import_meta__'
|
||||
) {
|
||||
return 'ignore-this-and-nested-nodes'
|
||||
}
|
||||
|
||||
// Web
|
||||
if (
|
||||
node.test.object.type === 'MetaProperty'
|
||||
&& node.test.object.meta.name === 'import'
|
||||
&& node.test.object.property.name === 'meta'
|
||||
) {
|
||||
return 'ignore-this-and-nested-nodes'
|
||||
}
|
||||
}
|
||||
|
||||
// Browser mode's "import.meta.env ="
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'AssignmentExpression'
|
||||
&& node.expression.left.type === 'MemberExpression'
|
||||
&& node.expression.left.object.type === 'MetaProperty'
|
||||
&& node.expression.left.object.meta.name === 'import'
|
||||
&& node.expression.left.object.property.name === 'meta'
|
||||
&& node.expression.left.property.type === 'Identifier'
|
||||
&& node.expression.left.property.name === 'env') {
|
||||
return true
|
||||
}
|
||||
|
||||
// SWC's decorators
|
||||
if (
|
||||
type === 'statement'
|
||||
&& node.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'CallExpression'
|
||||
&& node.expression.callee.type === 'Identifier'
|
||||
&& node.expression.callee.name === '_ts_decorate') {
|
||||
return 'ignore-this-and-nested-nodes'
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private async getSources<TransformResult extends (FetchResult | Awaited<ReturnType<typeof this.ctx.vitenode.transformRequest>>)>(
|
||||
@ -339,9 +311,8 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
|
||||
onTransform: (filepath: string) => Promise<TransformResult>,
|
||||
functions: Profiler.FunctionCoverage[] = [],
|
||||
): Promise<{
|
||||
source: string
|
||||
originalSource: string
|
||||
sourceMap?: { sourcemap: EncodedSourceMap }
|
||||
code: string
|
||||
map?: EncodedSourceMap
|
||||
}> {
|
||||
const filePath = normalize(fileURLToPath(url))
|
||||
|
||||
@ -353,45 +324,32 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
|
||||
|
||||
const map = transformResult?.map as EncodedSourceMap | undefined
|
||||
const code = transformResult?.code
|
||||
const sourcesContent = map?.sourcesContent || []
|
||||
|
||||
if (!sourcesContent[0]) {
|
||||
sourcesContent[0] = await fs.readFile(filePath, 'utf-8').catch(() => {
|
||||
if (!code) {
|
||||
const original = await fs.readFile(filePath, 'utf-8').catch(() => {
|
||||
// If file does not exist construct a dummy source for it.
|
||||
// These can be files that were generated dynamically during the test run and were removed after it.
|
||||
const length = findLongestFunctionLength(functions)
|
||||
return '/'.repeat(length)
|
||||
})
|
||||
|
||||
return { code: original }
|
||||
}
|
||||
|
||||
// These can be uncovered files picked by "coverage.include" or files that are loaded outside vite-node
|
||||
if (!map) {
|
||||
return {
|
||||
source: code || sourcesContent[0],
|
||||
originalSource: sourcesContent[0],
|
||||
// Vue needs special handling for "map.sources"
|
||||
if (map) {
|
||||
map.sources ||= []
|
||||
|
||||
map.sources = map.sources
|
||||
.filter(source => source != null)
|
||||
.map(source => new URL(source, url).href)
|
||||
|
||||
if (map.sources.length === 0) {
|
||||
map.sources.push(url)
|
||||
}
|
||||
}
|
||||
|
||||
const sources = (map.sources || [])
|
||||
.filter(source => source != null)
|
||||
.map(source => new URL(source, url).href)
|
||||
|
||||
if (sources.length === 0) {
|
||||
sources.push(url)
|
||||
}
|
||||
|
||||
return {
|
||||
originalSource: sourcesContent[0],
|
||||
source: code || sourcesContent[0],
|
||||
sourceMap: {
|
||||
sourcemap: excludeGeneratedCode(code, {
|
||||
...map,
|
||||
version: 3,
|
||||
sources,
|
||||
sourcesContent,
|
||||
}),
|
||||
},
|
||||
}
|
||||
return { code, map }
|
||||
}
|
||||
|
||||
private async convertCoverage(
|
||||
@ -464,7 +422,7 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
|
||||
functions,
|
||||
)
|
||||
|
||||
coverageMap.merge(await this.v8ToIstanbul(
|
||||
coverageMap.merge(await this.remapCoverage(
|
||||
url,
|
||||
startOffset,
|
||||
sources,
|
||||
@ -491,42 +449,6 @@ async function transformCoverage(coverageMap: CoverageMap) {
|
||||
return await sourceMapStore.transformCoverage(coverageMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove generated code from the source maps:
|
||||
* - Vite's export helpers: e.g. `Object.defineProperty(__vite_ssr_exports__, "sum", { enumerable: true, configurable: true, get(){ return sum }});`
|
||||
* - SWC's decorator metadata: e.g. `_ts_metadata("design:paramtypes", [\ntypeof Request === "undefined" ? Object : Request\n]),`
|
||||
*/
|
||||
function excludeGeneratedCode(
|
||||
source: string | undefined,
|
||||
map: EncodedSourceMap,
|
||||
) {
|
||||
if (!source) {
|
||||
return map
|
||||
}
|
||||
|
||||
if (
|
||||
!source.match(VITE_EXPORTS_LINE_PATTERN)
|
||||
&& !source.match(DECORATOR_METADATA_PATTERN)
|
||||
) {
|
||||
return map
|
||||
}
|
||||
|
||||
const trimmed = new MagicString(source)
|
||||
trimmed.replaceAll(VITE_EXPORTS_LINE_PATTERN, '\n')
|
||||
trimmed.replaceAll(DECORATOR_METADATA_PATTERN, match =>
|
||||
'\n'.repeat(match.split('\n').length - 1))
|
||||
|
||||
const trimmedMap = trimmed.generateMap({ hires: 'boundary' })
|
||||
|
||||
// A merged source map where the first one excludes generated parts
|
||||
const combinedMap = remapping(
|
||||
[{ ...trimmedMap, version: 3 }, map],
|
||||
() => null,
|
||||
)
|
||||
|
||||
return combinedMap as EncodedSourceMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the function with highest `endOffset` to determine the length of the file
|
||||
*/
|
||||
|
||||
@ -44,7 +44,6 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
|
||||
],
|
||||
allowExternal: false,
|
||||
excludeAfterRemap: false,
|
||||
ignoreEmptyLines: true,
|
||||
processingConcurrency: Math.min(
|
||||
20,
|
||||
os.availableParallelism?.() ?? os.cpus().length,
|
||||
|
||||
@ -248,9 +248,7 @@ export interface BaseCoverageOptions {
|
||||
* Defaults to `Math.min(20, os.availableParallelism?.() ?? os.cpus().length)`
|
||||
*/
|
||||
processingConcurrency?: number
|
||||
}
|
||||
|
||||
export interface CoverageIstanbulOptions extends BaseCoverageOptions {
|
||||
/**
|
||||
* Set to array of class method names to ignore for coverage
|
||||
*
|
||||
@ -259,27 +257,9 @@ export interface CoverageIstanbulOptions extends BaseCoverageOptions {
|
||||
ignoreClassMethods?: string[]
|
||||
}
|
||||
|
||||
export interface CoverageV8Options extends BaseCoverageOptions {
|
||||
/**
|
||||
* Ignore empty lines, comments and other non-runtime code, e.g. Typescript types
|
||||
* - Requires `experimentalAstAwareRemapping: false`
|
||||
*/
|
||||
ignoreEmptyLines?: boolean
|
||||
export interface CoverageIstanbulOptions extends BaseCoverageOptions {}
|
||||
|
||||
/**
|
||||
* Remap coverage with experimental AST based analysis
|
||||
* - Provides more accurate results compared to default mode
|
||||
*/
|
||||
experimentalAstAwareRemapping?: boolean
|
||||
|
||||
/**
|
||||
* Set to array of class method names to ignore for coverage.
|
||||
* - Requires `experimentalAstAwareRemapping: true`
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
ignoreClassMethods?: string[]
|
||||
}
|
||||
export interface CoverageV8Options extends BaseCoverageOptions {}
|
||||
|
||||
export interface CustomProviderOptions
|
||||
extends Pick<BaseCoverageOptions, FieldsWithDefaultValues> {
|
||||
|
||||
@ -1,174 +0,0 @@
|
||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
||||
deleted file mode 100644
|
||||
index 4f7e3bc8d1bba4feb51044ff9eb77b41f972f957..0000000000000000000000000000000000000000
|
||||
diff --git a/index.d.ts b/index.d.ts
|
||||
index ee7b286844f2bf96357218166e26e1c338f774cf..657531b7c75f43e9a4e957dd1f10797e44da5bb1 100644
|
||||
--- a/index.d.ts
|
||||
+++ b/index.d.ts
|
||||
@@ -1,5 +1,7 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
|
||||
+
|
||||
import { Profiler } from 'inspector'
|
||||
import { CoverageMapData } from 'istanbul-lib-coverage'
|
||||
import { SourceMapInput } from '@jridgewell/trace-mapping'
|
||||
@@ -20,6 +22,6 @@ declare class V8ToIstanbul {
|
||||
toIstanbul(): CoverageMapData
|
||||
}
|
||||
|
||||
-declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean): V8ToIstanbul
|
||||
+declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean, excludeEmptyLines?: boolean): V8ToIstanbul
|
||||
|
||||
export = v8ToIstanbul
|
||||
diff --git a/index.js b/index.js
|
||||
index 4db27a7d84324d0e6605c5506e3eee5665ddfeb0..7bfb839634b1e3c54efedc3c270d82edc4167a64 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,5 +1,6 @@
|
||||
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
|
||||
const V8ToIstanbul = require('./lib/v8-to-istanbul')
|
||||
|
||||
-module.exports = function (path, wrapperLength, sources, excludePath) {
|
||||
- return new V8ToIstanbul(path, wrapperLength, sources, excludePath)
|
||||
+module.exports = function (path, wrapperLength, sources, excludePath, excludeEmptyLines) {
|
||||
+ return new V8ToIstanbul(path, wrapperLength, sources, excludePath, excludeEmptyLines)
|
||||
}
|
||||
diff --git a/lib/source.js b/lib/source.js
|
||||
index d8ebc215f6ad83d472abafe976935acfe5c61b04..021fd2aed1f73ebb4adc449ce6e96f2d89c295a5 100644
|
||||
--- a/lib/source.js
|
||||
+++ b/lib/source.js
|
||||
@@ -1,23 +1,32 @@
|
||||
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
|
||||
const CovLine = require('./line')
|
||||
const { sliceRange } = require('./range')
|
||||
-const { originalPositionFor, generatedPositionFor, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')
|
||||
+const { originalPositionFor, generatedPositionFor, eachMapping, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')
|
||||
|
||||
module.exports = class CovSource {
|
||||
- constructor (sourceRaw, wrapperLength) {
|
||||
+ constructor (sourceRaw, wrapperLength, traceMap) {
|
||||
sourceRaw = sourceRaw ? sourceRaw.trimEnd() : ''
|
||||
this.lines = []
|
||||
this.eof = sourceRaw.length
|
||||
this.shebangLength = getShebangLength(sourceRaw)
|
||||
this.wrapperLength = wrapperLength - this.shebangLength
|
||||
- this._buildLines(sourceRaw)
|
||||
+ this._buildLines(sourceRaw, traceMap)
|
||||
}
|
||||
|
||||
- _buildLines (source) {
|
||||
+ _buildLines (source, traceMap) {
|
||||
let position = 0
|
||||
let ignoreCount = 0
|
||||
let ignoreAll = false
|
||||
+ const linesToCover = traceMap && this._parseLinesToCover(traceMap)
|
||||
+
|
||||
for (const [i, lineStr] of source.split(/(?<=\r?\n)/u).entries()) {
|
||||
- const line = new CovLine(i + 1, position, lineStr)
|
||||
+ const lineNumber = i + 1
|
||||
+ const line = new CovLine(lineNumber, position, lineStr)
|
||||
+
|
||||
+ if (linesToCover && !linesToCover.has(lineNumber)) {
|
||||
+ line.ignore = true
|
||||
+ }
|
||||
+
|
||||
if (ignoreCount > 0) {
|
||||
line.ignore = true
|
||||
ignoreCount--
|
||||
@@ -125,6 +134,18 @@ module.exports = class CovSource {
|
||||
if (this.lines[line - 1] === undefined) return this.eof
|
||||
return Math.min(this.lines[line - 1].startCol + relCol, this.lines[line - 1].endCol)
|
||||
}
|
||||
+
|
||||
+ _parseLinesToCover (traceMap) {
|
||||
+ const linesToCover = new Set()
|
||||
+
|
||||
+ eachMapping(traceMap, (mapping) => {
|
||||
+ if (mapping.originalLine !== null) {
|
||||
+ linesToCover.add(mapping.originalLine)
|
||||
+ }
|
||||
+ })
|
||||
+
|
||||
+ return linesToCover
|
||||
+ }
|
||||
}
|
||||
|
||||
// this implementation is pulled over from istanbul-lib-sourcemap:
|
||||
diff --git a/lib/v8-to-istanbul.js b/lib/v8-to-istanbul.js
|
||||
index 3616437b00658861dc5a8910c64d1449e9fdf467..4642ca4818ce982e2f186abe4289793768e7cdf9 100644
|
||||
--- a/lib/v8-to-istanbul.js
|
||||
+++ b/lib/v8-to-istanbul.js
|
||||
@@ -1,3 +1,4 @@
|
||||
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
|
||||
const assert = require('assert')
|
||||
const convertSourceMap = require('convert-source-map')
|
||||
const util = require('util')
|
||||
@@ -8,14 +9,9 @@ const CovBranch = require('./branch')
|
||||
const CovFunction = require('./function')
|
||||
const CovSource = require('./source')
|
||||
const { sliceRange } = require('./range')
|
||||
-const compatError = Error(`requires Node.js ${require('../package.json').engines.node}`)
|
||||
-const { readFileSync } = require('fs')
|
||||
-let readFile = () => { throw compatError }
|
||||
-try {
|
||||
- readFile = require('fs').promises.readFile
|
||||
-} catch (_err) {
|
||||
- // most likely we're on an older version of Node.js.
|
||||
-}
|
||||
+const { readFileSync, promises } = require('fs')
|
||||
+const readFile = promises.readFile
|
||||
+
|
||||
const { TraceMap } = require('@jridgewell/trace-mapping')
|
||||
const isOlderNode10 = /^v10\.(([0-9]\.)|(1[0-5]\.))/u.test(process.version)
|
||||
const isNode8 = /^v8\./.test(process.version)
|
||||
@@ -25,12 +21,13 @@ const isNode8 = /^v8\./.test(process.version)
|
||||
const cjsWrapperLength = isOlderNode10 ? require('module').wrapper[0].length : 0
|
||||
|
||||
module.exports = class V8ToIstanbul {
|
||||
- constructor (scriptPath, wrapperLength, sources, excludePath) {
|
||||
+ constructor (scriptPath, wrapperLength, sources, excludePath, excludeEmptyLines) {
|
||||
assert(typeof scriptPath === 'string', 'scriptPath must be a string')
|
||||
assert(!isNode8, 'This module does not support node 8 or lower, please upgrade to node 10')
|
||||
this.path = parsePath(scriptPath)
|
||||
this.wrapperLength = wrapperLength === undefined ? cjsWrapperLength : wrapperLength
|
||||
this.excludePath = excludePath || (() => false)
|
||||
+ this.excludeEmptyLines = excludeEmptyLines === true
|
||||
this.sources = sources || {}
|
||||
this.generatedLines = []
|
||||
this.branches = {}
|
||||
@@ -58,8 +55,8 @@ module.exports = class V8ToIstanbul {
|
||||
if (!this.sourceMap.sourcesContent) {
|
||||
this.sourceMap.sourcesContent = await this.sourcesContentFromSources()
|
||||
}
|
||||
- this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength), path: this.sourceMap.sources[i] }))
|
||||
- this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
|
||||
+ this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.sourceMap.sources[i] }))
|
||||
+ this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
|
||||
} else {
|
||||
const candidatePath = this.rawSourceMap.sourcemap.sources.length >= 1 ? this.rawSourceMap.sourcemap.sources[0] : this.rawSourceMap.sourcemap.file
|
||||
this.path = this._resolveSource(this.rawSourceMap, candidatePath || this.path)
|
||||
@@ -82,8 +79,8 @@ module.exports = class V8ToIstanbul {
|
||||
// We fallback to reading the original source from disk.
|
||||
originalRawSource = await readFile(this.path, 'utf8')
|
||||
}
|
||||
- this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength), path: this.path }]
|
||||
- this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
|
||||
+ this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.path }]
|
||||
+ this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
|
||||
}
|
||||
} else {
|
||||
this.covSources = [{ source: new CovSource(rawSource, this.wrapperLength), path: this.path }]
|
||||
@@ -281,8 +278,10 @@ module.exports = class V8ToIstanbul {
|
||||
s: {}
|
||||
}
|
||||
source.lines.forEach((line, index) => {
|
||||
- statements.statementMap[`${index}`] = line.toIstanbul()
|
||||
- statements.s[`${index}`] = line.ignore ? 1 : line.count
|
||||
+ if (!line.ignore) {
|
||||
+ statements.statementMap[`${index}`] = line.toIstanbul()
|
||||
+ statements.s[`${index}`] = line.count
|
||||
+ }
|
||||
})
|
||||
return statements
|
||||
}
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -143,9 +143,6 @@ patchedDependencies:
|
||||
cac@6.7.14:
|
||||
hash: a8f0f3517a47ce716ed90c0cfe6ae382ab763b021a664ada2a608477d0621588
|
||||
path: patches/cac@6.7.14.patch
|
||||
v8-to-istanbul@9.3.0:
|
||||
hash: fc8eccce7f8e7c27b0d0b1c63e93ff3adaa8c05c33d5603bacf58ea6d12951f3
|
||||
path: patches/v8-to-istanbul@9.3.0.patch
|
||||
|
||||
importers:
|
||||
|
||||
@ -633,9 +630,6 @@ importers:
|
||||
pathe:
|
||||
specifier: 'catalog:'
|
||||
version: 2.0.3
|
||||
v8-to-istanbul:
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0(patch_hash=fc8eccce7f8e7c27b0d0b1c63e93ff3adaa8c05c33d5603bacf58ea6d12951f3)
|
||||
vite-node:
|
||||
specifier: workspace:*
|
||||
version: link:../vite-node
|
||||
@ -8434,10 +8428,6 @@ packages:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
v8-to-istanbul@9.3.0:
|
||||
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
|
||||
varint@6.0.0:
|
||||
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||
|
||||
@ -16596,12 +16586,6 @@ snapshots:
|
||||
|
||||
utils-merge@1.0.1: {}
|
||||
|
||||
v8-to-istanbul@9.3.0(patch_hash=fc8eccce7f8e7c27b0d0b1c63e93ff3adaa8c05c33d5603bacf58ea6d12951f3):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
'@types/istanbul-lib-coverage': 2.0.6
|
||||
convert-source-map: 2.0.0
|
||||
|
||||
varint@6.0.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export function uncovered() {
|
||||
return 0
|
||||
export function uncovered(condition: boolean) {
|
||||
return condition ? 1 : 0
|
||||
}
|
||||
@ -394,7 +394,7 @@ test('coverage.autoUpdate cannot update thresholds when configuration file doesn
|
||||
})
|
||||
|
||||
test('boolean flag 100 should not crash CLI', async () => {
|
||||
let { stderr } = await runVitestCli('--coverage.enabled', '--coverage.thresholds.100', '--coverage.include=fixtures/coverage-test')
|
||||
let { stderr } = await runVitestCli('--coverage.enabled', '--coverage.thresholds.100', '--coverage.include=fixtures/coverage-test', '--passWithNoTests')
|
||||
// non-zero coverage shows up, which is non-deterministic, so strip it.
|
||||
stderr = stderr.replace(/\([0-9.]+%\) does/g, '(0%) does')
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/* v8 ignore next 4 */
|
||||
// padding
|
||||
/* istanbul ignore next -- @preserve */
|
||||
export function first() {
|
||||
return "First"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import libCoverage from 'istanbul-lib-coverage'
|
||||
import { expect } from 'vitest'
|
||||
import * as transpiled from '../fixtures/src/pre-bundle/bundle.js'
|
||||
import { coverageTest, formatSummary, isV8Provider, normalizeURL, readCoverageJson, runVitest, test } from '../utils.js'
|
||||
import { coverageTest, formatSummary, normalizeURL, readCoverageJson, runVitest, test } from '../utils.js'
|
||||
|
||||
test('bundled code with source maps to originals', async () => {
|
||||
await runVitest({
|
||||
@ -31,42 +31,22 @@ test('bundled code with source maps to originals', async () => {
|
||||
[second.path]: formatSummary(second.toSummary()),
|
||||
}
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/pre-bundle/first.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "4/6 (66.66%)",
|
||||
"statements": "4/6 (66.66%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/pre-bundle/second.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "4/6 (66.66%)",
|
||||
"statements": "4/6 (66.66%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/pre-bundle/first.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/pre-bundle/second.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/pre-bundle/first.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/pre-bundle/second.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
coverageTest('run bundled sources', () => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { beforeAll, expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
// Note that this test may fail if you have new files in "vitest/test/coverage/src"
|
||||
// and have not yet committed those
|
||||
@ -51,40 +51,20 @@ test('{ changed: "HEAD" }', async () => {
|
||||
const uncoveredFile = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/new-uncovered-file.ts')
|
||||
const changedFile = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/file-to-change.ts')
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect([uncoveredFile, changedFile]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/file-to-change.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "4/6 (66.66%)",
|
||||
"statements": "4/6 (66.66%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/new-uncovered-file.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/1 (100%)",
|
||||
"lines": "0/3 (0%)",
|
||||
"statements": "0/3 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect([uncoveredFile, changedFile]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/file-to-change.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/new-uncovered-file.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect([uncoveredFile, changedFile]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/file-to-change.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/new-uncovered-file.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}, SKIP)
|
||||
|
||||
@ -72,23 +72,6 @@ test('provider options, generic', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('provider specific options, v8', () => {
|
||||
assertType<Coverage>({
|
||||
provider: 'v8',
|
||||
experimentalAstAwareRemapping: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('provider specific options, istanbul', () => {
|
||||
assertType<Coverage>({
|
||||
provider: 'istanbul',
|
||||
ignoreClassMethods: ['string'],
|
||||
|
||||
// @ts-expect-error -- v8 specific error
|
||||
experimentalAstAwareRemapping: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('provider specific options, custom', () => {
|
||||
assertType<Coverage>({
|
||||
provider: 'custom',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { expect } from 'vitest'
|
||||
import { DecoratorsTester } from '../fixtures/src/decorators'
|
||||
import { coverageTest, isV8Provider, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { coverageTest, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('decorators generated metadata is ignored', async () => {
|
||||
await runVitest({
|
||||
@ -14,15 +14,8 @@ test('decorators generated metadata is ignored', async () => {
|
||||
const lineCoverage = fileCoverage.getLineCoverage()
|
||||
const branchCoverage = fileCoverage.getBranchCoverageByLine()
|
||||
|
||||
// Decorator should not be uncovered - on V8 this is marked as covered, on Istanbul it's excluded from report
|
||||
if (isV8Provider()) {
|
||||
expect(lineCoverage['4']).toBe(1)
|
||||
expect(branchCoverage['4'].coverage).toBe(100)
|
||||
}
|
||||
else {
|
||||
expect(lineCoverage['4']).toBeUndefined()
|
||||
expect(branchCoverage['4']).toBeUndefined()
|
||||
}
|
||||
expect(lineCoverage['4']).toBeUndefined()
|
||||
expect(branchCoverage['4']).toBeUndefined()
|
||||
|
||||
// Covered branch should be marked correctly
|
||||
expect(lineCoverage['7']).toBe(1)
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
import { beforeAll, expect } from 'vitest'
|
||||
import { add } from '../fixtures/src/empty-lines'
|
||||
import { coverageTest, describe, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
type CoveredLine = 1
|
||||
type UncoveredLine = 0
|
||||
type IgnoredLine = undefined
|
||||
|
||||
// Key is 1-based line number
|
||||
type LineCoverage = Record<number, CoveredLine | UncoveredLine | IgnoredLine>
|
||||
|
||||
describe('include empty lines', () => {
|
||||
let coveredFileLines: LineCoverage
|
||||
let uncoveredFileLines: LineCoverage
|
||||
|
||||
beforeAll(async () => {
|
||||
await runVitest({
|
||||
include: [normalizeURL(import.meta.url)],
|
||||
coverage: {
|
||||
reporter: 'json',
|
||||
ignoreEmptyLines: false,
|
||||
include: [
|
||||
'**/fixtures/src/empty-lines.ts',
|
||||
'**/fixtures/src/untested-file.ts',
|
||||
'**/fixtures/src/types-only.ts',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
;({ coveredFileLines, uncoveredFileLines } = await readCoverage())
|
||||
})
|
||||
|
||||
test('lines are included', async () => {
|
||||
for (const line of range(29)) {
|
||||
expect(coveredFileLines[line], `Line #${line}`).not.toBe(undefined)
|
||||
expect(coveredFileLines[line], `Line #${line}`).toBeTypeOf('number')
|
||||
}
|
||||
|
||||
for (const lines of [range(37), range(4, { base: 44 })]) {
|
||||
for (const line of lines) {
|
||||
expect(uncoveredFileLines[line], `Line #${line}`).not.toBe(undefined)
|
||||
expect(uncoveredFileLines[line], `Line #${line}`).toBeTypeOf('number')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('lines with ignore hints are ignored', () => {
|
||||
for (const line of range(6, { base: 38 })) {
|
||||
expect(uncoveredFileLines[line], `Line #${line}`).toBe(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('ignore empty lines', () => {
|
||||
let coveredFileLines: LineCoverage
|
||||
let uncoveredFileLines: LineCoverage
|
||||
let typesOnlyFileLines: LineCoverage
|
||||
|
||||
beforeAll(async () => {
|
||||
await runVitest({
|
||||
include: [normalizeURL(import.meta.url)],
|
||||
coverage: {
|
||||
reporter: 'json',
|
||||
include: [
|
||||
'**/fixtures/src/empty-lines.ts',
|
||||
'**/fixtures/src/untested-file.ts',
|
||||
'**/fixtures/src/types-only.ts',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
;({ coveredFileLines, uncoveredFileLines, typesOnlyFileLines } = await readCoverage())
|
||||
})
|
||||
|
||||
test('file containing only types has no uncovered lines', () => {
|
||||
expect(typesOnlyFileLines[1]).toBe(undefined)
|
||||
expect(typesOnlyFileLines[2]).toBe(undefined)
|
||||
expect(typesOnlyFileLines[3]).toBe(undefined)
|
||||
})
|
||||
|
||||
test('empty lines are ignored', async () => {
|
||||
expect(coveredFileLines[12]).toBe(undefined)
|
||||
expect(coveredFileLines[14]).toBe(undefined)
|
||||
expect(coveredFileLines[19]).toBe(undefined)
|
||||
expect(coveredFileLines[27]).toBe(undefined)
|
||||
expect(coveredFileLines[30]).toBe(undefined)
|
||||
|
||||
expect(uncoveredFileLines[5]).toBe(undefined)
|
||||
expect(uncoveredFileLines[7]).toBe(undefined)
|
||||
})
|
||||
|
||||
test('comments are ignored', async () => {
|
||||
expect(coveredFileLines[1]).toBe(undefined)
|
||||
expect(coveredFileLines[3]).toBe(undefined)
|
||||
expect(coveredFileLines[4]).toBe(undefined)
|
||||
expect(coveredFileLines[5]).toBe(undefined)
|
||||
expect(coveredFileLines[6]).toBe(undefined)
|
||||
expect(coveredFileLines[7]).toBe(undefined)
|
||||
expect(coveredFileLines[9]).toBe(undefined)
|
||||
expect(coveredFileLines[16]).toBe(undefined)
|
||||
|
||||
expect(uncoveredFileLines[1]).toBe(undefined)
|
||||
expect(uncoveredFileLines[2]).toBe(undefined)
|
||||
expect(uncoveredFileLines[3]).toBe(undefined)
|
||||
expect(uncoveredFileLines[4]).toBe(undefined)
|
||||
expect(uncoveredFileLines[6]).toBe(undefined)
|
||||
expect(uncoveredFileLines[13]).toBe(undefined)
|
||||
expect(uncoveredFileLines[20]).toBe(undefined)
|
||||
expect(uncoveredFileLines[34]).toBe(undefined)
|
||||
expect(uncoveredFileLines[45]).toBe(undefined)
|
||||
})
|
||||
|
||||
test('ignore hints are ignored', () => {
|
||||
expect(uncoveredFileLines[38]).toBe(undefined)
|
||||
expect(uncoveredFileLines[39]).toBe(undefined)
|
||||
expect(uncoveredFileLines[40]).toBe(undefined)
|
||||
expect(uncoveredFileLines[41]).toBe(undefined)
|
||||
expect(uncoveredFileLines[42]).toBe(undefined)
|
||||
expect(uncoveredFileLines[43]).toBe(undefined)
|
||||
})
|
||||
|
||||
test('typescript types are ignored', () => {
|
||||
expect(coveredFileLines[13]).toBe(undefined)
|
||||
expect(coveredFileLines[20]).toBe(undefined)
|
||||
expect(coveredFileLines[21]).toBe(undefined)
|
||||
expect(coveredFileLines[22]).toBe(undefined)
|
||||
expect(coveredFileLines[23]).toBe(undefined)
|
||||
expect(coveredFileLines[24]).toBe(undefined)
|
||||
expect(coveredFileLines[25]).toBe(undefined)
|
||||
expect(coveredFileLines[26]).toBe(undefined)
|
||||
|
||||
expect(uncoveredFileLines[17]).toBe(undefined)
|
||||
expect(uncoveredFileLines[25]).toBe(undefined)
|
||||
expect(uncoveredFileLines[26]).toBe(undefined)
|
||||
expect(uncoveredFileLines[27]).toBe(undefined)
|
||||
expect(uncoveredFileLines[28]).toBe(undefined)
|
||||
expect(uncoveredFileLines[29]).toBe(undefined)
|
||||
expect(uncoveredFileLines[30]).toBe(undefined)
|
||||
expect(uncoveredFileLines[31]).toBe(undefined)
|
||||
})
|
||||
|
||||
test('runtime code is not ignored', () => {
|
||||
// Covered
|
||||
expect(coveredFileLines[2]).toBe(1)
|
||||
expect(coveredFileLines[8]).toBe(1)
|
||||
expect(coveredFileLines[15]).toBe(1)
|
||||
expect(coveredFileLines[28]).toBe(1)
|
||||
|
||||
// Uncovered
|
||||
expect(coveredFileLines[10]).toBe(0)
|
||||
expect(coveredFileLines[17]).toBe(0)
|
||||
|
||||
// Uncovered
|
||||
expect(uncoveredFileLines[8]).toBe(0)
|
||||
expect(uncoveredFileLines[9]).toBe(0)
|
||||
expect(uncoveredFileLines[10]).toBe(0)
|
||||
expect(uncoveredFileLines[12]).toBe(0)
|
||||
expect(uncoveredFileLines[14]).toBe(0)
|
||||
expect(uncoveredFileLines[19]).toBe(0)
|
||||
expect(uncoveredFileLines[21]).toBe(0)
|
||||
expect(uncoveredFileLines[24]).toBe(0)
|
||||
expect(uncoveredFileLines[33]).toBe(0)
|
||||
expect(uncoveredFileLines[35]).toBe(0)
|
||||
expect(uncoveredFileLines[46]).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
coverageTest('cover some lines', () => {
|
||||
expect(add(10, 20)).toBe(30)
|
||||
})
|
||||
|
||||
async function readCoverage() {
|
||||
const coverageMap = await readCoverageMap()
|
||||
|
||||
const coveredFileLines = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/empty-lines.ts').getLineCoverage() as LineCoverage
|
||||
const uncoveredFileLines = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/untested-file.ts').getLineCoverage() as LineCoverage
|
||||
const typesOnlyFileLines = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/types-only.ts').getLineCoverage() as LineCoverage
|
||||
|
||||
return { coveredFileLines, uncoveredFileLines, typesOnlyFileLines }
|
||||
}
|
||||
|
||||
function range(count: number, options: { base: number } = { base: 1 }) {
|
||||
return Array.from({ length: count }).fill(0).map((_, i) => options.base + i)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { expect } from 'vitest'
|
||||
import { coverageTest, isExperimentalV8Provider, isV8Provider, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { coverageTest, isV8Provider, normalizeURL, readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('does not crash when file outside Vite is loaded (#5639)', async () => {
|
||||
await runVitest({
|
||||
@ -11,7 +11,7 @@ test('does not crash when file outside Vite is loaded (#5639)', async () => {
|
||||
const coverageMap = await readCoverageMap()
|
||||
const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/load-outside-vite.cjs')
|
||||
|
||||
if (isV8Provider() || isExperimentalV8Provider()) {
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "0/0 (100%)",
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect } from 'vitest'
|
||||
import { isExperimentalV8Provider, isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('ignore hints work', async () => {
|
||||
await runVitest({
|
||||
@ -20,10 +20,6 @@ test('ignore hints work', async () => {
|
||||
expect(lines[12]).toBeGreaterThanOrEqual(1)
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(lines[15]).toBeUndefined()
|
||||
expect(lines[18]).toBeGreaterThanOrEqual(1)
|
||||
}
|
||||
else if (isExperimentalV8Provider()) {
|
||||
expect(lines[15]).toBeUndefined()
|
||||
expect(lines[18]).toBeUndefined()
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('in-source tests work', async () => {
|
||||
const { stdout } = await runVitest({
|
||||
@ -25,31 +25,14 @@ test('in-source tests work', async () => {
|
||||
// If-branch is not taken - makes sure source maps are correct in in-source testing too
|
||||
expect(fileCoverage.getUncoveredLines()).toContain('5')
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "2/2 (100%)",
|
||||
"lines": "10/12 (83.33%)",
|
||||
"statements": "10/12 (83.33%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "1/1 (100%)",
|
||||
"lines": "2/3 (66.66%)",
|
||||
"statements": "2/3 (66.66%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// v8-to-istanbul cannot exclude whole if-block
|
||||
if (isV8Provider()) {
|
||||
return
|
||||
}
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "1/1 (100%)",
|
||||
"lines": "2/3 (66.66%)",
|
||||
"statements": "2/3 (66.66%)",
|
||||
}
|
||||
`)
|
||||
|
||||
// The "customNamedTestFunction" should be excluded by auto-generated ignore hints
|
||||
expect(functions).toMatchInlineSnapshot(`
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { TestSpecification } from 'vitest/node'
|
||||
import { expect, test } from 'vitest'
|
||||
import { formatSummary, isV8Provider, readCoverageMap, runVitest } from '../utils'
|
||||
import { formatSummary, readCoverageMap, runVitest } from '../utils'
|
||||
|
||||
const pools = ['forks']
|
||||
|
||||
@ -39,39 +39,20 @@ for (const isolate of [true, false]) {
|
||||
[math.path]: formatSummary(math.toSummary()),
|
||||
}
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(summary).toStrictEqual({
|
||||
'<process-cwd>/fixtures/src/branch.ts': {
|
||||
branches: '3/3 (100%)',
|
||||
functions: '1/1 (100%)',
|
||||
lines: '6/6 (100%)',
|
||||
statements: '6/6 (100%)',
|
||||
},
|
||||
'<process-cwd>/fixtures/src/math.ts': {
|
||||
branches: '4/4 (100%)',
|
||||
functions: '4/4 (100%)',
|
||||
lines: '12/12 (100%)',
|
||||
statements: '12/12 (100%)',
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
expect(summary).toStrictEqual({
|
||||
'<process-cwd>/fixtures/src/branch.ts': {
|
||||
branches: '2/2 (100%)',
|
||||
functions: '1/1 (100%)',
|
||||
lines: '4/4 (100%)',
|
||||
statements: '4/4 (100%)',
|
||||
},
|
||||
'<process-cwd>/fixtures/src/math.ts': {
|
||||
branches: '0/0 (100%)',
|
||||
functions: '4/4 (100%)',
|
||||
lines: '4/4 (100%)',
|
||||
statements: '4/4 (100%)',
|
||||
},
|
||||
expect(summary).toStrictEqual({
|
||||
'<process-cwd>/fixtures/src/branch.ts': {
|
||||
branches: '2/2 (100%)',
|
||||
functions: '1/1 (100%)',
|
||||
lines: '4/4 (100%)',
|
||||
statements: '4/4 (100%)',
|
||||
},
|
||||
)
|
||||
}
|
||||
'<process-cwd>/fixtures/src/math.ts': {
|
||||
branches: '0/0 (100%)',
|
||||
functions: '4/4 (100%)',
|
||||
lines: '4/4 (100%)',
|
||||
statements: '4/4 (100%)',
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { expect } from 'vitest'
|
||||
import { sum } from '../fixtures/src/math'
|
||||
import { captureStdout, coverageTest, isV8Provider, normalizeURL, runVitest, test } from '../utils'
|
||||
import { captureStdout, coverageTest, normalizeURL, runVitest, test } from '../utils'
|
||||
|
||||
test('report is not generated when tests fail', async () => {
|
||||
const stdout = captureStdout()
|
||||
@ -29,28 +29,15 @@ test('report is generated when tests fail and { reportOnFailure: true }', async
|
||||
},
|
||||
}, { throwOnError: false })
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(stdout()).toMatchInlineSnapshot(`
|
||||
"----------|---------|----------|---------|---------|-------------------
|
||||
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
All files | 50 | 100 | 25 | 50 |
|
||||
math.ts | 50 | 100 | 25 | 50 | 6-7,10-11,14-15
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
"
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(stdout()).toMatchInlineSnapshot(`
|
||||
"----------|---------|----------|---------|---------|-------------------
|
||||
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
All files | 25 | 100 | 25 | 25 |
|
||||
math.ts | 25 | 100 | 25 | 25 | 6-14
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
"
|
||||
`)
|
||||
}
|
||||
expect(stdout()).toMatchInlineSnapshot(`
|
||||
"----------|---------|----------|---------|---------|-------------------
|
||||
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
All files | 25 | 100 | 25 | 25 |
|
||||
math.ts | 25 | 100 | 25 | 25 | 6-14
|
||||
----------|---------|----------|---------|---------|-------------------
|
||||
"
|
||||
`)
|
||||
|
||||
expect(exitCode).toBe(1)
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import libCoverage from 'istanbul-lib-coverage'
|
||||
import { expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageJson, runVitest, test } from '../utils'
|
||||
import { readCoverageJson, runVitest, test } from '../utils'
|
||||
|
||||
test('pre-transpiled code with source maps to original (#5341)', async () => {
|
||||
await runVitest({
|
||||
@ -22,24 +22,12 @@ test('pre-transpiled code with source maps to original (#5341)', async () => {
|
||||
|
||||
const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/pre-transpiled/original.ts')
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "2/2 (100%)",
|
||||
"lines": "11/17 (64.7%)",
|
||||
"statements": "11/17 (64.7%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "3/6 (50%)",
|
||||
"functions": "2/2 (100%)",
|
||||
"lines": "6/8 (75%)",
|
||||
"statements": "6/8 (75%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "3/6 (50%)",
|
||||
"functions": "2/2 (100%)",
|
||||
"lines": "6/8 (75%)",
|
||||
"statements": "6/8 (75%)",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('coverage results matches snapshot', async () => {
|
||||
await runVitest({
|
||||
@ -17,52 +17,26 @@ test('coverage results matches snapshot', async () => {
|
||||
const coverageMap = await readCoverageMap()
|
||||
const fileCoverages = coverageMap.files().map(file => coverageMap.fileCoverageFor(file))
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/even.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "4/6 (66.66%)",
|
||||
"statements": "4/6 (66.66%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "6/12 (50%)",
|
||||
"statements": "6/12 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/untested-file.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/1 (100%)",
|
||||
"lines": "0/15 (0%)",
|
||||
"statements": "0/15 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/even.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/untested-file.ts": {
|
||||
"branches": "0/4 (0%)",
|
||||
"functions": "0/4 (0%)",
|
||||
"lines": "0/8 (0%)",
|
||||
"statements": "0/8 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/even.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/untested-file.ts": {
|
||||
"branches": "0/4 (0%)",
|
||||
"functions": "0/4 (0%)",
|
||||
"lines": "0/8 (0%)",
|
||||
"statements": "0/8 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { resolve } from 'node:path'
|
||||
import { expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('tests with multiple suites are covered (#3514)', async () => {
|
||||
const { stdout } = await runVitest({
|
||||
@ -29,24 +29,12 @@ test('tests with multiple suites are covered (#3514)', async () => {
|
||||
// Some valid coverage should be reported
|
||||
const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/math.ts')
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "6/12 (50%)",
|
||||
"statements": "6/12 (50%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(fileCoverage).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { expect, onTestFinished } from 'vitest'
|
||||
import { isV8Provider, runVitest, test } from '../utils'
|
||||
import { runVitest, test } from '../utils'
|
||||
|
||||
const config = 'fixtures/configs/vitest.config.thresholds-auto-update.ts'
|
||||
|
||||
@ -41,62 +41,32 @@ test('thresholds.autoUpdate updates thresholds', async () => {
|
||||
config,
|
||||
}, { throwOnError: false })
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(readConfig()).toMatchInlineSnapshot(`
|
||||
"import { defineConfig } from 'vitest/config'
|
||||
expect(readConfig()).toMatchInlineSnapshot(`
|
||||
"import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
thresholds: {
|
||||
autoUpdate: true,
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
thresholds: {
|
||||
autoUpdate: true,
|
||||
|
||||
// Global ones
|
||||
lines: 55.55,
|
||||
functions: 33.33,
|
||||
// Global ones
|
||||
lines: 33.33,
|
||||
functions: 33.33,
|
||||
branches: 100,
|
||||
statements: -4,
|
||||
|
||||
'**/src/math.ts': {
|
||||
branches: 100,
|
||||
statements: -8,
|
||||
|
||||
'**/src/math.ts': {
|
||||
branches: 100,
|
||||
functions: 25,
|
||||
lines: -6,
|
||||
statements: -6,
|
||||
}
|
||||
functions: 25,
|
||||
lines: -3,
|
||||
statements: -3,
|
||||
}
|
||||
}
|
||||
},
|
||||
})"
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(readConfig()).toMatchInlineSnapshot(`
|
||||
"import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
thresholds: {
|
||||
autoUpdate: true,
|
||||
|
||||
// Global ones
|
||||
lines: 33.33,
|
||||
functions: 33.33,
|
||||
branches: 100,
|
||||
statements: -4,
|
||||
|
||||
'**/src/math.ts': {
|
||||
branches: 100,
|
||||
functions: 25,
|
||||
lines: -3,
|
||||
statements: -3,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})"
|
||||
`)
|
||||
}
|
||||
}
|
||||
},
|
||||
})"
|
||||
`)
|
||||
})
|
||||
|
||||
function readConfig() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { expect } from 'vitest'
|
||||
import { sum } from '../fixtures/src/math'
|
||||
import { coverageTest, isV8Provider, normalizeURL, runVitest, test } from '../utils'
|
||||
import { coverageTest, normalizeURL, runVitest, test } from '../utils'
|
||||
|
||||
test('failing percentage thresholds', async () => {
|
||||
const { exitCode, stderr } = await runVitest({
|
||||
@ -18,12 +18,9 @@ test('failing percentage thresholds', async () => {
|
||||
},
|
||||
}, { throwOnError: false })
|
||||
|
||||
const lines = isV8Provider() ? '50%' : '25%'
|
||||
const statements = isV8Provider() ? '50%' : '25%'
|
||||
|
||||
expect(exitCode).toBe(1)
|
||||
expect(stderr).toContain(`ERROR: Coverage for lines (${lines}) does not meet "**/fixtures/src/math.ts" threshold (100%)`)
|
||||
expect(stderr).toContain(`ERROR: Coverage for statements (${statements}) does not meet "**/fixtures/src/math.ts" threshold (100%)`)
|
||||
expect(stderr).toContain('ERROR: Coverage for lines (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)')
|
||||
expect(stderr).toContain('ERROR: Coverage for statements (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)')
|
||||
expect(stderr).toContain('ERROR: Coverage for functions (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)')
|
||||
})
|
||||
|
||||
@ -36,7 +33,7 @@ test('failing absolute thresholds', async () => {
|
||||
'**/fixtures/src/math.ts': {
|
||||
branches: -1,
|
||||
functions: -2,
|
||||
lines: -5,
|
||||
lines: -2,
|
||||
statements: -1,
|
||||
},
|
||||
},
|
||||
@ -45,15 +42,9 @@ test('failing absolute thresholds', async () => {
|
||||
|
||||
expect(exitCode).toBe(1)
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(stderr).toContain('ERROR: Uncovered lines (6) exceed "**/fixtures/src/math.ts" threshold (5)')
|
||||
expect(stderr).toContain('ERROR: Uncovered functions (3) exceed "**/fixtures/src/math.ts" threshold (2)')
|
||||
expect(stderr).toContain('ERROR: Uncovered statements (6) exceed "**/fixtures/src/math.ts" threshold (1)')
|
||||
}
|
||||
else {
|
||||
expect(stderr).toContain('ERROR: Uncovered functions (3) exceed "**/fixtures/src/math.ts" threshold (2)')
|
||||
expect(stderr).toContain('ERROR: Uncovered statements (3) exceed "**/fixtures/src/math.ts" threshold (1)')
|
||||
}
|
||||
expect(stderr).toContain('ERROR: Uncovered functions (3) exceed "**/fixtures/src/math.ts" threshold (2)')
|
||||
expect(stderr).toContain('ERROR: Uncovered statements (3) exceed "**/fixtures/src/math.ts" threshold (1)')
|
||||
expect(stderr).toContain('ERROR: Uncovered lines (3) exceed "**/fixtures/src/math.ts" threshold (2)')
|
||||
})
|
||||
|
||||
coverageTest('cover some lines, but not too much', () => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { expect } from 'vitest'
|
||||
import { isEven, isOdd } from '../fixtures/src/even'
|
||||
import { sum } from '../fixtures/src/math'
|
||||
import { coverageTest, isV8Provider, normalizeURL, runVitest, test } from '../utils'
|
||||
import { coverageTest, normalizeURL, runVitest, test } from '../utils'
|
||||
|
||||
test('threshold glob patterns count in global coverage', async () => {
|
||||
await runVitest({
|
||||
@ -14,8 +14,8 @@ test('threshold glob patterns count in global coverage', async () => {
|
||||
thresholds: {
|
||||
'branches': 100,
|
||||
'functions': 50,
|
||||
'lines': isV8Provider() ? 66 : 50,
|
||||
'statements': isV8Provider() ? 66 : 50,
|
||||
'lines': 50,
|
||||
'statements': 50,
|
||||
|
||||
'**/fixtures/src/even.ts': {
|
||||
branches: 100,
|
||||
@ -49,22 +49,12 @@ test('{ thresholds: { 100: true } } on glob pattern', async () => {
|
||||
|
||||
expect(exitCode).toBe(1)
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(stderr).toMatchInlineSnapshot(`
|
||||
"ERROR: Coverage for lines (50%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for functions (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for statements (50%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
"
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(stderr).toMatchInlineSnapshot(`
|
||||
"ERROR: Coverage for lines (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for functions (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for statements (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
"
|
||||
`)
|
||||
}
|
||||
expect(stderr).toMatchInlineSnapshot(`
|
||||
"ERROR: Coverage for lines (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for functions (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
ERROR: Coverage for statements (25%) does not meet "**/fixtures/src/math.ts" threshold (100%)
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
coverageTest('cover some lines, but not too much', () => {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { readdirSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { beforeAll, expect } from 'vitest'
|
||||
import { rolldownVersion } from 'vitest/node'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
beforeAll(async () => {
|
||||
await runVitest({
|
||||
@ -24,34 +23,12 @@ test('files should not contain query parameters', () => {
|
||||
test('coverage results matches snapshot', async () => {
|
||||
const coverageMap = await readCoverageMap()
|
||||
|
||||
if (isV8Provider() && !rolldownVersion) {
|
||||
expect(coverageMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "5/7 (71.42%)",
|
||||
"functions": "3/5 (60%)",
|
||||
"lines": "36/45 (80%)",
|
||||
"statements": "36/45 (80%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
else if (isV8Provider() && rolldownVersion) {
|
||||
expect(coverageMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "7/9 (77.77%)",
|
||||
"functions": "4/6 (66.66%)",
|
||||
"lines": "36/45 (80%)",
|
||||
"statements": "36/45 (80%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(coverageMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "6/8 (75%)",
|
||||
"functions": "5/7 (71.42%)",
|
||||
"lines": "13/16 (81.25%)",
|
||||
"statements": "14/17 (82.35%)",
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(coverageMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
"branches": "6/8 (75%)",
|
||||
"functions": "5/7 (71.42%)",
|
||||
"lines": "13/16 (81.25%)",
|
||||
"statements": "14/17 (82.35%)",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect } from 'vitest'
|
||||
import { formatSummary, isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { formatSummary, readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('web worker coverage is correct', async () => {
|
||||
await runVitest({
|
||||
@ -28,40 +28,20 @@ test('web worker coverage is correct', async () => {
|
||||
}
|
||||
|
||||
// Check HTML report if these change unexpectedly
|
||||
if (isV8Provider()) {
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/worker-wrapper.ts": {
|
||||
"branches": "3/3 (100%)",
|
||||
"functions": "2/4 (50%)",
|
||||
"lines": "18/22 (81.81%)",
|
||||
"statements": "18/22 (81.81%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/worker.ts": {
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "2/3 (66.66%)",
|
||||
"lines": "11/19 (57.89%)",
|
||||
"statements": "11/19 (57.89%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/worker-wrapper.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "3/5 (60%)",
|
||||
"lines": "9/11 (81.81%)",
|
||||
"statements": "9/11 (81.81%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/worker.ts": {
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "2/3 (66.66%)",
|
||||
"lines": "7/12 (58.33%)",
|
||||
"statements": "7/12 (58.33%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(summary).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/worker-wrapper.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "3/5 (60%)",
|
||||
"lines": "9/11 (81.81%)",
|
||||
"statements": "9/11 (81.81%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/worker.ts": {
|
||||
"branches": "2/4 (50%)",
|
||||
"functions": "2/3 (66.66%)",
|
||||
"lines": "7/12 (58.33%)",
|
||||
"statements": "7/12 (58.33%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect } from 'vitest'
|
||||
import { isV8Provider, readCoverageMap, runVitest, test } from '../utils'
|
||||
import { readCoverageMap, runVitest, test } from '../utils'
|
||||
|
||||
test('uncovered files that require custom transform', async () => {
|
||||
await runVitest({
|
||||
@ -26,76 +26,38 @@ test('uncovered files that require custom transform', async () => {
|
||||
|
||||
const fileCoverages = coverageMap.files().map(file => coverageMap.fileCoverageFor(file))
|
||||
|
||||
if (isV8Provider()) {
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/covered.custom-1": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "2/3 (66.66%)",
|
||||
"statements": "2/3 (66.66%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "6/12 (50%)",
|
||||
"statements": "6/12 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/uncovered.custom-1": {
|
||||
"branches": "0/1 (0%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/2 (0%)",
|
||||
"statements": "0/2 (0%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/covered.custom-2": {
|
||||
"branches": "1/1 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "2/3 (66.66%)",
|
||||
"statements": "2/3 (66.66%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/uncovered.custom-2": {
|
||||
"branches": "0/1 (0%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/2 (0%)",
|
||||
"statements": "0/2 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
else {
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/covered.custom-1": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/uncovered.custom-1": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/covered.custom-2": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/uncovered.custom-2": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
}
|
||||
expect(fileCoverages).toMatchInlineSnapshot(`
|
||||
{
|
||||
"<process-cwd>/fixtures/src/covered.custom-1": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/math.ts": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/4 (25%)",
|
||||
"lines": "1/4 (25%)",
|
||||
"statements": "1/4 (25%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/src/uncovered.custom-1": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/covered.custom-2": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "1/2 (50%)",
|
||||
"lines": "1/2 (50%)",
|
||||
"statements": "1/2 (50%)",
|
||||
},
|
||||
"<process-cwd>/fixtures/workspaces/custom-2/src/uncovered.custom-2": {
|
||||
"branches": "0/0 (100%)",
|
||||
"functions": "0/1 (0%)",
|
||||
"lines": "0/1 (0%)",
|
||||
"statements": "0/1 (0%)",
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -46,8 +46,7 @@ export async function runVitest(config: UserConfig, options = { throwOnError: tr
|
||||
enabled: true,
|
||||
reporter: [],
|
||||
...config.coverage,
|
||||
provider: provider === 'v8-ast-aware' ? 'v8' : provider,
|
||||
experimentalAstAwareRemapping: provider === 'v8-ast-aware',
|
||||
provider,
|
||||
customProviderModule: provider === 'custom' ? 'fixtures/custom-provider' : undefined,
|
||||
},
|
||||
browser: {
|
||||
@ -110,10 +109,6 @@ export function isV8Provider() {
|
||||
return process.env.COVERAGE_PROVIDER === 'v8'
|
||||
}
|
||||
|
||||
export function isExperimentalV8Provider() {
|
||||
return process.env.COVERAGE_PROVIDER === 'v8-ast-aware'
|
||||
}
|
||||
|
||||
export function isBrowser() {
|
||||
return process.env.COVERAGE_BROWSER === 'true'
|
||||
}
|
||||
|
||||
@ -31,25 +31,6 @@ export default defineWorkspace([
|
||||
},
|
||||
},
|
||||
|
||||
// Test cases for experimental AST aware v8-provider
|
||||
{
|
||||
test: {
|
||||
...config.test,
|
||||
name: 'v8-ast-aware',
|
||||
env: { COVERAGE_PROVIDER: 'v8-ast-aware' },
|
||||
|
||||
// Intentionally run Istanbul tests too
|
||||
include: [GENERIC_TESTS, ISTANBUL_TESTS, V8_TESTS],
|
||||
exclude: [
|
||||
UNIT_TESTS,
|
||||
CUSTOM_TESTS,
|
||||
BROWSER_TESTS,
|
||||
// Not using original v8-to-istanbul that has patch applied: github.com/istanbuljs/v8-to-istanbul/pull/244
|
||||
'test/empty-lines.v8.test.ts',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Test cases for istanbul-provider
|
||||
{
|
||||
test: {
|
||||
@ -108,7 +89,7 @@ export default defineWorkspace([
|
||||
test: {
|
||||
...config.test,
|
||||
name: { label: 'v8-browser', color: 'red' },
|
||||
env: { COVERAGE_PROVIDER: 'v8-ast-aware', COVERAGE_BROWSER: 'true' },
|
||||
env: { COVERAGE_PROVIDER: 'v8', COVERAGE_BROWSER: 'true' },
|
||||
include: [
|
||||
BROWSER_TESTS,
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user