test-assert-typedarray-deepequal (#19285)

This commit is contained in:
pfg
2025-04-25 23:36:07 -07:00
committed by GitHub
parent d531e86634
commit ea681fa9ec
8 changed files with 321 additions and 3 deletions

View File

@@ -3361,6 +3361,8 @@ pub const Formatter = struct {
if (arrayBuffer.typed_array_type == .Uint8Array and
arrayBuffer.value.isBuffer(this.globalThis))
"Buffer"
else if (arrayBuffer.typed_array_type == .ArrayBuffer and arrayBuffer.shared)
"SharedArrayBuffer"
else
bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)),
);

View File

@@ -1085,6 +1085,10 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
return false;
}
if (UNLIKELY(left->isShared() != right->isShared())) {
return false;
}
if (byteLength == 0)
return true;
@@ -1292,6 +1296,45 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
if (UNLIKELY(vector == rightVector))
return true;
// For Float32Array and Float64Array, when not in strict mode, we need to
// handle +0 and -0 as equal, and NaN as not equal to itself.
if (!isStrict && (c1Type == Float16ArrayType || c1Type == Float32ArrayType || c1Type == Float64ArrayType)) {
if (c1Type == Float16ArrayType) {
auto* leftFloat = static_cast<const WTF::Float16*>(vector);
auto* rightFloat = static_cast<const WTF::Float16*>(rightVector);
size_t numElements = byteLength / sizeof(WTF::Float16);
for (size_t i = 0; i < numElements; i++) {
if (leftFloat[i] != rightFloat[i]) {
return false;
}
}
return true;
} else if (c1Type == Float32ArrayType) {
auto* leftFloat = static_cast<const float*>(vector);
auto* rightFloat = static_cast<const float*>(rightVector);
size_t numElements = byteLength / sizeof(float);
for (size_t i = 0; i < numElements; i++) {
if (leftFloat[i] != rightFloat[i]) {
return false;
}
}
return true;
} else { // Float64Array
auto* leftDouble = static_cast<const double*>(vector);
auto* rightDouble = static_cast<const double*>(rightVector);
size_t numElements = byteLength / sizeof(double);
for (size_t i = 0; i < numElements; i++) {
if (leftDouble[i] != rightDouble[i]) {
return false;
}
}
return true;
}
}
return (memcmp(vector, rightVector, byteLength) == 0);
}
case StringObjectType: {

View File

@@ -23,7 +23,17 @@
const { SafeMap, SafeSet, SafeWeakSet } = require("internal/primordials");
const { Buffer } = require("node:buffer");
const { isKeyObject, isPromise, isRegExp, isMap, isSet, isDate, isWeakSet, isWeakMap } = require("node:util/types");
const {
isKeyObject,
isPromise,
isRegExp,
isMap,
isSet,
isDate,
isWeakSet,
isWeakMap,
isAnyArrayBuffer,
} = require("node:util/types");
const { innerOk } = require("internal/assert/utils");
const { validateFunction } = require("internal/validators");
@@ -32,6 +42,7 @@ const ArrayPrototypeIndexOf = Array.prototype.indexOf;
const ArrayPrototypeJoin = Array.prototype.join;
const ArrayPrototypePush = Array.prototype.push;
const ArrayPrototypeSlice = Array.prototype.slice;
const ArrayBufferIsView = ArrayBuffer.isView;
const NumberIsNaN = Number.isNaN;
const ObjectAssign = Object.assign;
const ObjectIs = Object.is;
@@ -383,6 +394,16 @@ function compareBranch(actual, expected, comparedObjects?) {
return Bun.deepEquals(actual, expected, true);
}
// Check for ArrayBuffer object equality
if (
ArrayBufferIsView(actual) ||
isAnyArrayBuffer(actual) ||
ArrayBufferIsView(expected) ||
isAnyArrayBuffer(expected)
) {
return Bun.deepEquals(actual, expected, true);
}
for (const type of typesToCallDeepStrictEqualWith) {
if (type(actual) || type(expected)) {
return isDeepStrictEqual(actual, expected);
@@ -693,7 +714,7 @@ async function waitForActual(promiseFn) {
} else if (checkIsPromise(promiseFn)) {
resultPromise = promiseFn;
} else {
throw $ERR_INVALID_ARG_TYPE("promiseFn", ["Function", "Promise"], promiseFn);
throw $ERR_INVALID_ARG_TYPE("promiseFn", ["function", "an instance of Promise"], promiseFn);
}
try {

View File

@@ -1706,6 +1706,12 @@ describe("expect()", () => {
expect(array2).not.toEqual(expect.arrayContaining([{ a: 2, b: 3 }]));
});
test("toEqual ArrayBuffer with SharedArrayBuffer", () => {
const ab1 = new SharedArrayBuffer(1);
expect(ab1).toEqual(new SharedArrayBuffer(1));
expect(ab1).not.toEqual(new ArrayBuffer(1));
});
test("symbol based keys in arrays are processed correctly", () => {
const mySymbol = Symbol("test");

View File

@@ -0,0 +1,107 @@
import assert from "assert";
import { test, describe, expect } from "bun:test";
function makeBlock(f: Function, ...args: any[]) {
return function () {
return f.apply(this, args);
};
}
describe("TypedArray deepEqual", () => {
describe("equalArrayPairs", () => {
const equalArrayPairs = [
[new Uint8Array(1e5), new Uint8Array(1e5)],
[new Uint16Array(1e5), new Uint16Array(1e5)],
[new Uint32Array(1e5), new Uint32Array(1e5)],
[new Uint8ClampedArray(1e5), new Uint8ClampedArray(1e5)],
[new Int8Array(1e5), new Int8Array(1e5)],
[new Int16Array(1e5), new Int16Array(1e5)],
[new Int32Array(1e5), new Int32Array(1e5)],
[new Float32Array(1e5), new Float32Array(1e5)],
[new Float64Array(1e5), new Float64Array(1e5)],
[new Float32Array([+0.0]), new Float32Array([+0.0])],
[new Uint8Array([1, 2, 3, 4]).subarray(1), new Uint8Array([2, 3, 4])],
[new Uint16Array([1, 2, 3, 4]).subarray(1), new Uint16Array([2, 3, 4])],
[new Uint32Array([1, 2, 3, 4]).subarray(1, 3), new Uint32Array([2, 3])],
[new ArrayBuffer(3), new ArrayBuffer(3)],
[new SharedArrayBuffer(3), new SharedArrayBuffer(3)],
];
for (const arrayPair of equalArrayPairs) {
test(`${arrayPair[0].constructor.name} should equal`, () => {
assert.deepEqual(arrayPair[0], arrayPair[1]);
assert.deepStrictEqual(arrayPair[0], arrayPair[1]);
});
}
});
describe("looseEqualArrayPairs", () => {
const looseEqualArrayPairs = [
// @ts-ignore
[new Float16Array([+0.0]), new Float16Array([-0.0])],
[new Float32Array([+0.0]), new Float32Array([-0.0])],
[new Float64Array([+0.0]), new Float64Array([-0.0])],
];
for (const arrayPair of looseEqualArrayPairs) {
test(`${arrayPair[0].constructor.name} should be loosely equal but not strictly equal`, () => {
assert.deepEqual(arrayPair[0], arrayPair[1]);
expect(() => assert.deepStrictEqual(arrayPair[0], arrayPair[1])).toThrow(assert.AssertionError);
});
}
});
describe("looseNotEqualArrayPairs", () => {
const looseNotEqualArrayPairs = [
// @ts-ignore
[new Float16Array([NaN]), new Float16Array([NaN])],
[new Float32Array([NaN]), new Float32Array([NaN])],
[new Float64Array([NaN]), new Float64Array([NaN])],
];
for (const arrayPair of looseNotEqualArrayPairs) {
test(`${arrayPair[0].constructor.name} should be strictly equal but not loosely equal`, () => {
expect(() => assert.deepEqual(arrayPair[0], arrayPair[1])).toThrow(assert.AssertionError);
assert.deepStrictEqual(arrayPair[0], arrayPair[1]);
});
}
});
describe("notEqualArrayPairs", () => {
const notEqualArrayPairs = [
[new ArrayBuffer(3), new SharedArrayBuffer(3)],
[new Int16Array(256), new Uint16Array(256)],
[new Int16Array([256]), new Uint16Array([256])],
[new Float64Array([+0.0]), new Float32Array([-0.0])],
[new Uint8Array(2), new Uint8Array(3)],
[new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
[new Uint8ClampedArray([300, 2, 3]), new Uint8Array([300, 2, 3])],
[new Uint16Array([2]), new Uint16Array([3])],
[new Uint16Array([0]), new Uint16Array([256])],
[new Int16Array([0]), new Uint16Array([256])],
[new Int16Array([-256]), new Uint16Array([0xff00])], // same bits
[new Int32Array([-256]), new Uint32Array([0xffffff00])], // ditto
[new Float32Array([0.1]), new Float32Array([0.0])],
[new Float32Array([0.1]), new Float32Array([0.1, 0.2])],
[new Float64Array([0.1]), new Float64Array([0.0])],
[new Uint8Array([1, 2, 3]).buffer, new Uint8Array([4, 5, 6]).buffer],
[
new Uint8Array(new SharedArrayBuffer(3)).fill(1).buffer,
new Uint8Array(new SharedArrayBuffer(3)).fill(2).buffer,
],
[new ArrayBuffer(2), new ArrayBuffer(3)],
[new SharedArrayBuffer(2), new SharedArrayBuffer(3)],
[new ArrayBuffer(2), new SharedArrayBuffer(3)],
[new Uint8Array(new ArrayBuffer(3)).fill(1).buffer, new Uint8Array(new SharedArrayBuffer(3)).fill(2).buffer],
[new ArrayBuffer(3), new SharedArrayBuffer(3)],
[new SharedArrayBuffer(2), new ArrayBuffer(2)],
];
for (const arrayPair of notEqualArrayPairs) {
test(`${arrayPair[0].constructor.name} should not equal ${arrayPair[1].constructor.name}`, () => {
expect(() => assert.deepEqual(arrayPair[0], arrayPair[1])).toThrow(assert.AssertionError);
expect(() => assert.deepStrictEqual(arrayPair[0], arrayPair[1])).toThrow(assert.AssertionError);
});
}
});
});

View File

@@ -0,0 +1,110 @@
'use strict';
require('../common');
const assert = require('assert');
const { test, suite } = require('node:test');
function makeBlock(f) {
const args = Array.prototype.slice.call(arguments, 1);
return function() {
return f.apply(this, args);
};
}
suite('equalArrayPairs', () => {
const equalArrayPairs = [
[new Uint8Array(1e5), new Uint8Array(1e5)],
[new Uint16Array(1e5), new Uint16Array(1e5)],
[new Uint32Array(1e5), new Uint32Array(1e5)],
[new Uint8ClampedArray(1e5), new Uint8ClampedArray(1e5)],
[new Int8Array(1e5), new Int8Array(1e5)],
[new Int16Array(1e5), new Int16Array(1e5)],
[new Int32Array(1e5), new Int32Array(1e5)],
[new Float32Array(1e5), new Float32Array(1e5)],
[new Float64Array(1e5), new Float64Array(1e5)],
[new Float32Array([+0.0]), new Float32Array([+0.0])],
[new Uint8Array([1, 2, 3, 4]).subarray(1), new Uint8Array([2, 3, 4])],
[new Uint16Array([1, 2, 3, 4]).subarray(1), new Uint16Array([2, 3, 4])],
[new Uint32Array([1, 2, 3, 4]).subarray(1, 3), new Uint32Array([2, 3])],
[new ArrayBuffer(3), new ArrayBuffer(3)],
[new SharedArrayBuffer(3), new SharedArrayBuffer(3)],
];
for (const arrayPair of equalArrayPairs) {
test('', () => {
// eslint-disable-next-line no-restricted-properties
assert.deepEqual(arrayPair[0], arrayPair[1]);
assert.deepStrictEqual(arrayPair[0], arrayPair[1]);
});
}
});
suite('looseEqualArrayPairs', () => {
const looseEqualArrayPairs = [
[new Float32Array([+0.0]), new Float32Array([-0.0])],
[new Float64Array([+0.0]), new Float64Array([-0.0])],
];
for (const arrayPair of looseEqualArrayPairs) {
test('', () => {
// eslint-disable-next-line no-restricted-properties
assert.deepEqual(arrayPair[0], arrayPair[1]);
assert.throws(
makeBlock(assert.deepStrictEqual, arrayPair[0], arrayPair[1]),
assert.AssertionError
);
});
}
});
suite('notEqualArrayPairs', () => {
const notEqualArrayPairs = [
[new ArrayBuffer(3), new SharedArrayBuffer(3)],
[new Int16Array(256), new Uint16Array(256)],
[new Int16Array([256]), new Uint16Array([256])],
[new Float64Array([+0.0]), new Float32Array([-0.0])],
[new Uint8Array(2), new Uint8Array(3)],
[new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
[new Uint8ClampedArray([300, 2, 3]), new Uint8Array([300, 2, 3])],
[new Uint16Array([2]), new Uint16Array([3])],
[new Uint16Array([0]), new Uint16Array([256])],
[new Int16Array([0]), new Uint16Array([256])],
[new Int16Array([-256]), new Uint16Array([0xff00])], // same bits
[new Int32Array([-256]), new Uint32Array([0xffffff00])], // ditto
[new Float32Array([0.1]), new Float32Array([0.0])],
[new Float32Array([0.1]), new Float32Array([0.1, 0.2])],
[new Float64Array([0.1]), new Float64Array([0.0])],
[new Uint8Array([1, 2, 3]).buffer, new Uint8Array([4, 5, 6]).buffer],
[
new Uint8Array(new SharedArrayBuffer(3)).fill(1).buffer,
new Uint8Array(new SharedArrayBuffer(3)).fill(2).buffer,
],
[new ArrayBuffer(2), new ArrayBuffer(3)],
[new SharedArrayBuffer(2), new SharedArrayBuffer(3)],
[new ArrayBuffer(2), new SharedArrayBuffer(3)],
[
new Uint8Array(new ArrayBuffer(3)).fill(1).buffer,
new Uint8Array(new SharedArrayBuffer(3)).fill(2).buffer,
],
[new ArrayBuffer(3), new SharedArrayBuffer(3)],
[new SharedArrayBuffer(2), new ArrayBuffer(2)],
];
for (const arrayPair of notEqualArrayPairs) {
test('', () => {
assert.throws(
// eslint-disable-next-line no-restricted-properties
makeBlock(assert.deepEqual, arrayPair[0], arrayPair[1]),
assert.AssertionError
);
assert.throws(
makeBlock(assert.deepStrictEqual, arrayPair[0], arrayPair[1]),
assert.AssertionError
);
assert.throws(
makeBlock(assert.partialDeepStrictEqual, arrayPair[0], arrayPair[1]),
assert.AssertionError
);
});
}
});

View File

@@ -152,3 +152,29 @@ NamedError: console.error a named error
Error log"
`);
});
it("console.log with SharedArrayBuffer", () => {
const proc = Bun.spawnSync({
cmd: [
bunExe(),
"-e",
`
console.log(new ArrayBuffer(0));
console.log(new SharedArrayBuffer(0));
console.log(new ArrayBuffer(3));
console.log(new SharedArrayBuffer(3));
`,
],
env: bunEnv,
stdio: ["inherit", "pipe", "pipe"],
});
expect(proc.stderr.toString("utf8")).toBeEmpty();
expect(proc.exitCode).toBe(0);
expect(proc.stdout.toString("utf8")).toMatchInlineSnapshot(`
"ArrayBuffer(0) []
SharedArrayBuffer(0) []
ArrayBuffer(3) [ 0, 0, 0 ]
SharedArrayBuffer(3) [ 0, 0, 0 ]
"
`);
});

View File

@@ -660,8 +660,11 @@ for (const { body, fn } of bodyTypes) {
}
function arrayBuffer(buffer: BufferSource) {
if (buffer instanceof ArrayBuffer || buffer instanceof SharedArrayBuffer) {
if (buffer instanceof ArrayBuffer) {
return buffer;
}
if (buffer instanceof SharedArrayBuffer) {
return new Uint8Array(new Uint8Array(buffer)).buffer;
}
return buffer.buffer;
}