From a80fdd7af2196886a65e083cea98a564681db84a Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 29 Jan 2025 18:49:54 -0800 Subject: [PATCH 01/20] test-assert-esm-cjs-message-verify --- test/js/node/harness.ts | 1 + .../test-assert-esm-cjs-message-verify.js | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-esm-cjs-message-verify.js diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts index 52bfb3c33a8039..ff96f370105b08 100644 --- a/test/js/node/harness.ts +++ b/test/js/node/harness.ts @@ -372,6 +372,7 @@ if (normalized.includes("node/test/parallel")) { return { test, + it: test, describe, }; } diff --git a/test/js/node/test/parallel/needs-test/test-assert-esm-cjs-message-verify.js b/test/js/node/test/parallel/needs-test/test-assert-esm-cjs-message-verify.js new file mode 100644 index 00000000000000..fa5cdc03409755 --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-esm-cjs-message-verify.js @@ -0,0 +1,31 @@ +'use strict'; + +const { spawnPromisified } = require('../../common'); +const assert = require('node:assert'); +const { describe, it } = require('node:test'); + +const fileImports = { + commonjs: 'const assert = require("assert");', + module: 'import assert from "assert";', +}; + +describe('ensure the assert.ok throwing similar error messages for esm and cjs files', () => { + it('should return code 1 for each command', async () => { + const errorsMessages = []; + for (const [inputType, header] of Object.entries(fileImports)) { + const { stderr, code } = await spawnPromisified(process.execPath, [ + '--input-type', + inputType, + '--eval', + `${header}\nassert.ok(0 === 2);\n`, + ]); + assert.strictEqual(code, 1); + // For each error message, filter the lines which will starts with AssertionError + errorsMessages.push( + stderr.split('\n').find((s) => s.startsWith('AssertionError')) + ); + } + assert.strictEqual(errorsMessages.length, 2); + assert.deepStrictEqual(errorsMessages[0], errorsMessages[1]); + }); +}); From 6d050f45dfca43c7d076c6b224e758dacdceead3 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 29 Jan 2025 18:58:25 -0800 Subject: [PATCH 02/20] test-assert-calltracker --- src/js/internal/assert/assertion_error.ts | 3 + src/js/internal/primordials.js | 8 ++ .../test-assert-calltracker-getCalls.js | 74 ++++++++++ .../parallel/test-assert-calltracker-calls.js | 128 ++++++++++++++++++ .../test-assert-calltracker-report.js | 26 ++++ .../test-assert-calltracker-verify.js | 53 ++++++++ 6 files changed, 292 insertions(+) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-calltracker-getCalls.js create mode 100644 test/js/node/test/parallel/test-assert-calltracker-calls.js create mode 100644 test/js/node/test/parallel/test-assert-calltracker-report.js create mode 100644 test/js/node/test/parallel/test-assert-calltracker-verify.js diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts index 780e3489152f57..67a4ad03073730 100644 --- a/src/js/internal/assert/assertion_error.ts +++ b/src/js/internal/assert/assertion_error.ts @@ -377,6 +377,9 @@ class AssertionError extends Error { this.operator = operator; } ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction); + if ($isUndefinedOrNull(this.stack)) { + ErrorCaptureStackTrace(this, AssertionError); + } // Create error message including the error code in the name. this.stack; // eslint-disable-line no-unused-expressions // Reset the name. diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index 046f44fa659e48..d478237597af6b 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -129,6 +129,14 @@ export default { } }, ), + SafeWeakMap: makeSafe( + WeakMap, + class SafeWeakMap extends WeakMap { + constructor(i) { + super(i); + } + }, + ), SetPrototypeGetSize: getGetter(Set, "size"), String, TypedArrayPrototypeGetLength: getGetter(Uint8Array, "length"), diff --git a/test/js/node/test/parallel/needs-test/test-assert-calltracker-getCalls.js b/test/js/node/test/parallel/needs-test/test-assert-calltracker-getCalls.js new file mode 100644 index 00000000000000..5332be4abfeee0 --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-calltracker-getCalls.js @@ -0,0 +1,74 @@ +'use strict'; +require('../../common'); +const assert = require('assert'); +const { describe, it } = require('node:test'); + + +describe('assert.CallTracker.getCalls()', { concurrency: !process.env.TEST_PARALLEL }, () => { + const tracker = new assert.CallTracker(); + + it('should return empty list when no calls', () => { + const fn = tracker.calls(); + assert.deepStrictEqual(tracker.getCalls(fn), []); + }); + + it('should return calls', () => { + const fn = tracker.calls(() => {}); + const arg1 = {}; + const arg2 = {}; + fn(arg1, arg2); + fn.call(arg2, arg2); + assert.deepStrictEqual(tracker.getCalls(fn), [ + { arguments: [arg1, arg2], thisArg: undefined }, + { arguments: [arg2], thisArg: arg2 }]); + }); + + it('should throw when getting calls of a non-tracked function', () => { + [() => {}, 1, true, null, undefined, {}, []].forEach((fn) => { + assert.throws(() => tracker.getCalls(fn), { code: 'ERR_INVALID_ARG_VALUE' }); + }); + }); + + it('should return a frozen object', () => { + const fn = tracker.calls(); + fn(); + const calls = tracker.getCalls(fn); + // NOTE: v8 and jsc use different error messages for mutating frozen objects. + assert.throws(() => calls.push(1), /Attempted to assign to readonly property/); + assert.throws(() => Object.assign(calls[0], { foo: 'bar' }), /object that is not extensible/); + assert.throws(() => calls[0].arguments.push(1), /Attempted to assign to readonly property/); + }); +}); + +describe('assert.CallTracker.reset()', () => { + const tracker = new assert.CallTracker(); + + it('should reset calls', () => { + const fn = tracker.calls(); + fn(); + fn(); + fn(); + assert.strictEqual(tracker.getCalls(fn).length, 3); + tracker.reset(fn); + assert.deepStrictEqual(tracker.getCalls(fn), []); + }); + + it('should reset all calls', () => { + const fn1 = tracker.calls(); + const fn2 = tracker.calls(); + fn1(); + fn2(); + assert.strictEqual(tracker.getCalls(fn1).length, 1); + assert.strictEqual(tracker.getCalls(fn2).length, 1); + tracker.reset(); + assert.deepStrictEqual(tracker.getCalls(fn1), []); + assert.deepStrictEqual(tracker.getCalls(fn2), []); + }); + + + it('should throw when resetting a non-tracked function', () => { + [() => {}, 1, true, null, {}, []].forEach((fn) => { + assert.throws(() => tracker.reset(fn), { code: 'ERR_INVALID_ARG_VALUE' }); + }); + }); +}); diff --git a/test/js/node/test/parallel/test-assert-calltracker-calls.js b/test/js/node/test/parallel/test-assert-calltracker-calls.js new file mode 100644 index 00000000000000..7b73f3fefaf6ab --- /dev/null +++ b/test/js/node/test/parallel/test-assert-calltracker-calls.js @@ -0,0 +1,128 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// This test ensures that assert.CallTracker.calls() works as intended. + +const tracker = new assert.CallTracker(); + +function bar() {} + +const err = { + code: 'ERR_INVALID_ARG_TYPE', +}; + +// Ensures calls() throws on invalid input types. +assert.throws(() => { + const callsbar = tracker.calls(bar, '1'); + callsbar(); +}, err +); + +assert.throws(() => { + const callsbar = tracker.calls(bar, 0.1); + callsbar(); +}, { code: 'ERR_OUT_OF_RANGE' } +); + +assert.throws(() => { + const callsbar = tracker.calls(bar, true); + callsbar(); +}, err +); + +assert.throws(() => { + const callsbar = tracker.calls(bar, () => {}); + callsbar(); +}, err +); + +assert.throws(() => { + const callsbar = tracker.calls(bar, null); + callsbar(); +}, err +); + +// Expects an error as tracker.calls() cannot be called within a process exit +// handler. +process.on('exit', () => { + assert.throws(() => tracker.calls(bar, 1), { + code: 'ERR_UNAVAILABLE_DURING_EXIT', + }); +}); + +const msg = 'Expected to throw'; + +function func() { + throw new Error(msg); +} + +const callsfunc = tracker.calls(func, 1); + +// Expects callsfunc() to call func() which throws an error. +assert.throws( + () => callsfunc(), + { message: msg } +); + +{ + const tracker = new assert.CallTracker(); + const callsNoop = tracker.calls(1); + callsNoop(); + tracker.verify(); +} + +{ + const tracker = new assert.CallTracker(); + const callsNoop = tracker.calls(undefined, 1); + callsNoop(); + tracker.verify(); +} + +{ + function func() {} + const tracker = new assert.CallTracker(); + const callsfunc = tracker.calls(func); + assert.strictEqual(callsfunc.length, 0); +} + +{ + function func(a, b, c = 2) {} + const tracker = new assert.CallTracker(); + const callsfunc = tracker.calls(func); + assert.strictEqual(callsfunc.length, 2); +} + +{ + function func(a, b, c = 2) {} + delete func.length; + const tracker = new assert.CallTracker(); + const callsfunc = tracker.calls(func); + assert.strictEqual(Object.hasOwn(callsfunc, 'length'), false); +} + +{ + const ArrayIteratorPrototype = Reflect.getPrototypeOf( + Array.prototype.values() + ); + const { next } = ArrayIteratorPrototype; + ArrayIteratorPrototype.next = common.mustNotCall( + '%ArrayIteratorPrototype%.next' + ); + Object.prototype.get = common.mustNotCall('%Object.prototype%.get'); + + const customPropertyValue = Symbol(); + function func(a, b, c = 2) { + return a + b + c; + } + func.customProperty = customPropertyValue; + Object.defineProperty(func, 'length', { get: common.mustNotCall() }); + const tracker = new assert.CallTracker(); + const callsfunc = tracker.calls(func); + assert.strictEqual(Object.hasOwn(callsfunc, 'length'), true); + assert.strictEqual(callsfunc.customProperty, customPropertyValue); + assert.strictEqual(callsfunc(1, 2, 3), 6); + + ArrayIteratorPrototype.next = next; + delete Object.prototype.get; +} diff --git a/test/js/node/test/parallel/test-assert-calltracker-report.js b/test/js/node/test/parallel/test-assert-calltracker-report.js new file mode 100644 index 00000000000000..87ef0bff17fc0b --- /dev/null +++ b/test/js/node/test/parallel/test-assert-calltracker-report.js @@ -0,0 +1,26 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// This test ensures that the assert.CallTracker.report() works as intended. + +const tracker = new assert.CallTracker(); + +function foo() {} + +const callsfoo = tracker.calls(foo, 1); + +// Ensures that foo was added to the callChecks array. +assert.strictEqual(tracker.report()[0].operator, 'foo'); + +callsfoo(); + +// Ensures that foo was removed from the callChecks array after being called the +// expected number of times. +assert.strictEqual(typeof tracker.report()[0], 'undefined'); + +callsfoo(); + +// Ensures that foo was added back to the callChecks array after being called +// more than the expected number of times. +assert.strictEqual(tracker.report()[0].operator, 'foo'); diff --git a/test/js/node/test/parallel/test-assert-calltracker-verify.js b/test/js/node/test/parallel/test-assert-calltracker-verify.js new file mode 100644 index 00000000000000..118f04f7352780 --- /dev/null +++ b/test/js/node/test/parallel/test-assert-calltracker-verify.js @@ -0,0 +1,53 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// This test ensures that assert.CallTracker.verify() works as intended. + +const tracker = new assert.CallTracker(); + +const generic_msg = 'Functions were not called the expected number of times'; + +function foo() {} + +function bar() {} + +const callsfoo = tracker.calls(foo, 1); +const callsbar = tracker.calls(bar, 1); + +// Expects an error as callsfoo() and callsbar() were called less than one time. +assert.throws( + () => tracker.verify(), + { message: generic_msg } +); + +callsfoo(); + +// Expects an error as callsbar() was called less than one time. +assert.throws( + () => tracker.verify(), + { message: 'Expected the bar function to be executed 1 time(s) but was executed 0 time(s).' } +); +callsbar(); + +// Will throw an error if callsfoo() and callsbar isn't called exactly once. +tracker.verify(); + +const callsfoobar = tracker.calls(foo, 1); + +callsfoo(); + +// Expects an error as callsfoo() was called more than once and callsfoobar() was called less than one time. +assert.throws( + () => tracker.verify(), + { message: generic_msg } +); + +callsfoobar(); + + +// Expects an error as callsfoo() was called more than once +assert.throws( + () => tracker.verify(), + { message: 'Expected the foo function to be executed 1 time(s) but was executed 2 time(s).' } +); From 6a4653070fbf2e62b5bd8c5350ed242d5e1f0634 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 29 Jan 2025 21:35:18 -0800 Subject: [PATCH 03/20] test-assert-fail --- .../parallel/needs-test/test-assert-fail.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-fail.js diff --git a/test/js/node/test/parallel/needs-test/test-assert-fail.js b/test/js/node/test/parallel/needs-test/test-assert-fail.js new file mode 100644 index 00000000000000..1d9fbc5784eb4c --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-fail.js @@ -0,0 +1,50 @@ +'use strict'; + +require('../../common'); +const assert = require('assert'); +const { test } = require('node:test'); + +test('No args', () => { + assert.throws( + () => { assert.fail(); }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Failed', + operator: 'fail', + actual: undefined, + expected: undefined, + generatedMessage: true, + stack: /Failed/ + } + ); +}); + +test('One arg = message', () => { + assert.throws(() => { + assert.fail('custom message'); + }, { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'custom message', + operator: 'fail', + actual: undefined, + expected: undefined, + generatedMessage: false + }); +}); + +test('One arg = Error', () => { + assert.throws(() => { + assert.fail(new TypeError('custom message')); + }, { + name: 'TypeError', + message: 'custom message' + }); +}); + +test('Object prototype get', () => { + Object.prototype.get = () => { throw new Error('failed'); }; + assert.throws(() => assert.fail(''), { code: 'ERR_ASSERTION' }); + delete Object.prototype.get; +}); From 8182a4b902fe55fa39af0794df38093467e03881 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 29 Jan 2025 21:45:44 -0800 Subject: [PATCH 04/20] test-assert-if-error --- test/js/node/harness.ts | 6 +- .../needs-test/test-assert-if-error.js | 104 ++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-if-error.js diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts index ff96f370105b08..88b4378bf129d1 100644 --- a/test/js/node/harness.ts +++ b/test/js/node/harness.ts @@ -348,8 +348,9 @@ if (normalized.includes("node/test/parallel")) { } } - function describe(labelOrFn: string | Function, maybeFn?: Function) { - const [label, fn] = typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn]; + function describe(labelOrFn: string | Function, maybeFnOrOptions?: Function, maybeFn?: Function) { + const [label, fn] = + typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn ?? maybeFnOrOptions]; if (typeof fn !== "function") throw new TypeError("Second argument to describe() must be a function."); getContext().testStack.push(label); @@ -374,6 +375,7 @@ if (normalized.includes("node/test/parallel")) { test, it: test, describe, + suite: describe, }; } diff --git a/test/js/node/test/parallel/needs-test/test-assert-if-error.js b/test/js/node/test/parallel/needs-test/test-assert-if-error.js new file mode 100644 index 00000000000000..32d7456bae8ab5 --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-if-error.js @@ -0,0 +1,104 @@ +'use strict'; + +require('../../common'); +const assert = require('assert'); +const { test } = require('node:test'); + +test('Test that assert.ifError has the correct stack trace of both stacks', () => { + let err; + // Create some random error frames. + (function a() { + (function b() { + (function c() { + err = new Error('test error'); + })(); + })(); + })(); + + const msg = err.message; + const stack = err.stack; + + (function x() { + (function y() { + (function z() { + let threw = false; + try { + assert.ifError(err); + } catch (e) { + assert.strictEqual(e.message, + 'ifError got unwanted exception: test error'); + assert.strictEqual(err.message, msg); + assert.strictEqual(e.actual, err); + assert.strictEqual(e.actual.stack, stack); + assert.strictEqual(e.expected, null); + assert.strictEqual(e.operator, 'ifError'); + threw = true; + } + assert(threw); + })(); + })(); + })(); +}); + +test('General ifError tests', () => { + assert.throws( + () => { + const error = new Error(); + error.stack = 'Error: containing weird stack\nYes!\nI am part of a stack.'; + assert.ifError(error); + }, + (error) => { + assert(!error.stack.includes('Yes!')); + return true; + } + ); + + assert.throws( + () => assert.ifError(new TypeError()), + { + message: 'ifError got unwanted exception: TypeError' + } + ); + + assert.throws( + () => assert.ifError({ stack: false }), + { + message: 'ifError got unwanted exception: { stack: false }' + } + ); + + assert.throws( + () => assert.ifError({ constructor: null, message: '' }), + { + message: 'ifError got unwanted exception: ' + } + ); + + assert.throws( + () => { assert.ifError(false); }, + { + message: 'ifError got unwanted exception: false' + } + ); +}); + +test('Should not throw', () => { + assert.ifError(null); + assert.ifError(); + assert.ifError(undefined); +}); + +test('https://github.com/nodejs/node-v0.x-archive/issues/2893', () => { + let threw = false; + try { + // eslint-disable-next-line no-restricted-syntax + assert.throws(() => { + assert.ifError(null); + }); + } catch (e) { + threw = true; + assert.strictEqual(e.message, 'Missing expected exception.'); + assert(!e.stack.includes('throws'), e); + } + assert(threw); +}); From af7fb97a7c6ee6528b3fef2dd6cda6fc41876492 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 29 Jan 2025 21:46:28 -0800 Subject: [PATCH 05/20] wip --- .vscode/launch.json | 13 +++++++++++++ src/js/node/assert.ts | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b4149c3020b375..c5c33d5d986fa8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,19 @@ // - "cppvsdbg" is used instead of "lldb" on Windows, because "lldb" is too slow "version": "0.2.0", "configurations": [ + { + "type": "bun", + "request": "launch", + "name": "[js] bun test [file]", + "runtime": "${workspaceFolder}/build/debug/bun-debug", + "args": ["test", "${file}"], + "cwd": "${workspaceFolder}", + "env": { + "BUN_DEBUG_QUIET_LOGS": "1", + "BUN_DEBUG_jest": "1", + "BUN_GARBAGE_COLLECTOR_LEVEL": "1", + }, + }, // bun test [file] { "type": "lldb", diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index 4c12a84626e6ac..b95c454b32cc28 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -672,7 +672,7 @@ function getActual(fn) { return NO_EXCEPTION_SENTINEL; } -function checkIsPromise(obj) { +function checkIsPromise(obj): obj is Promise { // Accept native ES6 promises and promises that are implemented in a similar // way. Do not accept thenables that use a function as `obj` and that have no // `catch` handler. From 37f2454a3c719ef722b21b5130c54d0902097e88 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 30 Jan 2025 16:02:14 -0800 Subject: [PATCH 06/20] `test-assert-checktag` --- src/bun.js/bindings/BunObject.cpp | 4 +- src/bun.js/bindings/bindings.cpp | 6 +- .../needs-test/test-assert-checktag.js | 74 +++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-checktag.js diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index a694f5c86a0f9f..2aa8717432808b 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -489,12 +489,12 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject, JSC::JSValue arg1 = callFrame->uncheckedArgument(0); JSC::JSValue arg2 = callFrame->uncheckedArgument(1); - JSC::JSValue arg3 = callFrame->argument(2); + JSC::JSValue strict = callFrame->argument(2); Vector, 16> stack; MarkedArgumentBuffer gcBuffer; - if (arg3.isBoolean() && arg3.asBoolean()) { + if (strict.isBoolean() && strict.asBoolean()) { bool isEqual = Bun__deepEquals(globalObject, arg1, arg2, gcBuffer, stack, &scope, true); RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index f123454b52193c..15a524b8dd9bdc 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1371,7 +1371,11 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark compareAsNormalValue: break; } - + // globalThis is only equal to globalThis + // NOTE: Zig::GlobalObject is tagged as GlobalProxyType + case GlobalObjectType: + case GlobalProxyType: + return c1Type == c2Type; default: { break; } diff --git a/test/js/node/test/parallel/needs-test/test-assert-checktag.js b/test/js/node/test/parallel/needs-test/test-assert-checktag.js new file mode 100644 index 00000000000000..04f78f737a2442 --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-checktag.js @@ -0,0 +1,74 @@ +'use strict'; +const { hasCrypto } = require('../../common'); +const { test } = require('node:test'); +const assert = require('assert'); + +// Turn off no-restricted-properties because we are testing deepEqual! +/* eslint-disable no-restricted-properties */ + +// Disable colored output to prevent color codes from breaking assertion +// message comparisons. This should only be an issue when process.stdout +// is a TTY. +if (process.stdout.isTTY) + process.env.NODE_DISABLE_COLORS = '1'; + +test('', { skip: !hasCrypto }, () => { + // See https://github.com/nodejs/node/issues/10258 + { + const date = new Date('2016'); + function FakeDate() {} + FakeDate.prototype = Date.prototype; + const fake = new FakeDate(); + + assert.notDeepEqual(date, fake); + assert.notDeepEqual(fake, date); + + // For deepStrictEqual we check the runtime type, + // then reveal the fakeness of the fake date + assert.throws( + () => assert.deepStrictEqual(date, fake), + { + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ 2016-01-01T00:00:00.000Z\n' + + '- Date {}\n' + } + ); + assert.throws( + () => assert.deepStrictEqual(fake, date), + { + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ Date {}\n' + + '- 2016-01-01T00:00:00.000Z\n' + } + ); + } + + { // At the moment global has its own type tag + const fakeGlobal = {}; + Object.setPrototypeOf(fakeGlobal, Object.getPrototypeOf(globalThis)); + for (const prop of Object.keys(globalThis)) { + fakeGlobal[prop] = global[prop]; + } + assert.notDeepEqual(fakeGlobal, globalThis); + // Message will be truncated anyway, don't validate + assert.throws(() => assert.deepStrictEqual(fakeGlobal, globalThis), + assert.AssertionError); + } + + { // At the moment process has its own type tag + const fakeProcess = {}; + Object.setPrototypeOf(fakeProcess, Object.getPrototypeOf(process)); + for (const prop of Object.keys(process)) { + fakeProcess[prop] = process[prop]; + } + assert.notDeepEqual(fakeProcess, process); + // Message will be truncated anyway, don't validate + assert.throws(() => assert.deepStrictEqual(fakeProcess, process), + assert.AssertionError); + } +}); +/* eslint-enable */ From bcfe8ad4a9fb0b778d0143197d809ca7ef1e326e Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 30 Jan 2025 16:33:38 -0800 Subject: [PATCH 07/20] prepare for changes to non-strict equality --- src/bun.js/bindings/bindings.cpp | 74 +++++++------------------------- src/bun.js/bindings/bindings.zig | 11 +++++ test/js/bun/test/expect.test.js | 35 +++++++++++++++ 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 15a524b8dd9bdc..4936d08cb09f57 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2429,35 +2429,6 @@ size_t JSC__VM__heapSize(JSC__VM* arg0) return arg0->heap.size(); } -// This is very naive! -JSC__JSInternalPromise* JSC__VM__reloadModule(JSC__VM* vm, JSC__JSGlobalObject* arg1, - ZigString arg2) -{ - return nullptr; - // JSC::JSMap *map = JSC::jsDynamicCast( - // arg1->vm(), arg1->moduleLoader()->getDirect( - // arg1->vm(), JSC::Identifier::fromString(arg1->vm(), "registry"_s))); - - // const JSC::Identifier identifier = Zig::toIdentifier(arg2, arg1); - // JSC::JSValue val = JSC::identifierToJSValue(arg1->vm(), identifier); - - // if (!map->has(arg1, val)) return nullptr; - - // if (JSC::JSObject *registryEntry = - // JSC::jsDynamicCast(arg1-> map->get(arg1, val))) { - // auto moduleIdent = JSC::Identifier::fromString(arg1->vm(), "module"); - // if (JSC::JSModuleRecord *record = JSC::jsDynamicCast( - // arg1->vm(), registryEntry->getDirect(arg1->vm(), moduleIdent))) { - // registryEntry->putDirect(arg1->vm(), moduleIdent, JSC::jsUndefined()); - // JSC::JSModuleRecord::destroy(static_cast(record)); - // } - // map->remove(arg1, val); - // return JSC__JSModuleLoader__loadAndEvaluateModule(arg1, arg2); - // } - - // return nullptr; -} - bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { @@ -2466,52 +2437,37 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, return JSC::sameValue(globalObject, left, right); } +#define IMPL_DEEP_EQUALS_WRAPPER(strict, enableAsymmetricMatchers, globalObject, a, b) \ + ASSERT_NO_PENDING_EXCEPTION(globalObject); \ + JSValue v1 = JSValue::decode(a); \ + JSValue v2 = JSValue::decode(b); \ + ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); \ + Vector, 16> stack; \ + MarkedArgumentBuffer args; \ + return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true) + bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - ASSERT_NO_PENDING_EXCEPTION(globalObject); - JSValue v1 = JSValue::decode(JSValue0); - JSValue v2 = JSValue::decode(JSValue1); - - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); - Vector, 16> stack; - MarkedArgumentBuffer args; - return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true); + IMPL_DEEP_EQUALS_WRAPPER(false, false, globalObject, JSValue0, JSValue1); } bool JSC__JSValue__jestDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - JSValue v1 = JSValue::decode(JSValue0); - JSValue v2 = JSValue::decode(JSValue1); - - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); - Vector, 16> stack; - MarkedArgumentBuffer args; - return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true); + IMPL_DEEP_EQUALS_WRAPPER(false, true, globalObject, JSValue0, JSValue1); } bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - JSValue v1 = JSValue::decode(JSValue0); - JSValue v2 = JSValue::decode(JSValue1); - - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); - Vector, 16> stack; - MarkedArgumentBuffer args; - return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true); + IMPL_DEEP_EQUALS_WRAPPER(true, false, globalObject, JSValue0, JSValue1); } bool JSC__JSValue__jestStrictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - JSValue v1 = JSValue::decode(JSValue0); - JSValue v2 = JSValue::decode(JSValue1); - - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); - Vector, 16> stack; - MarkedArgumentBuffer args; - - return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true); + IMPL_DEEP_EQUALS_WRAPPER(true, true, globalObject, JSValue0, JSValue1); } +#undef IMPL_DEEP_EQUALS_WRAPPER + bool JSC__JSValue__deepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject, bool replacePropsWithAsymmetricMatchers) { JSValue obj = JSValue::decode(JSValue0); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index a055bb4b6bfb6b..bc653d35b630a8 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -5630,38 +5630,49 @@ pub const JSValue = enum(i64) { } /// Object.is() + /// /// This algorithm differs from the IsStrictlyEqual Algorithm by treating all NaN values as equivalent and by differentiating +0𝔽 from -0𝔽. /// https://tc39.es/ecma262/#sec-samevalue pub fn isSameValue(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { return @intFromEnum(this) == @intFromEnum(other) or cppFn("isSameValue", .{ this, other, global }); } + /// NOTE: can throw. Check for exceptions. pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + // JSC__JSValue__deepEquals return cppFn("deepEquals", .{ this, other, global }); } /// same as `JSValue.deepEquals`, but with jest asymmetric matchers enabled + /// NOTE: can throw. Check for exceptions. pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bun.JSError!bool { const result = cppFn("jestDeepEquals", .{ this, other, global }); if (global.hasException()) return error.JSError; return result; } + /// NOTE: can throw. Check for exceptions. pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + // JSC__JSValue__strictDeepEquals return cppFn("strictDeepEquals", .{ this, other, global }); } /// same as `JSValue.strictDeepEquals`, but with jest asymmetric matchers enabled + /// NOTE: can throw. Check for exceptions. pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + // JSC__JSValue__jestStrictDeepEquals return cppFn("jestStrictDeepEquals", .{ this, other, global }); } + /// NOTE: can throw. Check for exceptions. pub fn deepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool { + // JSC__JSValue__deepMatch return cppFn("deepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers }); } /// same as `JSValue.deepMatch`, but with jest asymmetric matchers enabled pub fn jestDeepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool { + // JSC__JSValue__jestDeepMatch return cppFn("jestDeepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers }); } diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index cc3ff40dce80a1..5fecb29f96aff5 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -43,6 +43,41 @@ describe("expect()", () => { } }); }; + describe("toBe()", () => { + let obj = {}; + it.each([ + [1, 1], + [1, 1.0], + [NaN, NaN], + [Infinity, Infinity], + [obj, obj], + [Symbol.for("a"), Symbol.for("a")], + ])("expect(%p).toBe(%p) == true", (a, b) => { + expect(a).toBe(b); + expect(b).toBe(a); + }); + it.each([ + [1, 2], + [1, true], + [1, "1"], + [0, false], + [0, ""], + [Infinity, -Infinity], + ["foo", "Foo"], + ["foo", "bar"], + ["", " "], + ["", " "], + ["", true], + [{}, {}], // + [new Set(), new Set()], // + [function a() {}, function a() {}], // + [Symbol.for("a"), Symbol.for("b")], + [Symbol("a"), Symbol("a")], + ])("expect(%p).toBe(%p) == false", (a, b) => { + expect(a).not.toBe(b); + expect(b).not.toBe(a); + }); + }); test("rejects", async () => { await expect(Promise.reject(4)).rejects.toBe(4); From 0738b7949e166532e250259b9cd9dfb3f1509c24 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 30 Jan 2025 16:51:48 -0800 Subject: [PATCH 08/20] cleanup --- src/bun.js/bindings/headers-handwritten.h | 1 + src/js/internal/assert/assertion_error.ts | 2 ++ test/js/bun/test/expect.test.js | 10 ++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 9a81eda9783d81..e6bb4e2de7bfc0 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -385,6 +385,7 @@ extern "C" size_t Bun__encoding__byteLengthUTF16(const UChar* ptr, size_t len, E extern "C" int64_t Bun__encoding__constructFromLatin1(void*, const unsigned char* ptr, size_t len, Encoding encoding); extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, size_t len, Encoding encoding); +/// @note throws a JS exception and returns false if a stack overflow occurs template bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSC::JSValue v1, JSC::JSValue v2, JSC::MarkedArgumentBuffer&, Vector, 16>& stack, JSC::ThrowScope* scope, bool addToStack); diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts index 67a4ad03073730..b7722209613554 100644 --- a/src/js/internal/assert/assertion_error.ts +++ b/src/js/internal/assert/assertion_error.ts @@ -377,6 +377,8 @@ class AssertionError extends Error { this.operator = operator; } ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction); + // JSC::Interpreter::getStackTrace() sometimes short-circuits without creating a .stack property. + // e.g.: https://github.com/oven-sh/WebKit/blob/e32c6356625cfacebff0c61d182f759abf6f508a/Source/JavaScriptCore/interpreter/Interpreter.cpp#L501 if ($isUndefinedOrNull(this.stack)) { ErrorCaptureStackTrace(this, AssertionError); } diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index 5fecb29f96aff5..d9e272937c3228 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -46,6 +46,10 @@ describe("expect()", () => { describe("toBe()", () => { let obj = {}; it.each([ + [0, 0.0], + [+0, +0], + [0, +0], + [-0, -0], [1, 1], [1, 1.0], [NaN, NaN], @@ -57,11 +61,13 @@ describe("expect()", () => { expect(b).toBe(a); }); it.each([ + [0, false], + [0, ""], + [0, -0], + [+0, -0], [1, 2], [1, true], [1, "1"], - [0, false], - [0, ""], [Infinity, -Infinity], ["foo", "Foo"], ["foo", "bar"], From ee4a7ef3303a81b205e8fe472089f9805404f6c1 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Fri, 31 Jan 2025 00:53:42 +0000 Subject: [PATCH 09/20] `bun run clang-format` --- src/bun.js/bindings/bindings.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 4936d08cb09f57..d09a683a1909b9 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2438,12 +2438,12 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, } #define IMPL_DEEP_EQUALS_WRAPPER(strict, enableAsymmetricMatchers, globalObject, a, b) \ - ASSERT_NO_PENDING_EXCEPTION(globalObject); \ - JSValue v1 = JSValue::decode(a); \ - JSValue v2 = JSValue::decode(b); \ - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); \ - Vector, 16> stack; \ - MarkedArgumentBuffer args; \ + ASSERT_NO_PENDING_EXCEPTION(globalObject); \ + JSValue v1 = JSValue::decode(a); \ + JSValue v2 = JSValue::decode(b); \ + ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); \ + Vector, 16> stack; \ + MarkedArgumentBuffer args; \ return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true) bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) From 3115f1445b30356051ee815371d4a557b0af8b30 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 30 Jan 2025 18:49:14 -0800 Subject: [PATCH 10/20] fix stackoverflow check --- src/bun.js/bindings/bindings.cpp | 4 ++++ src/bun.js/bindings/bindings.zig | 24 +++++++++++++----------- src/bun.js/test/expect.zig | 6 +++--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index d09a683a1909b9..74fcbb1d0adae7 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -670,6 +670,10 @@ template bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, MarkedArgumentBuffer& gcBuffer, Vector, 16>& stack, ThrowScope* scope, bool addToStack) { VM& vm = globalObject->vm(); + if (UNLIKELY(!vm.isSafeToRecurse())) { + throwStackOverflowError(globalObject, *scope); + return false; + } // need to check this before primitives, asymmetric matchers // can match against any type of value. diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index bc653d35b630a8..691e703a0cc40d 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -5637,31 +5637,33 @@ pub const JSValue = enum(i64) { return @intFromEnum(this) == @intFromEnum(other) or cppFn("isSameValue", .{ this, other, global }); } - /// NOTE: can throw. Check for exceptions. - pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool { // JSC__JSValue__deepEquals - return cppFn("deepEquals", .{ this, other, global }); + const result = cppFn("deepEquals", .{ this, other, global }); + if (global.hasException()) return error.JSError; + return result; } /// same as `JSValue.deepEquals`, but with jest asymmetric matchers enabled - /// NOTE: can throw. Check for exceptions. - pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bun.JSError!bool { + pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool { const result = cppFn("jestDeepEquals", .{ this, other, global }); if (global.hasException()) return error.JSError; return result; } - /// NOTE: can throw. Check for exceptions. - pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool { // JSC__JSValue__strictDeepEquals - return cppFn("strictDeepEquals", .{ this, other, global }); + const result = cppFn("strictDeepEquals", .{ this, other, global }); + if (global.hasException()) return error.JSError; + return result; } /// same as `JSValue.strictDeepEquals`, but with jest asymmetric matchers enabled - /// NOTE: can throw. Check for exceptions. - pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool { // JSC__JSValue__jestStrictDeepEquals - return cppFn("jestStrictDeepEquals", .{ this, other, global }); + const result = cppFn("jestStrictDeepEquals", .{ this, other, global }); + if (global.hasException()) return error.JSError; + return result; } /// NOTE: can throw. Check for exceptions. diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 9315790ff73fa1..edf2b3f2272f65 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -528,7 +528,7 @@ pub const Expect = struct { } const signature = comptime getSignature("toBe", "expected", false); - if (left.deepEquals(right, globalThis) or left.strictDeepEquals(right, globalThis)) { + if (try left.deepEquals(right, globalThis) or try left.strictDeepEquals(right, globalThis)) { const fmt = (if (!has_custom_label) "\n\nIf this test should pass, replace \"toBe\" with \"toEqual\" or \"toStrictEqual\"" else "") ++ "\n\nExpected: {any}\n" ++ @@ -1629,7 +1629,7 @@ pub const Expect = struct { const value: JSValue = try this.getValue(globalThis, thisValue, "toStrictEqual", "expected"); const not = this.flags.not; - var pass = value.jestStrictDeepEquals(expected, globalThis); + var pass = try value.jestStrictDeepEquals(expected, globalThis); if (not) pass = !pass; if (pass) return .undefined; @@ -2351,7 +2351,7 @@ pub const Expect = struct { if (Expect.isAsymmetricMatcher(expected_value)) { const signature = comptime getSignature("toThrow", "expected", false); - const is_equal = result.jestStrictDeepEquals(expected_value, globalThis); + const is_equal = try result.jestStrictDeepEquals(expected_value, globalThis); if (globalThis.hasException()) { return .zero; From d4bc26db9b5e6b2bfd79428502cb607b44e62656 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Fri, 31 Jan 2025 02:50:42 +0000 Subject: [PATCH 11/20] `bun run zig-format` --- src/bun.js/bindings/bindings.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 691e703a0cc40d..047e94a72e1fa2 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -5661,7 +5661,7 @@ pub const JSValue = enum(i64) { /// same as `JSValue.strictDeepEquals`, but with jest asymmetric matchers enabled pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool { // JSC__JSValue__jestStrictDeepEquals - const result = cppFn("jestStrictDeepEquals", .{ this, other, global }); + const result = cppFn("jestStrictDeepEquals", .{ this, other, global }); if (global.hasException()) return error.JSError; return result; } From 13b276d8c8c40e1e447734ce9e1bb851b7858eac Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 12:50:16 -0500 Subject: [PATCH 12/20] check for JSGlobalObject created in new vm context --- src/bun.js/bindings/bindings.cpp | 17 ++++++++++++++--- test/js/bun/bun-object/deep-equals.spec.ts | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 74fcbb1d0adae7..14967128df7fd8 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1072,6 +1072,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark foundMatchingKey = true; break; } + RETURN_IF_EXCEPTION(*scope, false); } if (!foundMatchingKey) { @@ -1110,6 +1111,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark foundMatchingKey = true; break; } + RETURN_IF_EXCEPTION(*scope, false); } if (!foundMatchingKey) { @@ -1377,9 +1379,18 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark } // globalThis is only equal to globalThis // NOTE: Zig::GlobalObject is tagged as GlobalProxyType - case GlobalObjectType: - case GlobalProxyType: - return c1Type == c2Type; + case GlobalObjectType: { + if (c1Type != c2Type) return false; + auto* g1 = jsDynamicCast(c1); + auto* g2 = jsDynamicCast(c2); + return g1->m_globalThis == g2->m_globalThis; + } + case GlobalProxyType: { + if (c1Type != c2Type) return false; + auto* gp1 = jsDynamicCast(c1); + auto* gp2 = jsDynamicCast(c2); + return gp1->target()->m_globalThis == gp2->target()->m_globalThis; + } default: { break; } diff --git a/test/js/bun/bun-object/deep-equals.spec.ts b/test/js/bun/bun-object/deep-equals.spec.ts index 323280114b151f..6d69941761922d 100644 --- a/test/js/bun/bun-object/deep-equals.spec.ts +++ b/test/js/bun/bun-object/deep-equals.spec.ts @@ -1,3 +1,5 @@ +import vm from "node:vm"; + describe.each([true, false])("Bun.deepEquals(a, b, strict: %p)", strict => { const deepEquals = (a: unknown, b: unknown) => Bun.deepEquals(a, b, strict); it.each([ @@ -55,4 +57,23 @@ describe.each([true, false])("Bun.deepEquals(a, b, strict: %p)", strict => { expect(deepEquals(foo, bar)).toBe(false); expect(deepEquals(foo, baz)).toBe(false); }); + + describe("global object", () => { + let contexts: [vm.Context, vm.Context]; + + beforeEach(() => { + contexts = [vm.createContext(), vm.createContext()]; + }); + afterEach(() => {}); + + // TODO: re-enable when https://github.com/oven-sh/bun/issues/17080 is resolved + it.skip("main global object is not equal to vm global objects", () => { + const [ctx] = contexts; + expect(deepEquals(global, ctx)).toBe(false); + + ctx.mainGlobal = global; + const areEqual = vm.runInContext("Bun.deepEquals(globalThis, mainGlobal)", ctx); + expect(areEqual).toBe(false); + }); + }); }); From 228ae3a06fd9221d3044ce690d562a29aa81ace5 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 13:32:47 -0500 Subject: [PATCH 13/20] fix bug in assert.equals --- src/bun.js/bindings/bindings.cpp | 30 +++++++++++++----------- src/js/node/assert.ts | 5 ++-- test/js/bun/test/jest.d.ts | 4 ++++ test/js/node/assert/assert.spec.ts | 37 ++++++++++++++++++++++++++++++ test/js/node/assert/assert.test.ts | 11 --------- 5 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 test/js/node/assert/assert.spec.ts delete mode 100644 test/js/node/assert/assert.test.ts diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 14967128df7fd8..2ce7068dce9988 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1545,6 +1545,19 @@ bool Bun__deepMatch( return true; } +// anonymous namespace to avoid name collision +namespace { + template + inline bool deepEqualsWrapperImpl(JSC__JSValue a, JSC__JSValue b, JSC__JSGlobalObject* global) + { + auto& vm = global->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + Vector, 16> stack; + MarkedArgumentBuffer args; + return Bun__deepEquals(global, JSC::JSValue::decode(a), JSC::JSValue::decode(b), args, stack, &scope, true); + } +} + extern "C" { bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0) @@ -2452,33 +2465,24 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, return JSC::sameValue(globalObject, left, right); } -#define IMPL_DEEP_EQUALS_WRAPPER(strict, enableAsymmetricMatchers, globalObject, a, b) \ - ASSERT_NO_PENDING_EXCEPTION(globalObject); \ - JSValue v1 = JSValue::decode(a); \ - JSValue v2 = JSValue::decode(b); \ - ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); \ - Vector, 16> stack; \ - MarkedArgumentBuffer args; \ - return Bun__deepEquals(globalObject, v1, v2, args, stack, &scope, true) - bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - IMPL_DEEP_EQUALS_WRAPPER(false, false, globalObject, JSValue0, JSValue1); + return deepEqualsWrapperImpl(JSValue0, JSValue1, globalObject); } bool JSC__JSValue__jestDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - IMPL_DEEP_EQUALS_WRAPPER(false, true, globalObject, JSValue0, JSValue1); + return deepEqualsWrapperImpl(JSValue0, JSValue1, globalObject); } bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - IMPL_DEEP_EQUALS_WRAPPER(true, false, globalObject, JSValue0, JSValue1); + return deepEqualsWrapperImpl(JSValue0, JSValue1, globalObject); } bool JSC__JSValue__jestStrictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { - IMPL_DEEP_EQUALS_WRAPPER(true, true, globalObject, JSValue0, JSValue1); + return deepEqualsWrapperImpl(JSValue0, JSValue1, globalObject); } #undef IMPL_DEEP_EQUALS_WRAPPER diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index b95c454b32cc28..b32ee2ba246a7a 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -189,9 +189,8 @@ assert.equal = function equal(actual: unknown, expected: unknown, message?: stri if (arguments.length < 2) { throw $ERR_MISSING_ARGS("actual", "expected"); } - // eslint-disable-next-line eqeqeq - // if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) { - if (actual != expected && !(isNaN(actual) && isNaN(expected))) { + + if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) { innerFail({ actual, expected, diff --git a/test/js/bun/test/jest.d.ts b/test/js/bun/test/jest.d.ts index fccb87b409e6ea..2d21e87e051282 100644 --- a/test/js/bun/test/jest.d.ts +++ b/test/js/bun/test/jest.d.ts @@ -3,3 +3,7 @@ declare var describe: typeof import("bun:test").describe; declare var test: typeof import("bun:test").test; declare var expect: typeof import("bun:test").expect; declare var it: typeof import("bun:test").it; +declare var beforeEach: typeof import("bun:test").beforeEach; +declare var afterEach: typeof import("bun:test").afterEach; +declare var beforeAll: typeof import("bun:test").beforeAll; +declare var afterAll: typeof import("bun:test").afterAll; diff --git a/test/js/node/assert/assert.spec.ts b/test/js/node/assert/assert.spec.ts new file mode 100644 index 00000000000000..d4dc871b590098 --- /dev/null +++ b/test/js/node/assert/assert.spec.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from "bun:test"; +import assert from "assert"; + +describe("assert(expr)", () => { + // https://github.com/oven-sh/bun/issues/941 + it.each([true, 1, "foo"])(`assert(%p) does not throw`, expr => { + expect(() => assert(expr)).not.toThrow(); + }); + + it.each([false, 0, "", null, undefined])(`assert(%p) throws`, expr => { + expect(() => assert(expr)).toThrow(assert.AssertionError); + }); + + it("is an alias for assert.ok", () => { + expect(assert).toBe(assert.ok); + }); +}); + +describe("assert.equal(actual, expected)", () => { + it.each([ + ["foo", "foo"], + [1, 1], + [1, true], + [0, ""], + [0, false], + ])(`%p == %p`, (actual, expected) => { + expect(() => assert.equal(actual, expected)).not.toThrow(); + }); + it.each([ + // + ["foo", "bar"], + [1, 0], + [true, false], + ])("%p != %p", (actual, expected) => { + expect(() => assert.equal(actual, expected)).toThrow(assert.AssertionError); + }); +}); diff --git a/test/js/node/assert/assert.test.ts b/test/js/node/assert/assert.test.ts deleted file mode 100644 index 2ba97531ab0c8e..00000000000000 --- a/test/js/node/assert/assert.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import assert from "assert"; -import { expect, test } from "bun:test"; - -// https://github.com/oven-sh/bun/issues/941 -test("assert as a function does not throw", () => assert(true)); -test("assert as a function does throw", () => { - try { - assert(false); - expect.unreachable(); - } catch (e) {} -}); From 8d3ddc1c1a479b3f2c67df7deb63c87a23358640 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:34:44 +0000 Subject: [PATCH 14/20] `bun run clang-format` --- src/bun.js/bindings/bindings.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 2ce7068dce9988..06c8383dd9f29a 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1547,15 +1547,15 @@ bool Bun__deepMatch( // anonymous namespace to avoid name collision namespace { - template - inline bool deepEqualsWrapperImpl(JSC__JSValue a, JSC__JSValue b, JSC__JSGlobalObject* global) - { - auto& vm = global->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - Vector, 16> stack; - MarkedArgumentBuffer args; - return Bun__deepEquals(global, JSC::JSValue::decode(a), JSC::JSValue::decode(b), args, stack, &scope, true); - } +template +inline bool deepEqualsWrapperImpl(JSC__JSValue a, JSC__JSValue b, JSC__JSGlobalObject* global) +{ + auto& vm = global->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + Vector, 16> stack; + MarkedArgumentBuffer args; + return Bun__deepEquals(global, JSC::JSValue::decode(a), JSC::JSValue::decode(b), args, stack, &scope, true); +} } extern "C" { From 8a23324a52f9300b630af08e86e715350b41f0a5 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 15:00:08 -0500 Subject: [PATCH 15/20] `test-assert-deep-with-error` --- src/bun.js/bindings/bindings.cpp | 105 +++++++++++++++++- test/js/node/assert/assert.spec.ts | 67 ++++++++++- .../needs-test/test-assert-deep-with-error.js | 82 ++++++++++++++ 3 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 test/js/node/test/parallel/needs-test/test-assert-deep-with-error.js diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 06c8383dd9f29a..41214d2a9bc05f 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1191,6 +1191,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark return false; } + // NOTE(@DonIsaac): could `left` ever _not_ be a JSC::ErrorInstance? if (JSC::ErrorInstance* left = jsDynamicCast(c1)) { JSC::ErrorInstance* right = jsDynamicCast(c2); @@ -1198,9 +1199,109 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark return false; } - return ( - left->sanitizedNameString(globalObject) == right->sanitizedNameString(globalObject) && left->sanitizedMessageString(globalObject) == right->sanitizedMessageString(globalObject)); + if ( + left->errorType() != right->errorType() || // quick check on ctors (does not handle subclasses) + left->sanitizedNameString(globalObject) != right->sanitizedNameString(globalObject) || // manual `.name` changes (usually in subclasses) + left->sanitizedMessageString(globalObject) != right->sanitizedMessageString(globalObject) // `.message` + ) { + return false; + } + + if constexpr (isStrict) { + if (left->runtimeTypeForCause() != right->runtimeTypeForCause()) { + return false; + } + } + + VM& vm = globalObject->vm(); + + // `.cause` is non-enumerable, so it must be checked explicitly. + // note that an undefined cause is different than a missing cause in + // strict mode. + const Identifier causeIdent = Identifier::fromLatin1(vm, "cause"); + const PropertyName cause(causeIdent); + if constexpr (isStrict) { + if (left->hasProperty(globalObject, cause) != right->hasProperty(globalObject, cause)) { + return false; + } + } + auto leftCause = left->get(globalObject, cause); + auto rightCause = right->get(globalObject, cause); + if (!Bun__deepEquals(globalObject, leftCause, rightCause, gcBuffer, stack, scope, true)) { + return false; + } + RETURN_IF_EXCEPTION(*scope, false); + + // check arbitrary enumerable properties. `.stack` is not checked. + left->materializeErrorInfoIfNeeded(vm); + right->materializeErrorInfoIfNeeded(vm); + // JSObject* o1 = c1->getObject(); + // JSObject* o2 = c2->getObject(); + JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + left->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude); + RETURN_IF_EXCEPTION(*scope, false); + right->getPropertyNames(globalObject, a2, DontEnumPropertiesMode::Exclude); + RETURN_IF_EXCEPTION(*scope, false); + + const size_t propertyArrayLength1 = a1.size(); + const size_t propertyArrayLength2 = a2.size(); + if constexpr (isStrict) { + if (propertyArrayLength1 != propertyArrayLength2) { + return false; + } + } + + // take a property name from one, try to get it from both + size_t i; + for (i = 0; i < propertyArrayLength1; i++) { + Identifier i1 = a1[i]; + if (!i1.isEmpty() && !i1.isSymbol() && i1.string() == "stack"_s) continue; + PropertyName propertyName1 = PropertyName(i1); + + JSValue prop1 = left->get(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if (UNLIKELY(!prop1)) { + return false; + } + + JSValue prop2 = right->getIfPropertyExists(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if constexpr (!isStrict) { + if (prop1.isUndefined() && prop2.isEmpty()) { + continue; + } + } + + if (!prop2) { + return false; + } + + if (!Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true)) { + return false; + } + + RETURN_IF_EXCEPTION(*scope, false); + } + + // for the remaining properties in the other object, make sure they are undefined + for (; i < propertyArrayLength2; i++) { + Identifier i2 = a2[i]; + PropertyName propertyName2 = PropertyName(i2); + + JSValue prop2 = right->getIfPropertyExists(globalObject, propertyName2); + RETURN_IF_EXCEPTION(*scope, false); + + if (!prop2.isUndefined()) { + return false; + } + } + + return true; } + break; } case Int8ArrayType: case Uint8ArrayType: diff --git a/test/js/node/assert/assert.spec.ts b/test/js/node/assert/assert.spec.ts index d4dc871b590098..90c82f159cd38e 100644 --- a/test/js/node/assert/assert.spec.ts +++ b/test/js/node/assert/assert.spec.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from "bun:test"; -import assert from "assert"; +import { describe, beforeEach, it, expect } from "bun:test"; +import assert, { AssertionError } from "assert"; describe("assert(expr)", () => { // https://github.com/oven-sh/bun/issues/941 @@ -8,7 +8,7 @@ describe("assert(expr)", () => { }); it.each([false, 0, "", null, undefined])(`assert(%p) throws`, expr => { - expect(() => assert(expr)).toThrow(assert.AssertionError); + expect(() => assert(expr)).toThrow(AssertionError); }); it("is an alias for assert.ok", () => { @@ -23,6 +23,7 @@ describe("assert.equal(actual, expected)", () => { [1, true], [0, ""], [0, false], + [Symbol.for("foo"), Symbol.for("foo")], ])(`%p == %p`, (actual, expected) => { expect(() => assert.equal(actual, expected)).not.toThrow(); }); @@ -31,7 +32,65 @@ describe("assert.equal(actual, expected)", () => { ["foo", "bar"], [1, 0], [true, false], + [{}, {}], + [Symbol("foo"), Symbol("foo")], + [new Error("oops"), new Error("oops")], ])("%p != %p", (actual, expected) => { - expect(() => assert.equal(actual, expected)).toThrow(assert.AssertionError); + expect(() => assert.equal(actual, expected)).toThrow(AssertionError); + }); +}); + +describe("assert.deepEqual(actual, expected)", () => { + describe("error instances", () => { + let e1: Error, e2: Error; + + beforeEach(() => { + e1 = new Error("oops"); + e2 = new Error("oops"); + }); + + it("errors with the same message and constructor are equal", () => { + expect(() => assert.deepEqual(e1, e2)).not.toThrow(); + }); + + it("errors with different messages are not equal", () => { + e2.message = "nope"; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + }); + + it("errors with different constructors are not equal", () => { + e2 = new TypeError("oops"); + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + }); + + it("errors with different names are not equal", () => { + e2.name = "SpecialError"; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + }); + + it("errors with different causes are not equal", () => { + e1.cause = { property: "value" }; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + e2.cause = { property: "another value" }; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + }); + + it("errors with the same cause are equal", () => { + e1.cause = { property: "value" }; + e2.cause = { property: "value" }; + expect(() => assert.deepEqual(e1, e2)).not.toThrow(); + }); + + it("adding different arbitrary properties makes errors unequal", () => { + expect(() => assert.deepEqual(e1, e2)).not.toThrow(); + e1.a = 1; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + e2.a = 1; + expect(() => assert.deepEqual(e1, e2)).not.toThrow(); + e2.a = { foo: "bar" }; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + e1.a = { foo: "baz" }; + expect(() => assert.deepEqual(e1, e2)).toThrow(AssertionError); + }); }); }); diff --git a/test/js/node/test/parallel/needs-test/test-assert-deep-with-error.js b/test/js/node/test/parallel/needs-test/test-assert-deep-with-error.js new file mode 100644 index 00000000000000..125d16cc93302d --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert-deep-with-error.js @@ -0,0 +1,82 @@ +'use strict'; +require('../../common'); +const assert = require('assert'); +const { test } = require('node:test'); + +const defaultStartMessage = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n'; + +test('Handle error causes', () => { + assert.deepStrictEqual(new Error('a', { cause: new Error('x') }), new Error('a', { cause: new Error('x') })); + assert.deepStrictEqual( + new Error('a', { cause: new RangeError('x') }), + new Error('a', { cause: new RangeError('x') }), + ); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a', { cause: new Error('x') }), new Error('a', { cause: new Error('y') })); + }, { message: defaultStartMessage + ' [Error: a] {\n' + + '+ [cause]: [Error: x]\n' + + '- [cause]: [Error: y]\n' + + ' }\n' }); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a', { cause: new Error('x') }), new Error('a', { cause: new TypeError('x') })); + }, { message: defaultStartMessage + ' [Error: a] {\n' + + '+ [cause]: [Error: x]\n' + + '- [cause]: [TypeError: x]\n' + + ' }\n' }); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a'), new Error('a', { cause: new Error('y') })); + }, { message: defaultStartMessage + '+ [Error: a]\n' + + '- [Error: a] {\n' + + '- [cause]: [Error: y]\n' + + '- }\n' }); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a'), new Error('a', { cause: { prop: 'value' } })); + }, { message: defaultStartMessage + '+ [Error: a]\n' + + '- [Error: a] {\n' + + '- [cause]: {\n' + + '- prop: \'value\'\n' + + '- }\n' + + '- }\n' }); + + assert.notDeepStrictEqual(new Error('a', { cause: new Error('x') }), new Error('a', { cause: new Error('y') })); + assert.notDeepStrictEqual( + new Error('a', { cause: { prop: 'value' } }), + new Error('a', { cause: { prop: 'a different value' } }) + ); +}); + +test('Handle undefined causes', () => { + assert.deepStrictEqual(new Error('a', { cause: undefined }), new Error('a', { cause: undefined })); + + assert.notDeepStrictEqual(new Error('a', { cause: 'undefined' }), new Error('a', { cause: undefined })); + assert.notDeepStrictEqual(new Error('a', { cause: undefined }), new Error('a')); + assert.notDeepStrictEqual(new Error('a'), new Error('a', { cause: undefined })); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a'), new Error('a', { cause: undefined })); + }, { message: defaultStartMessage + + '+ [Error: a]\n' + + '- [Error: a] {\n' + + '- [cause]: undefined\n' + + '- }\n' }); + + assert.throws(() => { + assert.deepStrictEqual(new Error('a', { cause: undefined }), new Error('a')); + }, { message: defaultStartMessage + + '+ [Error: a] {\n' + + '+ [cause]: undefined\n' + + '+ }\n' + + '- [Error: a]\n' }); + assert.throws(() => { + assert.deepStrictEqual(new Error('a', { cause: undefined }), new Error('a', { cause: 'undefined' })); + }, { message: defaultStartMessage + ' [Error: a] {\n' + + '+ [cause]: undefined\n' + + '- [cause]: \'undefined\'\n' + + ' }\n' }); +}); From bd686671756c8d05ff4cc823fa8dadcf3f0c6e68 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 16:02:27 -0500 Subject: [PATCH 16/20] fix: handle proxied arrays --- src/bun.js/bindings/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 41214d2a9bc05f..2c4a022e70b626 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -756,7 +756,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, if (v1Array != v2Array) return false; - if (v1Array && v2Array) { + if (v1Array && v2Array && !(o1->isProxy() || o2->isProxy())) { JSC::JSArray* array1 = JSC::jsCast(v1); JSC::JSArray* array2 = JSC::jsCast(v2); From 9291f0b7dba2cec78d3fbb8f5e1344a4b7f0314c Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 17:04:40 -0500 Subject: [PATCH 17/20] wip --- test/harness.ts | 2 +- test/js/node/assert/assert.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/harness.ts b/test/harness.ts index 5619b1095caa9e..98206adb79984e 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -1571,7 +1571,7 @@ export class VerdaccioRegistry { await rm(join(this.packagesPath, "private-pkg-dont-touch"), { force: true }); const packageDir = tmpdirSync(); const packageJson = join(packageDir, "package.json"); - this.writeBunfig(packageDir, bunfigOpts); + await this.writeBunfig(packageDir, bunfigOpts); this.users = {}; return { packageDir, packageJson }; } diff --git a/test/js/node/assert/assert.spec.ts b/test/js/node/assert/assert.spec.ts index 90c82f159cd38e..d38946f805fb6e 100644 --- a/test/js/node/assert/assert.spec.ts +++ b/test/js/node/assert/assert.spec.ts @@ -12,7 +12,7 @@ describe("assert(expr)", () => { }); it("is an alias for assert.ok", () => { - expect(assert).toBe(assert.ok); + expect(assert as Function).toBe(assert.ok); }); }); @@ -42,7 +42,7 @@ describe("assert.equal(actual, expected)", () => { describe("assert.deepEqual(actual, expected)", () => { describe("error instances", () => { - let e1: Error, e2: Error; + let e1: Error & Record, e2: Error & Record; beforeEach(() => { e1 = new Error("oops"); From becf63df87c9eaf4541c4f50f00d5c0721712cdf Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 15:49:38 -0800 Subject: [PATCH 18/20] Update src/bun.js/bindings/bindings.cpp Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> --- src/bun.js/bindings/bindings.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 2c4a022e70b626..c16baaf7fe9453 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1235,8 +1235,6 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark // check arbitrary enumerable properties. `.stack` is not checked. left->materializeErrorInfoIfNeeded(vm); right->materializeErrorInfoIfNeeded(vm); - // JSObject* o1 = c1->getObject(); - // JSObject* o2 = c2->getObject(); JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); left->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude); From 176552f1ae472280d25cb2babc650520e06418fd Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 15:49:55 -0800 Subject: [PATCH 19/20] Update src/bun.js/bindings/bindings.cpp Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> --- src/bun.js/bindings/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index c16baaf7fe9453..daa7dc0b5af7ff 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1218,7 +1218,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark // `.cause` is non-enumerable, so it must be checked explicitly. // note that an undefined cause is different than a missing cause in // strict mode. - const Identifier causeIdent = Identifier::fromLatin1(vm, "cause"); + const Identifier causeIdent = Identifier::fromString(vm, "cause"_s); const PropertyName cause(causeIdent); if constexpr (isStrict) { if (left->hasProperty(globalObject, cause) != right->hasProperty(globalObject, cause)) { From d8f51f6767611c249c7b456a7d2a2e4d3068215a Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Feb 2025 18:55:52 -0500 Subject: [PATCH 20/20] address pr feedback --- src/bun.js/bindings/bindings.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index daa7dc0b5af7ff..6108c6c8dbb42a 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1218,15 +1218,16 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark // `.cause` is non-enumerable, so it must be checked explicitly. // note that an undefined cause is different than a missing cause in // strict mode. - const Identifier causeIdent = Identifier::fromString(vm, "cause"_s); - const PropertyName cause(causeIdent); + const PropertyName cause(vm.propertyNames->cause); if constexpr (isStrict) { if (left->hasProperty(globalObject, cause) != right->hasProperty(globalObject, cause)) { return false; } } auto leftCause = left->get(globalObject, cause); + RETURN_IF_EXCEPTION(*scope, false); auto rightCause = right->get(globalObject, cause); + RETURN_IF_EXCEPTION(*scope, false); if (!Bun__deepEquals(globalObject, leftCause, rightCause, gcBuffer, stack, scope, true)) { return false; } @@ -1254,7 +1255,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark size_t i; for (i = 0; i < propertyArrayLength1; i++) { Identifier i1 = a1[i]; - if (!i1.isEmpty() && !i1.isSymbol() && i1.string() == "stack"_s) continue; + if (i1 == vm.propertyNames->stack) continue; PropertyName propertyName1 = PropertyName(i1); JSValue prop1 = left->get(globalObject, propertyName1); @@ -1287,6 +1288,7 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark // for the remaining properties in the other object, make sure they are undefined for (; i < propertyArrayLength2; i++) { Identifier i2 = a2[i]; + if (i2 == vm.propertyNames->stack) continue; PropertyName propertyName2 = PropertyName(i2); JSValue prop2 = right->getIfPropertyExists(globalObject, propertyName2); @@ -1316,6 +1318,11 @@ std::optional specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark if (!isTypedArrayType(static_cast(c2Type)) || c1Type != c2Type) { return false; } + auto info = c1->classInfo(); + auto info2 = c2->classInfo(); + if (!info || !info2) { + return false; + } JSC::JSArrayBufferView* left = jsCast(c1); JSC::JSArrayBufferView* right = jsCast(c2);