console: route global console through process.stdout/stderr, native table index column, _rawDebug (+4 tests) - #35380
Conversation
test-compile-cache-permission-allowed.js test-compile-cache-typescript-commonjs.js test-compile-cache-typescript-strip-sourcemaps.js test-eslint-prefer-abort-signal-abort.js Verbatim copies of the upstream v26.3.0 files. Each was run one process per file with bunfig.node-test.toml and exits 0 with no runtime changes.
Three fixes to Console#table (the `node:console` Console class, not the native global console.table): - Cells are left-justified. Node changed cli_table.js from centering to left-justifying, so every table Bun printed had the wrong padding. - Map and Set iterators are previewed instead of consumed as opaque iterables, so `console.table(map.entries())` renders separate Key and Values columns like Node. internal/util/inspect already had a previewEntries helper; it is now exported and wired up. - `isBuffer` was destructured off the `node:buffer` module namespace, where it does not exist, so any table containing a nested object threw "isBuffer is not a function". Vendors the upstream v26.3.0 test-console-table.js, which now passes, and updates console-table-iterators.test.ts to the layout real Node prints.
|
Updated 9:37 PM PT - Jul 23rd, 2026
⏳ @cirospaciari, your commit e7718d3 is still building in
Add |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Superseded by #35391, which combines this branch with the other five stacked on The branch |
|
Follow-up on the consolidation: Short version: it was a false pass, not a regression. On this branch Bun ignored Not permanent: a permission-model branch is in flight on the same base (flag surface, Your other three tests — |
|
🤖 From the combined PR #35391: The test asserts Nothing else from your branch is red — |
Stacked on #34660 — review that first.
What this does
Two things, both in the console output path.
1. The global
consolenow writes throughprocess.stdout/process.stderr. Bun's global console formats natively and writes straight to its own buffered writer, so replacingprocess.stdout.writehad no effect onconsole.log— the write went to the fd regardless. Node performs every console write through the stream, and Node's own test harness (test/common/hijackstdio.js) is built entirely on that. This was the single blocker shared by a group of upstream console tests.2.
console.table's native printer labels its index column. It used a blank space where Node writes(index), or(iteration index)for a Map, a Set, or one of their iterators. The column is also left-justified now, like every other cell and like Node'scli_table.js.Plus two smaller fixes found while working the same path:
process._rawDebugwas a no-op stub (Process_stubEmptyFunction). It now formats its arguments and writes to fd 2 directly, bypassingprocess.stderrthe way Node does, so it still reports when that stream is broken or replaced.console.assert(false, msg)printed justmsg. Node prefixesAssertion failed:.How the routing works, and what it costs
console.logis a hot path and the native printer exists for speed, so the fast path had to stay free.process.stdoutorprocess.stderrobject is constructed. A program that never touches either — the common case, and the one the native printer exists for — pays exactly one relaxed atomic load per console call and then takes the pre-existing code path unchanged.getDirectwalk up its prototype chain compared against thewriterecorded when the stream was built.getDirectnever invokes an accessor, a Proxy trap or any other user code, so the console write path still needs no throw scope (the Rust caller could not discharge one — seeProcess__emitMessageEvent). Anything exotic in the chain yields no direct slot, compares unequal, and routes through the ordinary JS path, which is the safe direction.writecall, matching Node'skWriteToConsole. The native writer is flushed first so the two sinks cannot interleave out of order.Measured with
perf stat -e instructions:u, taking the slope between N=100k and N=200kconsole.logcalls so process startup cancels out. Five interleaved runs per configuration, same binary directory, debug+ASAN build (bun bd), stdout to/dev/null:console.logprocess.stdoutnever touchedprocess.stdouttouched,writenot replacedThe two "this branch" rows are indistinguishable (0.01% apart), which is the number that matters for the gate: the
getDirectwalk is below the noise floor. The ~1% against baseline is not the gate — the fast path adds one atomic load — it is the refactor that moved the message body into awrite_messagehelper, which is a real call at-O0and inlines under optimization. Caveat stated plainly: a debug+ASAN build spends ~249k instructions perconsole.log, so this harness cannot resolve anything smaller than about 1%; I did not build an LTO release binary to tighten it.Tests
Vendored verbatim from
nodejs/nodeat tagv26.3.0:parallel/test-console-count.jsconsole.countwrites through the replacedprocess.stdout.writeparallel/test-process-raw-debug.jsprocess._rawDebugis implementedBoth fail on this branch's parent.
test-process-raw-debug.jsreports as a silent pass under the campaign's append-a-throw canary, and that is a false positive: the file's top-levelswitchends inreturn child()/return parent(), so an appended statement is unreachable in both branches. Verified real two other ways — it exits 1 on the parent build and exits 1 with athrowinjected insidechild().New Bun-side tests in
test/js/node/console/console.test.ts, both failing on the parent build:process.stdout.write/process.stderr.write, acrosslog,error,warnandcountconsole.assertprefixesAssertion failed:Also in this diff
test/js/bun/console/__snapshots__/*.snapand the inline expectations inconsole-table.test.tsare regenerated for the new index column.Bun.inspect.tableshares the native printer, so its output changes too;docs/runtime/utils.mdxis updated to what the code now prints. This is a deliberate change to a public Bun API's output — flagging it explicitly in case you would ratherBun.inspect.tablekeep the blank header, in which case the printer needs a flag.test-eslint-prefer-abort-signal-abort.js(skips permanently —tools/eslintis not vendored in Bun) and the two Amaro-gated compile-cache TypeScript tests (process.config.variables.node_use_amarocan never be true in Bun). All three passed only by skipping.Verified
--config=bunfig.node-test.toml,BUN_DEBUG_QUIET_LOGS=1,node:testfiles underbun test). Campaign preflight reportsPREFLIGHT OK.test-console-*,test-process-*,test-util-inspect*,test-util-format*,test-stdout-*,test-stderr-*andtest-repl-*files re-run individually: 93 pass, 0 fail.bun bd test test/js/node/console/ test/js/bun/console/: 85 pass, 1 skip, 0 fail.test/js/node/process/has 7 pre-existing failures; identical on the parent build.test/expectations.txtuntouched.Still blocked, for the record
The routing fix does not by itself land the rest of the hijackstdio-based tests. Each now gets past the interception and fails on something else:
test-console-diagnostics-channels.js— the hijack callback now fires; needsconsole.log/info/debug/warn/errorto publish todiagnostics_channelbefore formatting.test-console.js— needsconsole._timesexposed as a Map, andconsole.log's object formatting to matchutil.inspectexactly (Bun prints{\n slashes: "\\\\",\n}where Node prints{ slashes: '\\\\' }).test-console-stdio-setters.js— needs the native console to readconsole._stdout/console._stderrrather thanprocess.stdout/process.stderr. Bun already exposes those as custom accessors on the console object; the native path does not consult them.test-common.js— unrelated to console: Bun's adaptedtest/common/index.jsdoes not implement Node's leaked-global detection, so theleakedGlobal.jsfixture child exits 0.test-internal-errors.js(--expose-internalsplusinternal/errors.E) andtest-repl-tab-complete-buffer.js(repl.start) are unrelated to this path.console.time/timeEnd/timeLogstill write natively. That path does not use the console's writer at all — it goes throughOutput::print_error— and it writes to stderr where Node writes to stdout. Both want fixing together, separately from this change.