Skip to content

Treat two Invalid Dates as equal in deepEquals and fix their failure-message rendering - #34819

Open
robobun wants to merge 5 commits into
mainfrom
farm/f5a6766f/invalid-date-deep-equals
Open

Treat two Invalid Dates as equal in deepEquals and fix their failure-message rendering#34819
robobun wants to merge 5 commits into
mainfrom
farm/f5a6766f/invalid-date-deep-equals

Conversation

@robobun

@robobun robobun commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes #34816

Repro

import { test, expect } from "bun:test";
test("invalid date", () => {
  expect(new Date(NaN)).toEqual(new Date(NaN)); // fails
});
error: expect(received).toEqual(expected)

Expected: ul
Received: ul

Also affected the Node compat surface: util.isDeepStrictEqual(new Date(NaN), new Date(NaN)) returned false (Node returns true), and assert.deepStrictEqual threw.

Cause

Two bugs:

  1. specialObjectsDequal in bindings.cpp compared Dates with left->internalNumber() == right->internalNumber(), which is NaN == NaN for Invalid Dates, so they never compared equal. Node compares Dates with Object.is(getTime(), getTime()) semantics, under which two NaN time values are equal.
  2. The test runner's failure diff renders Dates by JSON-stringifying and trimming the surrounding quotes. JSON.stringify(new Date(NaN)) is the unquoted null, so trimming produced the garbage ul. The console formatter already special-cases this and prints Invalid Date; the test runner's copy was missing that branch.

Fix

  • Dates in deepEquals compare equal when both time values are NaN. This covers toEqual, toStrictEqual, Bun.deepEquals, assert.deepStrictEqual/deepEqual, and util.isDeepStrictEqual (they share the same comparison). This matches Node; jest's expect disagrees (it also fails this case), but Node parity plus expect(NaN).toEqual(NaN) passing makes equal the consistent answer.
  • The test runner's diff now prints Invalid Date for a NaN Date, matching console.log.

Verification

New tests fail on the released build and pass with this change:

  • test/js/bun/bun-object/deep-equals.spec.ts: Bun.deepEquals(new Date(NaN), new Date(NaN)) in both strict modes, plus invalid-vs-valid inequality
  • test/js/node/assert/deep-equal.test.ts: matrix entries for two invalid dates (strict and loose) and invalid vs valid
  • test/js/bun/test/expect.test.js: toEqual/toStrictEqual on Invalid Dates (Bun-only branch, since jest diverges)
  • test/js/bun/test/expect-invalid-date-message.test.ts: failure message renders Received: Invalid Date, not ul

[review] gate passed · iteration 0 · 6 files touched

fails on main (without fix)
ASAN without fix: 5 failed, 10 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/test/expect-invalid-date-message.test.ts test/js/bun/test/expect.test.js test/js/node/assert/deep-equal.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (3cc58c633)

test/js/bun/test/expect.test.js:
(pass) expect() > () [594.80ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [2.51ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [0.57ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [0.38ms]
(pass) expect() > toBe() > expect(-0).toBe(-0) == true [0.37ms]
(pass) expect() > toBe() > expect(1).toBe(1) == true [0.40ms]
(pass) expect() > toBe() > expect(1).toBe(1) == true [0.37ms]
(pass) expect() > toBe() > expect(NaN).toBe(NaN) == true [0.46ms]
(pass) expect() > toBe() > expect(Infinity).toBe(Infinity) == true [0.38ms]
(pass) expect() > toBe() > expect({}).toBe({}) == true [0.4
... (truncated)

release without fix: 12 failed, 10 skipped
bun test v1.4.0-canary.1 (1498d7b77)

test/js/bun/test/expect.test.js:
(pass) expect() > () [0.69ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [0.03ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true
(pass) expect() > toBe() > expect(0).toBe(0) == true
(pass) expect() > toBe() > expect(-0).toBe(-0) == true
(pass) expect() > toBe() > expect(1).toBe(1) == true
(pass) expect() > toBe() > expect(1).toBe(1) == true
(pass) expect() > toBe() > expect(NaN).toBe(NaN) == true
(pass) expect() > toBe() > expect(Infinity).toBe(Infinity) == true
(pass) expect() > toBe() > expect({}).toBe({}) == true
(pass) expect() > toBe() > expect(Symbol(a)).toBe(Symbol(a)) == true
(pass) expect() > toBe() > expect(0).toBe(false) == false [0.02ms]
(pass) expect() > toBe() > expect(0).toBe("") == false
(pass) expect() > toBe() > expect(0).toBe(-0) == false
(pass) expect() > toBe() > expect(0).toBe(-0) == false
(pass) expect() > toBe() > expect(1).toBe(2) == false
(pass) expect() > toBe() > expect(1).toBe(true) == false
(pass) expect() > toBe() > expect(1).toBe("1") == false
(pass) expect() > toBe() > expect(Infinity).toBe(-Infinity) == false
(pass) expect() > toBe() > expect("foo
... (truncated)
passes on PR (with fix)
ASAN with fix: 10 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/test/expect-invalid-date-message.test.ts test/js/bun/test/expect.test.js test/js/node/assert/deep-equal.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (3cc58c633)

test/js/bun/test/expect.test.js:
(pass) expect() > () [590.80ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [2.58ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [0.63ms]
(pass) expect() > toBe() > expect(0).toBe(0) == true [0.43ms]
(pass) expect() > toBe() > expect(-0).toBe(-0) == true [0.42ms]
(pass) expect() > toBe() > expect(1).toBe(1) == true [0.41ms]
(pass) expect() > toBe() > expect(1).toBe(1) == true [0.43ms]
(pass) expect() > toBe() > expect(NaN).toBe(NaN) == true [0.40ms]
(pass) expect() > toBe() > expect(Infinity).toBe(Infinity) == true [0.42ms]
(pass) expect() > toBe() > expect({}).toBe({}) == true [0.4
... (truncated)

release with fix: 10 skipped
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     3cc58c6333
  features     (none)

22 deps, 106 codegen, 1169 objects in 786ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1232] install /workspace/bun
bun install v1.4.0-canary.1 (1498d7b77)

Checked 124 installs across 170 packages (no changes) [12.00ms]
[2/1232] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (1498d7b77)

Checked 1 install across 2 packages (no changes) [1.00ms]
[3/1232] gen ErrorCode+*.h
[4/1232] gen bindgenv2
[5/1232] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (1498d7b77)

Checked 129 installs across 147 packages (no changes) [10.00ms]
[6/1232] fetch zlib
[zlib] up to date
[7/1232] gen .bind.ts → GeneratedBindings.cpp
[8/1232] fetch tinycc
[tinyc
... (truncated)
diff hotspot
src/jsc/bindings/bindings.cpp                      |  5 +++-
 src/runtime/test_runner/pretty_format.rs           |  5 +++-
 test/js/bun/bun-object/deep-equals.spec.ts         |  2 ++
 .../bun/test/expect-invalid-date-message.test.ts   | 29 ++++++++++++++++++++++
 test/js/bun/test/expect.test.js                    |  8 ++++++
 test/js/node/assert/deep-equal.test.ts             |  2 ++
 6 files changed, 49 insertions(+), 2 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                  reads  edits  tests
src/jsc/bindings/bindings.cpp                             2      1      0
src/runtime/test_runner/pretty_format.rs                  1      1      0
test/js/bun/bun-object/deep-equals.spec.ts                1      1      0
test/js/bun/test/expect-invalid-date-message.test.ts      0      2      0
test/js/bun/test/expect.test.js                           1      1      0
test/js/node/assert/deep-equal.test.ts                    1      2      0

root cause · written by the author bot

Deep equality compared two Date objects by checking their internal time values with a plain numeric equality, which fails when both dates are invalid because an invalid date's time value is NaN and NaN never equals itself. The fix updates the Date branch of the native deep-equality routine to treat two NaN time values as equal, matching Node's Object.is semantics, so that expect(new Date(NaN)).toEqual(new Date(NaN)) and the related assert and Bun.deepEquals surfaces now pass while invalid versus valid dates still compare unequal. A companion change in the test runner's pretty formatter ma…

…lure-message rendering

Two Dates with NaN time values now compare equal in deepEquals
(toEqual, toStrictEqual, Bun.deepEquals, assert.deepStrictEqual,
util.isDeepStrictEqual), matching Node.js.

The test runner's failure diff printed a sliced 'null' ("ul") for
Invalid Date because JSON.stringify(new Date(NaN)) is unquoted null;
it now prints "Invalid Date" like console.log already does.

Fixes #34816
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bae16860-8b29-4f20-85a0-c28b16964774

📥 Commits

Reviewing files that changed from the base of the PR and between f99d21d and 825a3fc.

📒 Files selected for processing (1)
  • test/js/bun/test/expect.test.js

Walkthrough

Changes

Invalid Date equality now treats two NaN internal times as equal. Date formatting renders invalid dates as Invalid Date, with expanded equality and expectation-output coverage.

Invalid Date handling

Layer / File(s) Summary
Date equality semantics
src/jsc/bindings/bindings.cpp, test/js/bun/bun-object/deep-equals.spec.ts, test/js/bun/test/expect.test.js, test/js/node/assert/deep-equal.test.ts
Date comparisons now equate two invalid dates and distinguish invalid dates from valid dates, with Bun and Node-compatible tests.
Invalid Date diagnostics
src/runtime/test_runner/pretty_format.rs, test/js/bun/test/expect-invalid-date-message.test.ts
Expectation formatting converts invalid-date JSON null output to Invalid Date, with subprocess coverage for the resulting failure message.

Possibly related issues

Possibly related PRs

  • oven-sh/bun#33080 — Updates the same deep-equality helper with related prototype-handling changes.
  • oven-sh/bun#34434 — Modifies the same specialObjectsDequal deep-equality path.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: invalid dates compare equal and their failure message rendering is fixed.
Description check ✅ Passed The description covers the fix and verification, though it uses different headings than the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
test/js/bun/test/expect.test.js

ast-grep timed out on this file


Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:50 AM PT - Jul 20th, 2026

@robobun, your commit 6e737f3 has 1 failures in Build #76297 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34819

That installs a local version of the PR into your bun-34819 executable, so you can run:

bun-34819 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Implement Node's deep equality algorithm for node:assert #33068 - Also fixes Invalid Date (NaN) deep equality comparison in specialObjectsDequal, where NaN == NaN was returning false

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the inline nit about draining stdout in the new subprocess test is worth tidying but not blocking.

Extended reasoning...

Overview

Two small, localized fixes for Invalid Date handling:

  • src/jsc/bindings/bindings.cpp: the Date arm of specialObjectsDequal now treats two NaN time values as equal (a == b || (isnan(a) && isnan(b))), matching Node's Object.is(getTime(), getTime()) semantics. This flows through Bun.deepEquals, toEqual/toStrictEqual, assert.deepStrictEqual/deepEqual, and util.isDeepStrictEqual.
  • src/runtime/test_runner/pretty_format.rs: the JSDate branch of the failure-diff formatter now special-cases the null output of JSON.stringify(new Date(NaN)) and prints Invalid Date instead of quote-trimming it into ul. This is a byte-for-byte copy of the existing branch in src/jsc/ConsoleObject.rs:4372.

Tests add matrix rows to three existing files and one new subprocess test asserting the failure message.

Security risks

None. No user-controlled input parsing, no allocation, no I/O — pure comparison/formatting of an already-validated internal double.

Level of scrutiny

Low. Both native changes are a handful of lines each, follow established sibling code exactly, and are covered by tests across every consumer surface (Bun.deepEquals strict/loose, toEqual/toStrictEqual, assert strict/loose, util.isDeepStrictEqual, plus the failure-message rendering). I checked for other Date-equality comparison sites via internalNumber() in src/jsc/bindings/ — this is the only one; the rest are extractions, not comparisons. ECMA-262's TimeClip guarantees the internal value is either an integral finite number or NaN, so the added disjunct doesn't change ±0 behavior.

Other factors

  • The one semantic choice — diverging from jest (which also fails new Date(NaN) equality) in favor of Node parity — is explained in the description, is consistent with expect(NaN).toEqual(NaN) already passing, and is scoped behind isBun in the shared expect test.
  • The inline nit (piped-but-undrained stdout in the new subprocess test) is a harness-convention cleanup; the child writes far under the pipe buffer here, so it's not a correctness or flake risk today.

Comment thread test/js/bun/test/expect-invalid-date-message.test.ts Outdated
@robobun

robobun commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Not a duplicate of #33068: that PR is a broader node:assert deep-equality rewrite that happens to include the same one-line NaN-date change in specialObjectsDequal. This PR is a focused fix for #34816 and also fixes the test runner's failure-message rendering for Invalid Date (the "Expected: ul" garbage), which #33068 does not touch. If #33068 lands first, the bindings.cpp hunk here becomes a trivial merge conflict and the rest still applies.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused two-line native fix with matching test coverage; the earlier stdout-drain nit was addressed in 3cc58c6.

What was reviewed:

  • specialObjectsDequal Date arm: NaN-aware compare is the standard a == b || (isnan(a) && isnan(b)) shape, matches Node's Object.is(getTime()) semantics.
  • pretty_format.rs Date branch: byte-for-byte the same b"null" → "Invalid Date" guard already in ConsoleObject.rs:4372, so the two formatters now agree.
  • New tests cover all surfaces sharing the comparison (Bun.deepEquals strict/loose, toEqual/toStrictEqual, assert.deepStrictEqual/deepEqual, util.isDeepStrictEqual) plus invalid-vs-valid inequality and the failure-message rendering.
Extended reasoning...

Overview

Two tiny native changes plus test coverage across four files:

  • src/jsc/bindings/bindings.cpp (+4/-1): the JSDateType arm of specialObjectsDequal now treats two NaN time values as equal instead of falling through NaN == NaN.
  • src/runtime/test_runner/pretty_format.rs (+4/-1): the JSON-stringify Date path now maps the unquoted null from JSON.stringify(new Date(NaN)) to Invalid Date before the quote-trim step, exactly mirroring the existing branch in src/jsc/ConsoleObject.rs:4372-4376.
  • Test additions in deep-equals.spec.ts, expect.test.js, deep-equal.test.ts, and a new subprocess test for the failure-message rendering.

Security risks

None. This is equality semantics for Date objects and diagnostic-message formatting. No untrusted input parsing, no auth/crypto/permission surface, no memory management changes. internalNumber() returns a plain double from the DateInstance, and std::isnan on it is side-effect-free.

Level of scrutiny

Low. Each native change is a single conditional following an established pattern:

  • The bindings.cpp change is the canonical NaN-aware double compare; it only widens equality for the one case where both sides are NaN and cannot change any other Date comparison result.
  • The pretty_format.rs change is a copy of the sibling branch already shipping in the console formatter, applied at the same point in an otherwise-identical code shape (out_buf == b"null" before the len() > 2 quote-trim). The b"null" check is on raw JSON output inside the js_type == JSType::JSDate guard, so it can't collide with a valid ISO string.

Other factors

  • Bug-class coverage: the PR covers every consumer of specialObjectsDequal for Dates (Bun.deepEquals both modes, toEqual/toStrictEqual, node:assert strict+loose, util.isDeepStrictEqual) and the negative case (invalid vs valid), matching REVIEW.md's variant-matrix guidance.
  • Jest divergence: the expect.test.js addition is correctly gated on if (isBun) since upstream jest disagrees; the PR description explains the Node-parity rationale.
  • Prior feedback addressed: my earlier nit about the undrained stdout pipe in expect-invalid-date-message.test.ts was fixed in 3cc58c6 — the test now uses the canonical three-way Promise.all([stdout.text(), stderr.text(), exited]).
  • Harness conventions: the new test file uses tempDir, bunExe, spreads bunEnv, sets NO_COLOR, asserts stderr content before the exit code, and asserts a nonzero exit — all in line with the repo's test guidance.
  • Overlap with #33068: the author already noted the bindings.cpp hunk overlaps a larger open PR; that's a merge-conflict concern, not a correctness one, and this PR additionally fixes the ul rendering that #33068 doesn't touch.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the earlier stdout-drain nit was addressed in 3cc58c6 and this run found nothing further.

What was reviewed:

  • specialObjectsDequal Date branch: NaN-aware equality matches Node's Object.is(getTime()) semantics; valid-vs-invalid still returns false.
  • pretty_format.rs change is byte-for-byte the same guard already in ConsoleObject.rs:4372, scoped to the JSDate branch only.
  • New tests cover all shared entry points (Bun.deepEquals strict/loose, toEqual/toStrictEqual, assert.deepStrictEqual/deepEqual, util.isDeepStrictEqual) plus the failure-message rendering; evidence gate confirms they fail on main and pass here.
Extended reasoning...

Overview

Two tiny production changes fixing #34816: (1) src/jsc/bindings/bindings.cpp — the JSDateType arm of specialObjectsDequal now treats two NaN time values as equal (leftTime == rightTime || (isnan(leftTime) && isnan(rightTime))), matching Node's Object.is semantics for Date comparison; (2) src/runtime/test_runner/pretty_format.rs — the JSON-stringify-then-trim-quotes path for Dates now special-cases the unquoted null that JSON.stringify(new Date(NaN)) produces and prints Invalid Date instead of the sliced garbage ul. Four test files add coverage: Bun.deepEquals matrix, node:assert matrix, expect.test.js toEqual/toStrictEqual (Bun-only, since jest diverges), and a new subprocess test asserting the failure-message text.

Security risks

None. This is deep-equality comparison logic and diagnostic output formatting for the test runner; no untrusted input parsing, no auth/crypto/permissions surface.

Level of scrutiny

Low-to-moderate. The C++ change is a 3-line arithmetic tweak in a well-understood switch arm with no allocation, no exception-throwing calls, and no lifetime concerns — internalNumber() is a plain double read. The Rust change is an exact copy of the guard that already exists at src/jsc/ConsoleObject.rs:4372-4377 (the console formatter that this pretty-formatter is a near-duplicate of), so it follows established precedent rather than introducing new logic. The b"null" check is inside the js_type == JSType::JSDate branch, so it can't affect non-Date JSON output.

Other factors

  • The evidence gate in the PR description shows the new tests fail on main (5 failures ASAN, 12 release) and pass with the fix (0 failures both), satisfying the "fails for the right reason" requirement.
  • My prior review nit (undrained stdout: "pipe") was addressed in 3cc58c6 — the current diff shows the canonical three-way Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]).
  • The PR description explicitly documents the jest divergence and justifies choosing Node parity (consistent with expect(NaN).toEqual(NaN) already passing), and the expect.test.js additions are gated behind if (isBun) so the file still runs against jest.
  • The overlap with #33068 was acknowledged; this PR's pretty-format fix is unique to it and the bindings.cpp hunk would be a trivial merge if #33068 lands first.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/test_runner/pretty_format.rs`:
- Around line 1940-1942: Update the JSON date formatting logic around the
out_buf == b"null" branch to render Invalid Date only when the Date’s internal
time value is invalid, rather than for every null result from toJSON(). Preserve
null output for valid Dates whose toJSON() returns null, and add a regression
test covering that case.

In `@test/js/bun/test/expect.test.js`:
- Around line 725-731: Extend the invalid-versus-valid Date assertions in the
isBun block to also use toStrictEqual: assert new Date(NaN) is not strictly
equal to new Date(0), and new Date(0) is not strictly equal to new Date(NaN).
Keep the existing toEqual assertions and invalid-Date equality checks unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9fca0639-a12a-4401-a7ce-731642f46734

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff1454 and f99d21d.

📒 Files selected for processing (6)
  • src/jsc/bindings/bindings.cpp
  • src/runtime/test_runner/pretty_format.rs
  • test/js/bun/bun-object/deep-equals.spec.ts
  • test/js/bun/test/expect-invalid-date-message.test.ts
  • test/js/bun/test/expect.test.js
  • test/js/node/assert/deep-equal.test.ts

Comment thread src/runtime/test_runner/pretty_format.rs
Comment thread test/js/bun/test/expect.test.js

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused fix with good test coverage across all affected surfaces.

What was reviewed:

  • Date comparison in specialObjectsDequal: NaN-handling matches Node's Object.is(getTime(), getTime()) semantics; valid-vs-invalid still rejects.
  • pretty_format.rs b"null" branch: confirmed byte-for-byte match with ConsoleObject.rs:4372-4377, so the two formatters agree.
  • Tests slot into existing it.each/cases matrices; subprocess test drains all pipes and asserts exit code 1.
Extended reasoning...

Overview

Two-line native fix in src/jsc/bindings/bindings.cpp (Date branch of specialObjectsDequal) so two Invalid Dates compare equal, plus a three-line branch in src/runtime/test_runner/pretty_format.rs so the failure diff prints Invalid Date instead of the sliced garbage ul. Four test files add coverage: Bun.deepEquals, expect().toEqual/toStrictEqual, assert.deepStrictEqual/deepEqual/util.isDeepStrictEqual, and a subprocess test for the failure-message rendering.

Security risks

None. No user input parsing, allocation, or I/O changes. The bindings.cpp change only adds an std::isnan check on doubles already read from DateInstance::internalNumber(); the formatter change swaps a static byte slice.

Level of scrutiny

Low-to-moderate. The specialObjectsDequal path is shared by toEqual, toStrictEqual, Bun.deepEquals, and the node:assert/util surfaces, so a regression would be visible — but the change is a strict widening (a == b || (isnan(a) && isnan(b))) that only affects the case where both were previously NaN, which was demonstrably wrong. The formatter change is a verbatim copy of the existing ConsoleObject.rs branch (verified at lines 4372-4377), so it brings the two copies into agreement rather than introducing new behavior.

Other factors

  • The robobun evidence gate shows the new tests fail on both ASAN-debug and release builds without the fix and pass with it.
  • My earlier nit (undrained stdout pipe) was fixed in 3cc58c6; CodeRabbit's toStrictEqual matrix suggestion was applied in 825a3fc; the toJSON-returns-null edge case was reasonably declined (mirrors console formatter, strictly better than the prior ul output). All review threads are resolved.
  • Test additions follow existing file conventions (extending it.each tables and the cases matrix rather than new ad-hoc blocks).
  • Overlap with #33068 is a single-line intersection; this PR additionally fixes the failure-message rendering, which #33068 does not.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused two-line fix with the right semantics and coverage.

What was reviewed:

  • Date NaN comparison in specialObjectsDequal — additive to the existing ==, matches Node's Object.is semantics; valid-vs-invalid still unequal.
  • pretty_format.rs b"null" branch mirrors ConsoleObject.rs:4372 byte-for-byte, so the two formatters stay in agreement.
  • Prior review nits (stdout drain, toStrictEqual negative cases) are addressed and resolved; tests span Bun.deepEquals, expect, and node:assert.
Extended reasoning...

Overview

Two tightly scoped changes: (1) src/jsc/bindings/bindings.cpp adds || (std::isnan(leftTime) && std::isnan(rightTime)) to the JSDateType arm of specialObjectsDequal, so two Invalid Dates compare equal under Bun.deepEquals/toEqual/assert.deepStrictEqual; and (2) src/runtime/test_runner/pretty_format.rs special-cases JSON.stringify(new Date(NaN)) === "null" to print Invalid Date instead of the quote-trimmed garbage ul. Four test files add coverage across every surface that shares the comparison.

Security risks

None. No untrusted input parsing, no allocation, no new JS entry points. internalNumber() is a plain double read on an already-type-checked DateInstance; std::isnan is pure. The formatter change compares a local buffer to a literal.

Level of scrutiny

Low-to-moderate. The C++ change is a one-clause boolean OR that is strictly additive to the previous == — every case that returned true before still does, and only NaN/NaN flips from false to true, which is the documented Node behavior. The Rust change is copied verbatim from the sibling formatter at ConsoleObject.rs:4372-4377, so it introduces no divergence between console.log and expect-diff rendering. The PR description includes gate evidence that the new tests fail on main (both ASAN debug and release) and pass with the fix.

Other factors

All three prior review threads are resolved: the stdout-drain harness nit I raised was fixed in 3cc58c6, CodeRabbit's toStrictEqual invalid-vs-valid coverage was added in 825a3fc, and CodeRabbit withdrew the toJSON()→null concern once formatter parity was pointed out. Test coverage hits the full variant matrix — Bun.deepEquals strict/loose, toEqual/toStrictEqual, assert.deepStrictEqual/deepEqual/util.isDeepStrictEqual, plus the negative (invalid vs valid) direction and the failure-message subprocess test. Nothing left outstanding.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bun test toEqual incorrect comparison of Invalid Date.

1 participant