inspector: Node-shaped inspector.console, CLI-started url()/close(), synchronous Session.post (+19 tests) - #35381
Conversation
|
Found 4 issues this PR may fix:
🤖 Generated with Claude Code |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
9da3669 to
6c8cae2
Compare
…/close(), synchronous Session.post callbacks
- inspector.console reports to attached sessions only, with Node's key set,
instead of aliasing the global console and writing to stdout.
- inspector.url()/close()/waitForDebugger() see a server started by --inspect,
not only one started by inspector.open().
- Session.post delivers its callback synchronously and throws
ERR_INSPECTOR_NOT_CONNECTED even when a callback was passed, matching Node.
- CPU profiles account for wall-clock time spent outside JS with (idle) nodes.
- process.binding('inspector').isEnabled().
- NodeTracing.getCategories.
- --debug and --debug-brk are rejected with Node's DEP0062 message and exit 9.
6c8cae2 to
cdc6e24
Compare
|
Superseded by #35396, which combines this branch with One thing to know: The branch |
Stacked on #34719 — review that first.
What this does
Closes six more upstream
node:inspectortest files, and vendors thirteen morethat already passed but had never been added.
Six behaviour fixes, each one required by a vendored test:
inspector.consolereports to the inspector only. It was a copy of theglobal console, so
require('inspector').console.log(x)printedxonstdout. In Node it is V8's inspector console: the call becomes a
Runtime.consoleAPICallednotification for attached sessions and nothing iswritten to stdout or stderr. A new native entry point calls the inspector
controller's own console client, which is the half of
Bun::ConsoleObjectthat does not print. The object now carries Node's key set in Node's order.
inspector.url(),close()andwaitForDebugger()see a CLI-startedinspector. They only knew about a server started by
inspector.open(), sounder
--inspecturl()returnedundefinedandopen()did not reportthe port as already taken. The debugger thread now reports the CDP endpoint's
URL back to the inspected thread for
--inspecttoo, over the same controlchannel
inspector.open()already used, soclose()can stop it. A workerstill reports no URL: it cannot start an inspector of its own.
Session.postmatches Node's calling contract. The callback now runssynchronously, before
post()returns, and posting on a disconnected sessionthrows
ERR_INSPECTOR_NOT_CONNECTEDeven when a callback was given. Both werechecked against node v26.3.0 rather than inferred.
(idle)samplesfor wall-clock time the thread was not running JS; JSC's sampling profiler
records nothing at all then, so the profile claimed a 1-second sleep took
15 ms. Gaps longer than one extra sampling period are now reported as
(idle)samples under
(root).process.binding('inspector')exposesisEnabled().NodeTracing.getCategoriesanswers with Node's category list.Plus one smaller one:
--debugand--debug-brkare rejected with Node's DEP0062 message and exitcode 9 instead of being silently ignored. Only for the runtime commands (the
ones that accept
--inspect), and only when the flag precedes the entrypoint,so
bun --bun node-gyp build --debugstill passes it through.Upstream tests now vendored (19)
Already passing on the base, never vendored (13):
test-inspector-async-stack-traces-promise-then,test-inspector-break-e,test-inspector-break-when-eval,test-inspector-debug-brk-flag,test-inspector-debug-end,test-inspector-exception,test-inspector-heap-allocation-tracker,test-inspector-module,test-inspector-multisession-ws,test-inspector-stop-profile-after-done,test-inspector-wait-for-connection,test-inspector-wait.mjs,test-inspector-waiting-for-disconnect.Made to pass by this change (6):
test-inspector-already-activated-cli,test-inspector-console,test-inspector-enabled,test-inspector-has-idle,test-inspector-invalid-args,test-inspector-tracing-domain.All nineteen are byte-identical to v26.3.0.
test/expectations.txtisuntouched.
Still failing, and why
Of the 45 upstream inspector files not vendored anywhere, 26 remain out:
(
multisession-js,async-context-brk,bindings). In-process pausing works— a
debuggerstatement pauses, and a breakpoint on a statement line hits.All three of these set the breakpoint on the
}that closes a one-linefunction. That original line has no mapping, so the adapter resolves it to the
next mapped position, which is past the function, and it never fires. V8
resolves it to the function's exit hook.
close-worker,exit-worker-in-wait-for-connection,exit-worker-in-wait-for-connection2,connect-main-thread,async-hook-after-done,worker-target).inspector.open()rejects off themain thread, and the debugger-thread state is a process-wide singleton with
one URL and one control callback. Per-worker servers,
connectToMainThread,and the
NodeWorker/Targetdomains are all downstream of keying that stateper inspected thread.
vmexecution contexts (contexts,scriptparsed-context,vm-global-accessors-sideeffects,vm-global-accessors-getter-sideeffect).JSC's runtime agent has exactly one execution context and no
throwOnSideEffect. Note the twovm-global-accessorsfiles exit 0 todaywith their assertion swallowed by Node's own
Sessionwarning path, so theyare deliberately not vendored — they would be vacuous passes over wrong
output.
(
async-stack-traces-set-interval,async-hook-setup-at-inspect-brk). Bothassert an async frame with
url === 'node:internal/process/execution'. Bun'sinternals are not Node's module graph.
--inspect-brkpauses in the entry module (esm). Bun prepends adebuggerstatement to the entry; Node pauses at the first statement of thefirst evaluated module, which for an ESM entry is a dependency. Matching it
means pausing via
setPauseOnNextStatementinstead, which changes every--inspect-brksession.Runtime.evaluatetimeout(runtime-evaluate-with-timeout). Nothingterminates the evaluation, so the test hangs. A fix needs JSC's watchdog
around the in-process dispatch plus a mapping from the resulting termination
to a
-32000 Execution was terminatedreply; the second half is the unknown./json/versionidentity (inspector). The test wants exactly{"Browser":"node.js/<version>","Protocol-Version":"1.1"}; the basedeliberately keeps Bun's fields there for
--inspectbecause debug.bun.sh andthe VSCode extension identify a Bun target by them. It also needs
includeCommandLineAPIrequire()support.overwrite-config). Its--require ./test/fixtures/overwrite-config-preload-module.jsis relative tothe repo root, and the fixture itself does
require('../common'). Bun's nodefixtures live under
test/js/node/test/fixtures, so no single placementsatisfies both.
debug-async-hook(times out waiting for disconnect underbun test),inspect-brk-node(--inspect-brk-nodemust break inside thebootstrap),
port-zeroandport-zero-cluster(--inspect-port, andprocess.debugPortreflecting the resolved ephemeral port),strip-types(gated on
process.config.variables.node_use_amaro, which Bun deliberatelydoes not set — Node's amaro path is strip-only and Bun's transpiler is not —
and which would not be enough anyway: the test then needs
Debugger.scriptParsed.hasSourceURLfor transpiled TypeScript),dom-storage(--experimental-storage-inspection).How this was verified
Every added file was run one process per file the way CI does
(
bun run --config=bunfig.node-test.toml <file>), three times each for the sixnew ones.
Three existing assertions changed with the behaviour they covered:
inspector-profiler.test.tsassertedinspector.console.log === console.logand that a disconnected
post()with a callback delivers the error to it;process.test.jsassertedprocess.binding("inspector")throws. All three nowassert the new behaviour, and two cases were added:
inspector.console.logwrites nothing to stdout, and the binding exposes
isEnabled.Everything was re-run on base
bc6bf60d10plus this change:inspector.test.ts29/29,inspector-profiler.test.ts45/45,process.test.js125/125,cpu-prof.test.ts9/9,vitest.test.ts2/2, the 31inspector tests the base already vendored, and preflight over all 19 added
files — byte-verbatim, one process each, plus a silent-pass check that appends
a throw to each file and confirms it then fails.