Skip to content

node v26 compat: util.diff(), dns perf entries, proxy-aware deepStrictEqual - #35382

Closed
cirospaciari wants to merge 8 commits into
claude/callback-throw-uncaughtfrom
claude/node-v26-sweep-a
Closed

node v26 compat: util.diff(), dns perf entries, proxy-aware deepStrictEqual#35382
cirospaciari wants to merge 8 commits into
claude/callback-throw-uncaughtfrom
claude/node-v26-sweep-a

Conversation

@cirospaciari

@cirospaciari cirospaciari commented Jul 24, 2026

Copy link
Copy Markdown
Member

Stacked on #34660 — review that first.

What this does

Closes a slice of the Node v26.3.0 test/parallel compatibility gap: three runtime gaps
are fixed and the three upstream tests they unblock are vendored verbatim.

Why now

The v26 test-suite sweep needs these files present in test/js/node/test/parallel, and
each was blocked on a real behavioural difference rather than on test scaffolding.

What changed

assert.deepStrictEqual sees through a Proxy (src/jsc/bindings/bindings.cpp).
Two changes to the strict comparison path:

  1. It bailed out whenever the two objects reported different class names. JSC reports a
    Proxy's own class name, not its target's, so assert.deepStrictEqual(new Proxy({}, {}), {})
    returned false — as did util.isDeepStrictEqual for any proxy. Node compares
    prototypes here, never class names, so the class-name check is now skipped when either
    side is a proxy. The prototype check immediately above it and the own-property walk
    below it already go through the proxy traps.

  2. WeakMaps, WeakSets and Promises were rejected when either operand was one. Node tests
    the left operand only, which makes the comparison deliberately asymmetric: a Proxy
    wrapping a promise equals that promise with the proxy on the left, but not the other
    way round. Confirmed against the v26.3.0 binary:

    $ node -e "assert.deepStrictEqual(new Proxy(p, {}), p)"   # passes
    $ node -e "assert.deepStrictEqual(p, new Proxy(p, {}))"   # throws
    

    The check moved out of specialObjectsDequal (which is called twice with the operands
    swapped) into Bun__deepEquals, where operand order is stable. Both changes are inside
    if constexpr (checkPrototypes), i.e. the node:assert / node:util entry point only —
    Bun.deepEquals and expect() are untouched.

util.diff() is implemented (src/js/node/util.ts, src/runtime/node/node_assert*.rs).
New in Node v26: it reports the Myers diff of two strings or two string arrays as
[operation, value] pairs (-1 delete, 0 unchanged, 1 insert). Bun already had the
Myers comparator natively for node:assert diffs, but it only accepted strings; it now also
accepts arrays of strings, one element per line, so the public API and node:assert share
one implementation rather than growing a second copy of the algorithm.

node:dns emits 'dns' performance entries (src/js/node/dns.ts). Node records a
PerformanceEntry of type 'dns' for every successful lookup, lookupService and
resolver query, naming resolver entries after the c-ares binding (queryAny, queryA, …).
Bun recorded none, so a PerformanceObserver watching { type: 'dns' } never fired. This
reuses the JS-side node-entry registry that 'net' and 'http' already use. Entries are
only constructed when such an observer is registered, so the ordinary path allocates
nothing extra.

Upstream tests now vendored (3)

  • parallel/test-diff.jsutil.diff()
  • parallel/test-dns-perf_hooks.js'dns' performance entries
  • parallel/test-common-must-not-mutate-object-deep.mjsdeepStrictEqual against the
    immutable-view proxies the upstream harness hands to tests
    All three are byte-identical to v26.3.0, and each one fails on the base branch for the
    reason its commit fixes — none is a test that merely skips itself.

How this was verified

  • Each vendored file run the way CI runs it: one process per file,
    bun run --config=bunfig.node-test.toml <file> (bun test for the files that import
    node:test), with BUN_GARBAGE_COLLECTOR_LEVEL=1 BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING=1 NO_COLOR=1 FORCE_COLOR=0. All three exit 0; all three fail on the base branch.
  • Regression sweep over the 60 already-vendored test-assert-*, test-util-* and
    test-dns-* upstream files: all still pass.
  • bun test test/js/node/dns test/js/node/util test/js/bun/test/expect.test.js: no new
    failures (two pre-existing debug-build timeouts in util-inspect/parse-args reproduce
    identically on the base binary).
  • util.diff output compared field-by-field against node v26.3.0 for the string, array,
    identical-input and three error cases in the upstream test.

Note for reviewers

Another branch in this campaign also edits Bun__deepEquals in
src/jsc/bindings/bindings.cpp: it adds key-material comparison for Bun::JSKeyObject /
JSCryptoKey, whose own-property walk currently reports unrelated keys as equal. That
change and this one are complementary — it lands a new case inside specialObjectsDequal,
this one narrows two guards outside it — but they touch nearby lines, so expect a small
textual conflict.

The strict deep-equality path rejected any pair where the two objects
report different class names. JSC reports a Proxy's own class name, not
its target's, so assert.deepStrictEqual(new Proxy({}, {}), {}) failed
even though every trap forwards to an identical target. Node compares
prototypes here, never class names.

Skip the class-name check when either side is a proxy; the prototype
check and the own-property walk above and below it already go through
the traps.
Adds the v26 util.diff(actual, expected) API, which reports the Myers
diff of two strings or two string arrays as [operation, value] pairs
(-1 delete, 0 unchanged, 1 insert).

The existing native myersDiff comparator only accepted strings; it now
also takes arrays of strings, diffing one element per line, so the
public API and node:assert share a single implementation.
node records a PerformanceEntry of type 'dns' for every successful
lookup, lookupService and resolver query, with the query entries named
after the c-ares binding (queryAny, queryA, ...). Bun recorded none, so
a PerformanceObserver watching { type: 'dns' } never fired.

Entries are only constructed when such an observer is registered.
test-diff.js and test-dns-perf_hooks.js are unblocked by the two
preceding commits. test-compile-cache-typescript-esm.js already passed
(it skips itself on a build that reports no Amaro, which is what Bun
reports).
deepStrictEqual bailed out when either side was a WeakMap, WeakSet or
Promise. node tests only the left operand, which makes the comparison
asymmetric on purpose: a Proxy wrapping a promise equals that promise
with the proxy on the left, but not the other way round. Verified
against the v26.3.0 binary.
Exercises deepStrictEqual against the immutable-view proxies the
upstream harness hands to tests.
@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator
Updated 10:00 PM PT - Jul 23rd, 2026

@cirospaciari, your commit 0c71dce is still building in Build #79313, but has 1 failures so far (All Failures):

  • 📦 Binary size — 2 over 0.50 MB
  • targetthis build canary: main #79319
    sizeΔ
    bun-darwin-aarch6457.83 MB57.58 MB+260.0 KB
    bun-darwin-x6463.20 MB62.95 MB+258.1 KB
    bun-linux-aarch6470.99 MB70.42 MB+579.4 KB
    bun-linux-x6472.52 MB71.95 MB+577.6 KB
    bun-linux-aarch64-musl64.51 MB64.32 MB+192.0 KB
    bun-linux-x64-musl66.63 MB66.45 MB+192.0 KB
    bun-linux-aarch64-android78.34 MB77.97 MB+385.1 KB
    bun-linux-x64-android80.51 MB80.10 MB+416.3 KB
    bun-freebsd-x6482.78 MB82.56 MB+224.1 KB
    bun-freebsd-aarch6484.54 MB84.31 MB+240.1 KB
    bun-windows-x6479.95 MB79.70 MB+250.5 KB
    bun-windows-aarch6470.57 MB70.34 MB+234.0 KB

    Add [skip size check] to the commit message if this increase is intentional.

autofix-ci Bot and others added 2 commits July 24, 2026 04:39
It only ever skips: it gates on process.config.variables.node_use_amaro,
which Bun reports as falsy and cannot report as true. Vendoring it adds
a file that asserts nothing.
@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Add Node's util.diff and show Bun support in Bun's Node reference #20396 - Requests util.diff support, which this PR implements

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #20396

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. perf_hooks: emit 'dns' observer entries, stop advertising 'resource' #33041 - Implements the same DNS PerformanceEntry emission via PerformanceObserver in src/js/node/dns.ts
  2. Treat Proxy as transparent in deep equality and toMatchObject #32948 - Fixes the same proxy-transparency bug in Bun__deepEquals by replacing calculatedClassName with observable prototype comparison for Proxies

🤖 Generated with Claude Code

@cirospaciari

Copy link
Copy Markdown
Member Author

Superseded by #35391, which combines this branch with the other five stacked on claude/callback-throw-uncaught so there is one review surface instead of six. Every source change and every vendored test from this branch is carried over unchanged; #35391's description lists the merge conflicts that needed a decision and links back here for the rationale.

The branch claude/node-v26-sweep-a is not deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants