Skip to content

Commit 426ce6d

Browse files
authored
fix(coverage): correct coverage when isolate: false is used (#6957)
1 parent fa75409 commit 426ce6d

File tree

16 files changed

+193
-51
lines changed

16 files changed

+193
-51
lines changed

packages/browser/src/client/tester/tester.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
122122
try {
123123
await Promise.all([
124124
setupCommonEnv(config),
125-
startCoverageInsideWorker(config.coverage, executor),
125+
startCoverageInsideWorker(config.coverage, executor, { isolate: config.browser.isolate }),
126126
(async () => {
127127
const VitestIndex = await import('vitest')
128128
Object.defineProperty(window, '__vitest_index__', {
@@ -160,7 +160,7 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
160160
}, 'Cleanup Error')
161161
}
162162
state.environmentTeardownRun = true
163-
await stopCoverageInsideWorker(config.coverage, executor).catch((error) => {
163+
await stopCoverageInsideWorker(config.coverage, executor, { isolate: config.browser.isolate }).catch((error) => {
164164
client.rpc.onUnhandledError({
165165
name: error.name,
166166
message: error.message,
Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
1+
import type { CoverageMapData } from 'istanbul-lib-coverage'
2+
import type { CoverageProviderModule } from 'vitest/node'
13
import type { IstanbulCoverageProvider } from './provider'
24
import { COVERAGE_STORE_KEY } from './constants'
35

4-
export async function getProvider(): Promise<IstanbulCoverageProvider> {
5-
// to not bundle the provider
6-
const providerPath = './provider.js'
7-
const { IstanbulCoverageProvider } = (await import(
8-
/* @vite-ignore */
9-
providerPath
10-
)) as typeof import('./provider')
11-
return new IstanbulCoverageProvider()
12-
}
13-
14-
export function takeCoverage(): any {
15-
// @ts-expect-error -- untyped global
16-
const coverage = globalThis[COVERAGE_STORE_KEY]
6+
export default {
7+
takeCoverage() {
8+
// @ts-expect-error -- untyped global
9+
return globalThis[COVERAGE_STORE_KEY]
10+
},
1711

1812
// Reset coverage map to prevent duplicate results if this is called twice in row
19-
// @ts-expect-error -- untyped global
20-
globalThis[COVERAGE_STORE_KEY] = {}
13+
startCoverage() {
14+
// @ts-expect-error -- untyped global
15+
const coverageMap = globalThis[COVERAGE_STORE_KEY] as CoverageMapData
16+
17+
// When isolated, there are no previous results
18+
if (!coverageMap) {
19+
return
20+
}
21+
22+
for (const filename in coverageMap) {
23+
const branches = coverageMap[filename].b
24+
25+
for (const key in branches) {
26+
branches[key] = branches[key].map(() => 0)
27+
}
28+
29+
for (const metric of ['f', 's'] as const) {
30+
const entry = coverageMap[filename][metric]
2131

22-
return coverage
23-
}
32+
for (const key in entry) {
33+
entry[key] = 0
34+
}
35+
}
36+
}
37+
},
2438

25-
const _default: {
26-
getProvider: () => Promise<IstanbulCoverageProvider>
27-
takeCoverage: () => any
28-
} = {
29-
getProvider,
30-
takeCoverage,
31-
}
39+
async getProvider(): Promise<IstanbulCoverageProvider> {
40+
// to not bundle the provider
41+
const providerPath = './provider.js'
42+
const { IstanbulCoverageProvider } = (await import(
43+
/* @vite-ignore */
44+
providerPath
45+
)) as typeof import('./provider')
3246

33-
export default _default
47+
return new IstanbulCoverageProvider()
48+
},
49+
} satisfies CoverageProviderModule

packages/coverage-v8/src/browser.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
import type { CoverageProviderModule } from 'vitest/node'
12
import type { V8CoverageProvider } from './provider'
23
import { cdp } from '@vitest/browser/context'
34
import { loadProvider } from './load-provider'
45

56
const session = cdp()
7+
let enabled = false
68

79
type ScriptCoverage = Awaited<ReturnType<typeof session.send<'Profiler.takePreciseCoverage'>>>
810

911
export default {
1012
async startCoverage() {
13+
if (enabled) {
14+
return
15+
}
16+
17+
enabled = true
18+
1119
await session.send('Profiler.enable')
1220
await session.send('Profiler.startPreciseCoverage', {
1321
callCount: true,
@@ -32,15 +40,14 @@ export default {
3240
return { result }
3341
},
3442

35-
async stopCoverage() {
36-
await session.send('Profiler.stopPreciseCoverage')
37-
await session.send('Profiler.disable')
43+
stopCoverage() {
44+
// Browser mode should not stop coverage as same V8 instance is shared between tests
3845
},
3946

4047
async getProvider(): Promise<V8CoverageProvider> {
4148
return loadProvider()
4249
},
43-
}
50+
} satisfies CoverageProviderModule
4451

4552
function filterResult(coverage: ScriptCoverage['result'][number]): boolean {
4653
if (!coverage.url.startsWith(window.location.origin)) {

packages/coverage-v8/src/index.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1+
import type { CoverageProviderModule } from 'vitest/node'
12
import type { V8CoverageProvider } from './provider'
23
import inspector, { type Profiler } from 'node:inspector'
34
import { provider } from 'std-env'
45
import { loadProvider } from './load-provider'
56

67
const session = new inspector.Session()
8+
let enabled = false
79

810
export default {
9-
startCoverage(): void {
11+
startCoverage({ isolate }) {
12+
if (isolate === false && enabled) {
13+
return
14+
}
15+
16+
enabled = true
17+
1018
session.connect()
1119
session.post('Profiler.enable')
1220
session.post('Profiler.startPreciseCoverage', {
@@ -34,7 +42,11 @@ export default {
3442
})
3543
},
3644

37-
stopCoverage(): void {
45+
stopCoverage({ isolate }) {
46+
if (isolate === false) {
47+
return
48+
}
49+
3850
session.post('Profiler.stopPreciseCoverage')
3951
session.post('Profiler.disable')
4052
session.disconnect()
@@ -43,7 +55,7 @@ export default {
4355
async getProvider(): Promise<V8CoverageProvider> {
4456
return loadProvider()
4557
},
46-
}
58+
} satisfies CoverageProviderModule
4759

4860
function filterResult(coverage: Profiler.ScriptCoverage): boolean {
4961
if (!coverage.url.startsWith('file://')) {

packages/vitest/src/integrations/coverage.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ export async function getCoverageProvider(
7979
export async function startCoverageInsideWorker(
8080
options: SerializedCoverageConfig | undefined,
8181
loader: Loader,
82+
runtimeOptions: { isolate: boolean },
8283
) {
8384
const coverageModule = await resolveCoverageProviderModule(options, loader)
8485

8586
if (coverageModule) {
86-
return coverageModule.startCoverage?.()
87+
return coverageModule.startCoverage?.(runtimeOptions)
8788
}
8889

8990
return null
@@ -105,11 +106,12 @@ export async function takeCoverageInsideWorker(
105106
export async function stopCoverageInsideWorker(
106107
options: SerializedCoverageConfig | undefined,
107108
loader: Loader,
109+
runtimeOptions: { isolate: boolean },
108110
) {
109111
const coverageModule = await resolveCoverageProviderModule(options, loader)
110112

111113
if (coverageModule) {
112-
return coverageModule.stopCoverage?.()
114+
return coverageModule.stopCoverage?.(runtimeOptions)
113115
}
114116

115117
return null

packages/vitest/src/node/types/coverage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export interface CoverageProviderModule {
6666
/**
6767
* Executed before tests are run in the worker thread.
6868
*/
69-
startCoverage?: () => unknown | Promise<unknown>
69+
startCoverage?: (runtimeOptions: { isolate: boolean }) => unknown | Promise<unknown>
7070

7171
/**
7272
* Executed on after each run in the worker thread. Possible to return a payload passed to the provider
@@ -76,7 +76,7 @@ export interface CoverageProviderModule {
7676
/**
7777
* Executed after all tests have been run in the worker thread.
7878
*/
79-
stopCoverage?: () => unknown | Promise<unknown>
79+
stopCoverage?: (runtimeOptions: { isolate: boolean }) => unknown | Promise<unknown>
8080
}
8181

8282
export type CoverageReporter = keyof ReportOptions | (string & {})

packages/vitest/src/runtime/runBaseTests.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ export async function run(
2525
): Promise<void> {
2626
const workerState = getWorkerState()
2727

28+
const isIsolatedThreads = config.pool === 'threads' && (config.poolOptions?.threads?.isolate ?? true)
29+
const isIsolatedForks = config.pool === 'forks' && (config.poolOptions?.forks?.isolate ?? true)
30+
const isolate = isIsolatedThreads || isIsolatedForks
31+
2832
await setupGlobalEnv(config, environment, executor)
29-
await startCoverageInsideWorker(config.coverage, executor)
33+
await startCoverageInsideWorker(config.coverage, executor, { isolate })
3034

3135
if (config.chaiConfig) {
3236
setupChaiConfig(config.chaiConfig)
@@ -50,14 +54,7 @@ export async function run(
5054
= performance.now() - workerState.durations.environment
5155

5256
for (const file of files) {
53-
const isIsolatedThreads
54-
= config.pool === 'threads'
55-
&& (config.poolOptions?.threads?.isolate ?? true)
56-
const isIsolatedForks
57-
= config.pool === 'forks'
58-
&& (config.poolOptions?.forks?.isolate ?? true)
59-
60-
if (isIsolatedThreads || isIsolatedForks) {
57+
if (isolate) {
6158
executor.mocker.reset()
6259
resetModules(workerState.moduleCache, true)
6360
}
@@ -77,7 +74,7 @@ export async function run(
7774
vi.restoreAllMocks()
7875
}
7976

80-
await stopCoverageInsideWorker(config.coverage, executor)
77+
await stopCoverageInsideWorker(config.coverage, executor, { isolate })
8178
},
8279
)
8380

packages/vitest/src/runtime/runVmTests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export async function run(
6262
getSourceMap: source => workerState.moduleCache.getSourceMap(source),
6363
})
6464

65-
await startCoverageInsideWorker(config.coverage, executor)
65+
await startCoverageInsideWorker(config.coverage, executor, { isolate: false })
6666

6767
if (config.chaiConfig) {
6868
setupChaiConfig(config.chaiConfig)
@@ -101,7 +101,7 @@ export async function run(
101101
vi.restoreAllMocks()
102102
}
103103

104-
await stopCoverageInsideWorker(config.coverage, executor)
104+
await stopCoverageInsideWorker(config.coverage, executor, { isolate: false })
105105
}
106106

107107
function resolveCss(mod: NodeJS.Module) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { beforeAll } from "vitest";
2+
import { branch } from "./src/branch";
3+
4+
beforeAll(() => {
5+
branch(1);
6+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const branch = async (a: number) => {
2+
if (a === 15) {
3+
return true;
4+
}
5+
6+
return false;
7+
};

0 commit comments

Comments
 (0)