Skip to content

Commit e3b7775

Browse files
authored
fix(coverage): prevent filtering out virtual files before remapping to sources (#8860)
1 parent c57511b commit e3b7775

File tree

7 files changed

+60
-26
lines changed

7 files changed

+60
-26
lines changed

packages/coverage-istanbul/src/provider.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { CoverageMap } from 'istanbul-lib-coverage'
22
import type { Instrumenter } from 'istanbul-lib-instrument'
33
import type { ProxifiedModule } from 'magicast'
44
import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, Vite, Vitest } from 'vitest/node'
5-
import { promises as fs } from 'node:fs'
5+
import { existsSync, promises as fs } from 'node:fs'
66
// @ts-expect-error missing types
77
import { defaults as istanbulDefaults } from '@istanbuljs/schema'
88
import createDebug from 'debug'
@@ -15,7 +15,6 @@ import { parseModule } from 'magicast'
1515
import c from 'tinyrainbow'
1616
import { BaseCoverageProvider } from 'vitest/coverage'
1717
import { isCSSRequest } from 'vitest/node'
18-
1918
import { version } from '../package.json' with { type: 'json' }
2019
import { COVERAGE_STORE_KEY } from './constants'
2120

@@ -118,9 +117,15 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider<ResolvedCover
118117
coverageMap.merge(await transformCoverage(uncoveredCoverage))
119118
}
120119

121-
if (this.options.excludeAfterRemap) {
122-
coverageMap.filter(filename => this.isIncluded(filename))
123-
}
120+
coverageMap.filter((filename) => {
121+
const exists = existsSync(filename)
122+
123+
if (this.options.excludeAfterRemap) {
124+
return exists && this.isIncluded(filename)
125+
}
126+
127+
return exists
128+
})
124129

125130
if (debug.enabled) {
126131
debug('Generate coverage total time %d ms', (performance.now() - start!).toFixed())

packages/coverage-v8/src/provider.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { CoverageMap } from 'istanbul-lib-coverage'
22
import type { ProxifiedModule } from 'magicast'
33
import type { Profiler } from 'node:inspector'
44
import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, TestProject, Vite, Vitest } from 'vitest/node'
5-
import { promises as fs } from 'node:fs'
5+
import { existsSync, promises as fs } from 'node:fs'
66
import { fileURLToPath } from 'node:url'
77
// @ts-expect-error -- untyped
88
import { mergeProcessCovs } from '@bcoe/v8-coverage'
@@ -86,9 +86,15 @@ export class V8CoverageProvider extends BaseCoverageProvider<ResolvedCoverageOpt
8686
coverageMap.merge(await transformCoverage(untestedCoverage))
8787
}
8888

89-
if (this.options.excludeAfterRemap) {
90-
coverageMap.filter(filename => this.isIncluded(filename))
91-
}
89+
coverageMap.filter((filename) => {
90+
const exists = existsSync(filename)
91+
92+
if (this.options.excludeAfterRemap) {
93+
return exists && this.isIncluded(filename)
94+
}
95+
96+
return exists
97+
})
9298

9399
if (debug.enabled) {
94100
debug(`Generate coverage total time ${(performance.now() - start!).toFixed()} ms`)

packages/vitest/src/node/coverage.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { SerializedCoverageConfig } from '../runtime/config'
66
import type { AfterSuiteRunMeta } from '../types/general'
77
import { existsSync, promises as fs, readdirSync, writeFileSync } from 'node:fs'
88
import path from 'node:path'
9-
import { cleanUrl, slash } from '@vitest/utils/helpers'
9+
import { slash } from '@vitest/utils/helpers'
1010
import { relative, resolve } from 'pathe'
1111
import pm from 'picomatch'
1212
import { glob } from 'tinyglobby'
@@ -161,19 +161,12 @@ export class BaseCoverageProvider<Options extends ResolvedCoverageOptions<'istan
161161
// By default `coverage.include` matches all files, except "coverage.exclude"
162162
const glob = this.options.include || '**'
163163

164-
let included = roots.some((root) => {
165-
const options: pm.PicomatchOptions = {
166-
contains: true,
167-
dot: true,
168-
cwd: root,
169-
ignore: this.options.exclude,
170-
}
171-
172-
return pm.isMatch(filename, glob, options)
164+
const included = pm.isMatch(filename, glob, {
165+
contains: true,
166+
dot: true,
167+
ignore: this.options.exclude,
173168
})
174169

175-
included &&= existsSync(cleanUrl(filename))
176-
177170
this.globCache.set(filename, included)
178171

179172
return included

test/coverage-test/fixtures/configs/vitest.config.virtual-files.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { readFileSync } from 'node:fs'
2+
import { resolve } from 'node:path'
13
import { Plugin, defineConfig, mergeConfig } from 'vitest/config'
4+
import { transformWithEsbuild } from 'vite'
25

36
import base from './vitest.config'
47

@@ -22,6 +25,10 @@ function VirtualFilesPlugin(): Plugin {
2225
if (id === '\0vitest-custom-virtual-file-2') {
2326
return 'src/\0vitest-custom-virtual-file-2.ts'
2427
}
28+
29+
if (id.includes('vitest-custom-virtual:math')) {
30+
return resolve(import.meta.dirname, "../src/vitest-custom-virtual:math")
31+
}
2532
},
2633
load(id) {
2734
if (id === 'src/virtual:vitest-custom-virtual-file-1.ts') {
@@ -38,6 +45,13 @@ function VirtualFilesPlugin(): Plugin {
3845
export default virtualFile;
3946
`
4047
}
48+
49+
if(id.includes("vitest-custom-virtual:math")) {
50+
const filename = resolve(import.meta.dirname, "../src/math.ts");
51+
const sources = readFileSync(filename, "utf8")
52+
53+
return transformWithEsbuild(sources, filename)
54+
}
4155
},
4256
}
43-
}
57+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// @ts-expect-error -- untyped virtual file provided by custom plugin
22
import virtualFile1 from 'virtual:vitest-custom-virtual-file-1'
33

4-
54
// @ts-expect-error -- untyped virtual file provided by custom plugin
65
import virtualFile2 from '\0vitest-custom-virtual-file-2'
76

7+
// @ts-expect-error -- untyped virtual file provided by custom plugin
8+
import * as virtualMath from 'vitest-custom-virtual:math'
9+
810
export function getVirtualFileImports() {
9-
return { virtualFile1, virtualFile2 }
11+
return { virtualFile1, virtualFile2, virtualMath }
1012
}

test/coverage-test/fixtures/test/virtual-files-fixture.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { expect, test } from 'vitest'
22
import { getVirtualFileImports} from '../src/virtual-files'
33

44
test("verify virtual files work", () => {
5-
const {virtualFile1, virtualFile2} = getVirtualFileImports()
5+
const {virtualFile1, virtualFile2, virtualMath} = getVirtualFileImports()
66

77
expect(virtualFile1).toBe('This file should be excluded from coverage report #1')
88
expect(virtualFile2).toBe('This file should be excluded from coverage report #2')
99

10-
})
10+
expect(virtualMath).toHaveProperty('sum')
11+
expect(virtualMath.sum(50, 65)).toBe(115)
12+
})

test/coverage-test/test/virtual-files.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@ test('virtual files should be excluded', async () => {
2626
// Vitest browser
2727
expect(file).not.toContain('\x00')
2828
}
29+
30+
expect(files).toContain('<process-cwd>/fixtures/src/math.ts')
31+
32+
const fileCoverage = coverageMap.fileCoverageFor('<process-cwd>/fixtures/src/math.ts')
33+
expect(fileCoverage).toMatchInlineSnapshot(`
34+
{
35+
"branches": "0/0 (100%)",
36+
"functions": "1/4 (25%)",
37+
"lines": "1/4 (25%)",
38+
"statements": "1/4 (25%)",
39+
}
40+
`)
2941
})

0 commit comments

Comments
 (0)