Skip to content

Commit

Permalink
feat(browser): support v8 coverage (#6273)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio authored Aug 12, 2024
1 parent 198a3e6 commit 34199bd
Show file tree
Hide file tree
Showing 28 changed files with 428 additions and 202 deletions.
19 changes: 6 additions & 13 deletions packages/browser/src/client/tester/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { loadDiffConfig, loadSnapshotSerializers, takeCoverageInsideWorker } fro
import { TraceMap, originalPositionFor } from 'vitest/utils'
import { page } from '@vitest/browser/context'
import { globalChannel } from '@vitest/browser/client'
import { importFs, importId } from '../utils'
import { executor } from '../utils'
import { VitestBrowserSnapshotEnvironment } from './snapshot'
import { rpc } from './rpc'
import type { VitestBrowserClientMocker } from './mocker'
Expand Down Expand Up @@ -91,7 +91,7 @@ export function createBrowserRunner(
if (coverage) {
await rpc().onAfterSuiteRun({
coverage,
transformMode: 'web',
transformMode: 'browser',
projectName: this.config.name,
})
}
Expand Down Expand Up @@ -148,27 +148,20 @@ export async function initiateRunner(
const runnerClass
= config.mode === 'test' ? VitestTestRunner : NodeBenchmarkRunner

const executeId = (id: string) => {
if (id[0] === '/' || id[1] === ':') {
return importFs(id)
}
return importId(id)
}

const BrowserRunner = createBrowserRunner(runnerClass, mocker, state, {
takeCoverage: () =>
takeCoverageInsideWorker(config.coverage, { executeId }),
takeCoverageInsideWorker(config.coverage, executor),
})
if (!config.snapshotOptions.snapshotEnvironment) {
config.snapshotOptions.snapshotEnvironment = new VitestBrowserSnapshotEnvironment()
}
const runner = new BrowserRunner({
config,
})
const executor = { executeId } as VitestExecutor

const [diffOptions] = await Promise.all([
loadDiffConfig(config, executor),
loadSnapshotSerializers(config, executor),
loadDiffConfig(config, executor as unknown as VitestExecutor),
loadSnapshotSerializers(config, executor as unknown as VitestExecutor),
])
runner.config.diffOptions = diffOptions
cachedRunner = runner
Expand Down
8 changes: 6 additions & 2 deletions packages/browser/src/client/tester/tester.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SpyModule, collectTests, setupCommonEnv, startTests } from 'vitest/browser'
import { SpyModule, collectTests, setupCommonEnv, startCoverageInsideWorker, startTests, stopCoverageInsideWorker } from 'vitest/browser'
import { page } from '@vitest/browser/context'
import { channel, client, onCancel } from '@vitest/browser/client'
import { getBrowserState, getConfig, getWorkerState } from '../utils'
import { executor, getBrowserState, getConfig, getWorkerState } from '../utils'
import { setupDialogsSpy } from './dialog'
import { setupConsoleLogSpy } from './logger'
import { createSafeRpc } from './rpc'
Expand Down Expand Up @@ -114,6 +114,8 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {

try {
await setupCommonEnv(config)
await startCoverageInsideWorker(config.coverage, executor)

for (const file of files) {
state.filepath = file

Expand All @@ -139,6 +141,8 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
}, 'Cleanup Error')
}
state.environmentTeardownRun = true
await stopCoverageInsideWorker(config.coverage, executor)

debug('finished running tests')
done(files)
}
Expand Down
11 changes: 11 additions & 0 deletions packages/browser/src/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export async function importFs(id: string) {
return getBrowserState().wrapModule(() => import(/* @vite-ignore */ name))
}

export const executor = {
isBrowser: true,

executeId: (id: string) => {
if (id[0] === '/' || id[1] === ':') {
return importFs(id)
}
return importId(id)
},
}

export function getConfig(): SerializedConfig {
return getBrowserState().config
}
Expand Down
5 changes: 3 additions & 2 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
return
}

if (transformMode !== 'web' && transformMode !== 'ssr') {
if (transformMode !== 'web' && transformMode !== 'ssr' && transformMode !== 'browser') {
throw new Error(`Invalid transform mode: ${transformMode}`)
}

let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT)

if (!entry) {
entry = { web: [], ssr: [] }
entry = { web: [], ssr: [], browser: [] }
this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry)
}

Expand Down Expand Up @@ -251,6 +251,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
for (const filenames of [
coveragePerProject.ssr,
coveragePerProject.web,
coveragePerProject.browser,
]) {
const coverageMapByTransformMode = libCoverage.createCoverageMap({})

Expand Down
11 changes: 11 additions & 0 deletions packages/coverage-v8/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./browser": {
"types": "./dist/browser.d.ts",
"default": "./dist/browser.js"
},
"./*": "./*"
},
"main": "./dist/index.js",
Expand All @@ -41,8 +45,14 @@
"dev": "rollup -c --watch --watch.include 'src/**'"
},
"peerDependencies": {
"@vitest/browser": "workspace:*",
"vitest": "workspace:*"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
},
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^0.2.3",
Expand All @@ -63,6 +73,7 @@
"@types/istanbul-lib-report": "^3.0.3",
"@types/istanbul-lib-source-maps": "^4.0.4",
"@types/istanbul-reports": "^3.0.4",
"@vitest/browser": "workspace:*",
"pathe": "^1.1.2",
"v8-to-istanbul": "^9.3.0",
"vite-node": "workspace:*",
Expand Down
1 change: 1 addition & 0 deletions packages/coverage-v8/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const pkg = require('./package.json')

const entries = {
index: 'src/index.ts',
browser: 'src/browser.ts',
provider: 'src/provider.ts',
}

Expand Down
63 changes: 63 additions & 0 deletions packages/coverage-v8/src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { cdp } from '@vitest/browser/context'
import type { V8CoverageProvider } from './provider'
import { loadProvider } from './load-provider'

const session = cdp()

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

export default {
async startCoverage() {
await session.send('Profiler.enable')
await session.send('Profiler.startPreciseCoverage', {
callCount: true,
detailed: true,
})
},

async takeCoverage(): Promise<{ result: any[] }> {
const coverage = await session.send('Profiler.takePreciseCoverage')
const result: typeof coverage.result = []

// Reduce amount of data sent over rpc by doing some early result filtering
for (const entry of coverage.result) {
if (filterResult(entry)) {
result.push({
...entry,
url: decodeURIComponent(entry.url.replace(window.location.origin, '')),
})
}
}

return { result }
},

async stopCoverage() {
await session.send('Profiler.stopPreciseCoverage')
await session.send('Profiler.disable')
},

async getProvider(): Promise<V8CoverageProvider> {
return loadProvider()
},
}

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

if (coverage.url.includes('/node_modules/')) {
return false
}

if (coverage.url.includes('__vitest_browser__')) {
return false
}

if (coverage.url.includes('__vitest__/assets')) {
return false
}

return true
}
57 changes: 46 additions & 11 deletions packages/coverage-v8/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,58 @@
import type { Profiler } from 'node:inspector'
import * as coverage from './takeCoverage'
import inspector, { type Profiler } from 'node:inspector'
import { provider } from 'std-env'
import type { V8CoverageProvider } from './provider'
import { loadProvider } from './load-provider'

const session = new inspector.Session()

export default {
startCoverage(): void {
return coverage.startCoverage()
session.connect()
session.post('Profiler.enable')
session.post('Profiler.startPreciseCoverage', {
callCount: true,
detailed: true,
})
},

takeCoverage(): Promise<{ result: Profiler.ScriptCoverage[] }> {
return coverage.takeCoverage()
return new Promise((resolve, reject) => {
session.post('Profiler.takePreciseCoverage', async (error, coverage) => {
if (error) {
return reject(error)
}

// Reduce amount of data sent over rpc by doing some early result filtering
const result = coverage.result.filter(filterResult)

resolve({ result })
})

if (provider === 'stackblitz') {
resolve({ result: [] })
}
})
},

stopCoverage(): void {
return coverage.stopCoverage()
session.post('Profiler.stopPreciseCoverage')
session.post('Profiler.disable')
session.disconnect()
},

async getProvider(): Promise<V8CoverageProvider> {
// to not bundle the provider
const name = './provider.js'
const { V8CoverageProvider } = (await import(
name
)) as typeof import('./provider')
return new V8CoverageProvider()
return loadProvider()
},
}

function filterResult(coverage: Profiler.ScriptCoverage): boolean {
if (!coverage.url.startsWith('file://')) {
return false
}

if (coverage.url.includes('/node_modules/')) {
return false
}

return true
}
8 changes: 8 additions & 0 deletions packages/coverage-v8/src/load-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// to not bundle the provider
const name = './provider.js'

export async function loadProvider() {
const { V8CoverageProvider } = (await import(/* @vite-ignore */ name)) as typeof import('./provider')

return new V8CoverageProvider()
}
Loading

0 comments on commit 34199bd

Please sign in to comment.