Skip to content

Commit e77c553

Browse files
authoredMar 14, 2024··
fix: --inspect-brk to pause before execution (#5355)
1 parent d627e20 commit e77c553

File tree

10 files changed

+162
-5
lines changed

10 files changed

+162
-5
lines changed
 

‎packages/vite-node/src/source-map.ts

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export function withInlineSourcemap(result: TransformResult, options: {
4848
while (OTHER_SOURCE_MAP_REGEXP.test(code))
4949
code = code.replace(OTHER_SOURCE_MAP_REGEXP, '')
5050

51+
// If the first line is not present on source maps, add simple 1:1 mapping ([0,0,0,0], [1,0,0,0])
52+
// so that debuggers can be set to break on first line
53+
if (map.mappings.startsWith(';'))
54+
map.mappings = `AAAA,CAAA${map.mappings}`
55+
5156
const sourceMap = Buffer.from(JSON.stringify(map), 'utf-8').toString('base64')
5257
result.code = `${code.trimEnd()}\n\n${VITE_NODE_SOURCEMAPPING_SOURCE}\n//# ${VITE_NODE_SOURCEMAPPING_URL};base64,${sourceMap}\n`
5358

‎packages/vitest/src/runtime/inspector.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { createRequire } from 'node:module'
22

3-
import type { ResolvedConfig } from '../types'
3+
import type { ContextRPC } from '../types'
44

55
const __require = createRequire(import.meta.url)
66
let inspector: typeof import('node:inspector')
7+
let session: InstanceType<typeof inspector.Session>
78

89
/**
910
* Enables debugging inside `worker_threads` and `child_process`.
1011
* Should be called as early as possible when worker/process has been set up.
1112
*/
12-
export function setupInspect(config: ResolvedConfig) {
13+
export function setupInspect(ctx: ContextRPC) {
14+
const config = ctx.config
1315
const isEnabled = config.inspect || config.inspectBrk
1416

1517
if (isEnabled) {
@@ -20,8 +22,22 @@ export function setupInspect(config: ResolvedConfig) {
2022
if (!isOpen) {
2123
inspector.open()
2224

23-
if (config.inspectBrk)
25+
if (config.inspectBrk) {
2426
inspector.waitForDebugger()
27+
const firstTestFile = ctx.files[0]
28+
29+
// Stop at first test file
30+
if (firstTestFile) {
31+
session = new inspector.Session()
32+
session.connect()
33+
34+
session.post('Debugger.enable')
35+
session.post('Debugger.setBreakpointByUrl', {
36+
lineNumber: 0,
37+
url: new URL(firstTestFile, import.meta.url).href,
38+
})
39+
}
40+
}
2541
}
2642
}
2743

@@ -32,7 +48,9 @@ export function setupInspect(config: ResolvedConfig) {
3248
const keepOpen = config.watch && (isIsolatedSingleFork || isIsolatedSingleThread)
3349

3450
return function cleanup() {
35-
if (isEnabled && !keepOpen && inspector)
51+
if (isEnabled && !keepOpen && inspector) {
3652
inspector.close()
53+
session?.disconnect()
54+
}
3755
}
3856
}

‎packages/vitest/src/runtime/worker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (isChildProcess())
1515
export async function run(ctx: ContextRPC) {
1616
const prepareStart = performance.now()
1717

18-
const inspectorCleanup = setupInspect(ctx.config)
18+
const inspectorCleanup = setupInspect(ctx)
1919

2020
process.env.VITEST_WORKER_ID = String(ctx.workerId)
2121
process.env.VITEST_POOL_ID = String(poolId)

‎pnpm-lock.yaml

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/inspect/fixtures/math.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { expect, test } from "vitest";
2+
3+
test("sum", () => {
4+
expect(1 + 1).toBe(2)
5+
})
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
test: {
5+
include: ['./**.test.ts'],
6+
watch: false,
7+
},
8+
})

‎test/inspect/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@vitest/test-inspect",
3+
"type": "module",
4+
"private": true,
5+
"scripts": {
6+
"test": "vitest"
7+
},
8+
"devDependencies": {
9+
"vite": "latest",
10+
"vitest": "workspace:*",
11+
"ws": "^8.14.2"
12+
}
13+
}

‎test/inspect/test/inspect.test.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type { InspectorNotification } from 'node:inspector'
2+
import { expect, test } from 'vitest'
3+
import WebSocket from 'ws'
4+
5+
import { isWindows } from '../../../packages/vite-node/src/utils'
6+
import { runVitestCli } from '../../test-utils'
7+
8+
type Message = Partial<InspectorNotification<any>>
9+
10+
test.skipIf(isWindows)('--inspect-brk stops at test file', async () => {
11+
const vitest = await runVitestCli('--root', 'fixtures', '--inspect-brk', '--no-file-parallelism')
12+
13+
await vitest.waitForStderr('Debugger listening on ')
14+
const url = vitest.stderr.split('\n')[0].replace('Debugger listening on ', '')
15+
16+
const { receive, send } = await createChannel(url)
17+
18+
send({ method: 'Debugger.enable' })
19+
send({ method: 'Runtime.enable' })
20+
await receive('Runtime.executionContextCreated')
21+
22+
const paused = receive('Debugger.paused')
23+
send({ method: 'Runtime.runIfWaitingForDebugger' })
24+
25+
const { params } = await paused
26+
const scriptId = params.callFrames[0].functionLocation.scriptId
27+
28+
// Verify that debugger paused on test file
29+
const response = receive()
30+
send({ method: 'Debugger.getScriptSource', params: { scriptId } })
31+
const { result } = await response as any
32+
33+
expect(result.scriptSource).toContain('test("sum", () => {')
34+
expect(result.scriptSource).toContain('expect(1 + 1).toBe(2)')
35+
36+
send({ method: 'Debugger.resume' })
37+
38+
await vitest.waitForStdout('Test Files 1 passed (1)')
39+
await vitest.isDone
40+
})
41+
42+
async function createChannel(url: string) {
43+
const ws = new WebSocket(url)
44+
45+
let id = 1
46+
let receiver = defer()
47+
48+
ws.onerror = receiver.reject
49+
ws.onmessage = (message) => {
50+
const response = JSON.parse(message.data.toString())
51+
receiver.resolve(response)
52+
}
53+
54+
async function receive(filter?: string) {
55+
const message = await receiver.promise
56+
receiver = defer()
57+
58+
if (filter && message.method !== filter)
59+
return receive(filter)
60+
61+
return message
62+
}
63+
64+
function send(message: Message) {
65+
ws.send(JSON.stringify({ ...message, id: id++ }))
66+
}
67+
68+
await new Promise(r => ws.on('open', r))
69+
70+
return { receive, send }
71+
}
72+
73+
function defer(): {
74+
promise: Promise<Message>
75+
resolve: (response: Message) => void
76+
reject: (error: unknown) => void
77+
} {
78+
const pr = {} as ReturnType<typeof defer>
79+
80+
pr.promise = new Promise((resolve, reject) => {
81+
pr.resolve = resolve
82+
pr.reject = reject
83+
})
84+
85+
return pr
86+
}

‎test/inspect/vitest.config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
test: {
5+
include: ['./test/**'],
6+
},
7+
})

‎test/test-utils/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ export async function runCli(command: string, _options?: Options | string, ...ar
188188
await cli.isDone
189189
})
190190

191+
if (args.includes('--inspect') || args.includes('--inspect-brk'))
192+
return cli
193+
191194
if (args.includes('--watch')) {
192195
if (command === 'vitest') // Wait for initial test run to complete
193196
await cli.waitForStdout('Waiting for file changes')

0 commit comments

Comments
 (0)
Please sign in to comment.