Skip to content

Commit 2297d93

Browse files
authored
fix(coverage): v8 browser memory leak between test files (#11466)
1 parent b618afd commit 2297d93

7 files changed

Lines changed: 73 additions & 2 deletions

File tree

‎packages/coverage-v8/src/browser.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ const mod: CoverageProviderModule = {
2323
return triggerCommand('__vitest_takeV8Coverage', [window.location.href])
2424
},
2525

26-
stopCoverage() {
27-
// Browser mode should not stop coverage as same V8 instance is shared between tests
26+
async stopCoverage({ isolate }) {
27+
if (isolate === false) {
28+
return
29+
}
30+
31+
await triggerCommand('__vitest_stopV8Coverage')
2832
},
2933

3034
async getProvider(): Promise<V8CoverageProvider> {

‎packages/coverage-v8/src/commands.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@ import { resolve } from 'pathe'
99
export const commands: Record<string, BrowserCommand<any[]>> = {
1010
startV8Coverage,
1111
takeV8Coverage,
12+
stopV8Coverage,
1213
}
1314

1415
async function startV8Coverage(context: BrowserCommandContext): Promise<void> {
1516
const session: CDPSession = await context.__ensureCDPHandler()
17+
18+
// Previous test file's modules must be collected, otherwise V8 reuses their compiled code without block coverage
19+
await session.send('HeapProfiler.collectGarbage')
20+
1621
await session.send('Profiler.enable')
1722
await session.send('Profiler.startPreciseCoverage', {
1823
callCount: true,
1924
detailed: true,
2025
})
2126
}
2227

28+
async function stopV8Coverage(context: BrowserCommandContext): Promise<void> {
29+
const session: CDPSession = await context.__ensureCDPHandler()
30+
await session.send('Profiler.stopPreciseCoverage')
31+
await session.send('Profiler.disable')
32+
}
33+
2334
async function takeV8Coverage(context: BrowserCommandContext, pageUrl: string): Promise<string> {
2435
const session: CDPSession = await context.__ensureCDPHandler()
2536
const coverage = await session.send('Profiler.takePreciseCoverage')
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { test } from "vitest";
2+
import { commands } from "vitest/browser";
3+
4+
test("report live documents", async () => {
5+
await (commands as any).countLiveDocuments();
6+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { test } from "vitest";
2+
import { commands } from "vitest/browser";
3+
4+
test("report live documents", async () => {
5+
await (commands as any).countLiveDocuments();
6+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { test } from "vitest";
2+
import { commands } from "vitest/browser";
3+
4+
test("report live documents", async () => {
5+
await (commands as any).countLiveDocuments();
6+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { test } from "vitest";
2+
import { commands } from "vitest/browser";
3+
4+
test("report live documents", async () => {
5+
await (commands as any).countLiveDocuments();
6+
});

‎test/coverage-test/test/isolation.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,38 @@ for (const isolate of [true, false]) {
5656
}
5757
}
5858

59+
/*
60+
* This is mostly relevant for V8 provider which enables Profiler,
61+
* but we'll test Istanbul too.
62+
*/
63+
test('browser mode tab isolation (#11447)', async ({ skip }) => {
64+
skip(process.env.COVERAGE_BROWSER !== 'true', 'only relevant for browser mode')
65+
66+
const liveDocuments: number[] = []
67+
68+
await runVitest({
69+
include: ['fixtures/test/memory-leak-*-fixture.test.ts'],
70+
fileParallelism: false,
71+
coverage: { reporter: 'json' },
72+
browser: {
73+
commands: {
74+
async countLiveDocuments(context) {
75+
const session = await context.page.context().newCDPSession(context.page)
76+
await session.send('HeapProfiler.collectGarbage')
77+
78+
const { documents } = await session.send('Memory.getDOMCounters')
79+
liveDocuments.push(documents)
80+
81+
await session.detach()
82+
},
83+
},
84+
},
85+
})
86+
87+
expect(liveDocuments).toHaveLength(4)
88+
expect(new Set(liveDocuments).size).toBe(1)
89+
})
90+
5991
class Sorter {
6092
sort(files: TestSpecification[]) {
6193
return files.sort((a) => {

0 commit comments

Comments
 (0)