Skip to content

Commit 36087d1

Browse files
authoredMar 15, 2023
fix: --inspect to work inside workers (#2983)
1 parent c2e25bb commit 36087d1

File tree

9 files changed

+114
-11
lines changed

9 files changed

+114
-11
lines changed
 

‎docs/guide/debugging.md

+16
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,19 @@ JavaScript file | ./node_modules/vitest/vitest.mjs
4545
Application parameters | run --threads false
4646

4747
Then run this configuration in debug mode. The IDE will stop at JS/TS breakpoints set in the editor.
48+
49+
## Node Inspector, e.g. Chrome DevTools
50+
51+
Vitest also supports debugging tests without IDEs. However this requires that tests are not run parallel. Use one of the following commands to launch Vitest.
52+
53+
```sh
54+
# To run in a single worker
55+
vitest --inspect-brk --single-thread
56+
57+
# To run in a child process
58+
vitest --inspect-brk --no-threads
59+
```
60+
61+
Once Vitest starts it will stop execution and waits for you to open developer tools that can connect to [NodeJS inspector](https://nodejs.org/en/docs/guides/debugging-getting-started/). You can use Chrome DevTools for this by opening `chrome://inspect` on browser.
62+
63+
In watch mode you can keep the debugger open during test re-runs by using the `--single-thread --isolate false` options.

‎packages/vitest/src/node/cli-wrapper.ts

-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ const ENTRY = new URL('./cli.js', import.meta.url)
1111

1212
/** Arguments passed to Node before the script */
1313
const NODE_ARGS = [
14-
'--inspect',
15-
'--inspect-brk',
1614
'--trace-deprecation',
1715
'--experimental-wasm-threads',
1816
'--wasm-atomics-on-non-shared-memory',

‎packages/vitest/src/node/config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ export function resolveConfig(
106106
resolved.shard = { index, count }
107107
}
108108

109+
if (resolved.inspect || resolved.inspectBrk) {
110+
if (resolved.threads !== false && resolved.singleThread !== true) {
111+
const inspectOption = `--inspect${resolved.inspectBrk ? '-brk' : ''}`
112+
throw new Error(`You cannot use ${inspectOption} without "threads: false" or "singleThread: true"`)
113+
}
114+
}
115+
109116
resolved.deps = resolved.deps || {}
110117
// vitenode will try to import such file with native node,
111118
// but then our mocker will not work properly

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { RuntimeRPC } from '../types/rpc'
66
import type { ChildContext } from '../types/child'
77
import { mockMap, moduleCache, startViteNode } from './execute'
88
import { rpcDone } from './rpc'
9+
import { setupInspect } from './inspector'
910

1011
function init(ctx: ChildContext) {
1112
const { config } = ctx
@@ -58,10 +59,17 @@ function unwrapConfig(config: ResolvedConfig) {
5859
}
5960

6061
export async function run(ctx: ChildContext) {
61-
init(ctx)
62-
const { run, executor } = await startViteNode(ctx)
63-
await run(ctx.files, ctx.config, ctx.environment, executor)
64-
await rpcDone()
62+
const inspectorCleanup = setupInspect(ctx.config)
63+
64+
try {
65+
init(ctx)
66+
const { run, executor } = await startViteNode(ctx)
67+
await run(ctx.files, ctx.config, ctx.environment, executor)
68+
await rpcDone()
69+
}
70+
finally {
71+
inspectorCleanup()
72+
}
6573
}
6674

6775
const procesExit = process.exit
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import inspector from 'node:inspector'
2+
3+
import type { ResolvedConfig } from '../types'
4+
5+
/**
6+
* Enables debugging inside `worker_threads` and `child_process`.
7+
* Should be called as early as possible when worker/process has been set up.
8+
*/
9+
export function setupInspect(config: ResolvedConfig) {
10+
const isEnabled = config.inspect || config.inspectBrk
11+
12+
if (isEnabled) {
13+
// Inspector may be open already if "isolate: false" is used
14+
const isOpen = inspector.url() !== undefined
15+
16+
if (!isOpen) {
17+
inspector.open()
18+
19+
if (config.inspectBrk)
20+
inspector.waitForDebugger()
21+
}
22+
}
23+
24+
// In watch mode the inspector can persist re-runs if "isolate: false, singleThread: true" is used
25+
const keepOpen = config.watch && !config.isolate && config.singleThread
26+
27+
return function cleanup() {
28+
if (isEnabled && !keepOpen)
29+
inspector.close()
30+
}
31+
}

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { workerId as poolId } from 'tinypool'
33
import type { RuntimeRPC, WorkerContext } from '../types'
44
import { getWorkerState } from '../utils/global'
55
import { mockMap, moduleCache, startViteNode } from './execute'
6+
import { setupInspect } from './inspector'
67
import { rpcDone } from './rpc'
78

89
function init(ctx: WorkerContext) {
@@ -43,8 +44,15 @@ function init(ctx: WorkerContext) {
4344
}
4445

4546
export async function run(ctx: WorkerContext) {
46-
init(ctx)
47-
const { run, executor } = await startViteNode(ctx)
48-
await run(ctx.files, ctx.config, ctx.environment, executor)
49-
await rpcDone()
47+
const inspectorCleanup = setupInspect(ctx.config)
48+
49+
try {
50+
init(ctx)
51+
const { run, executor } = await startViteNode(ctx)
52+
await run(ctx.files, ctx.config, ctx.environment, executor)
53+
await rpcDone()
54+
}
55+
finally {
56+
inspectorCleanup()
57+
}
5058
}

‎packages/vitest/src/types/config.ts

+14
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,20 @@ export interface InlineConfig {
550550
* Path to a custom test runner.
551551
*/
552552
runner?: string
553+
554+
/**
555+
* Debug tests by opening `node:inspector` in worker / child process.
556+
* Provides similar experience as `--inspect` Node CLI argument.
557+
* Requires `singleThread: true` OR `threads: false`.
558+
*/
559+
inspect?: boolean
560+
561+
/**
562+
* Debug tests by opening `node:inspector` in worker / child process and wait for debugger to connect.
563+
* Provides similar experience as `--inspect-brk` Node CLI argument.
564+
* Requires `singleThread: true` OR `threads: false`.
565+
*/
566+
inspectBrk?: boolean
553567
}
554568

555569
export interface TypecheckConfig {

‎test/config/test/failures.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,21 @@ test('shard index must be smaller than count', async () => {
1919

2020
expect(error).toMatch('Error: --shard <index> must be a positive number less then <count>')
2121
})
22+
23+
test('inspect requires changing threads or singleThread', async () => {
24+
const { error } = await runVitest('run', ['--inspect'])
25+
26+
expect(error).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"')
27+
})
28+
29+
test('inspect cannot be used with threads', async () => {
30+
const { error } = await runVitest('run', ['--inspect', '--threads', 'true'])
31+
32+
expect(error).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"')
33+
})
34+
35+
test('inspect-brk cannot be used with threads', async () => {
36+
const { error } = await runVitest('run', ['--inspect-brk', '--threads', 'true'])
37+
38+
expect(error).toMatch('Error: You cannot use --inspect-brk without "threads: false" or "singleThread: true"')
39+
})

‎test/config/test/utils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ export async function runVitest(mode: 'run' | 'watch', cliArguments: string[]) {
66
let error = ''
77

88
subprocess.stderr?.on('data', (data) => {
9-
error += stripAnsi(data.toString())
9+
error = stripAnsi(data.toString())
10+
11+
// Sometimes on Windows CI execa doesn't exit properly. Force exit when stderr is caught.
12+
subprocess.kill()
1013
})
1114

1215
await new Promise(resolve => subprocess.on('exit', resolve))

0 commit comments

Comments
 (0)
Please sign in to comment.