diff --git a/benchmark/assert/deepequal-buffer.js b/benchmark/assert/deepequal-buffer.js index 1b6aa3bee1d6ac..50bcd3047b146a 100644 --- a/benchmark/assert/deepequal-buffer.js +++ b/benchmark/assert/deepequal-buffer.js @@ -7,7 +7,7 @@ const bench = common.createBenchmark(main, { len: [1e2, 1e3], strict: [0, 1], arrayBuffer: [0, 1], - method: ['deepEqual', 'notDeepEqual', 'unequal_length'], + method: ['deepEqual', 'notDeepEqual', 'unequal_length', 'partial'], }, { combinationFilter: (p) => { return p.strict === 1 || p.method === 'deepEqual'; @@ -18,11 +18,16 @@ function main({ len, n, method, strict, arrayBuffer }) { let actual = Buffer.alloc(len); let expected = Buffer.alloc(len + Number(method === 'unequal_length')); - if (method === 'unequal_length') { method = 'notDeepEqual'; } + if (method === 'partial') { + method = 'partialDeepStrictEqual'; + } else if (strict) { + method = method.replace('eep', 'eepStrict'); + } + for (let i = 0; i < len; i++) { actual.writeInt8(i % 128, i); expected.writeInt8(i % 128, i); @@ -33,10 +38,6 @@ function main({ len, n, method, strict, arrayBuffer }) { expected[position] = expected[position] + 1; } - if (strict) { - method = method.replace('eep', 'eepStrict'); - } - const fn = assert[method]; if (arrayBuffer) { diff --git a/benchmark/assert/partial-deep-equal.js b/benchmark/assert/partial-deep-equal.js index eee88167525cee..cdda4006874d20 100644 --- a/benchmark/assert/partial-deep-equal.js +++ b/benchmark/assert/partial-deep-equal.js @@ -4,12 +4,13 @@ const common = require('../common.js'); const assert = require('assert'); const bench = common.createBenchmark(main, { - n: [25], + n: [125], size: [500], - extraProps: [0], + extraProps: [0, 1], datasetName: [ 'objects', 'sets', + 'setsWithObjects', 'maps', 'circularRefs', 'typedArrays', @@ -31,17 +32,29 @@ function createObjects(length, extraProps, depth = 0) { foo: 'yarp', nope: { bar: '123', - ...extraProps ? { a: [1, 2, i] } : {}, + ...(extraProps ? { a: [1, 2, i] } : {}), c: {}, b: !depth ? createObjects(2, extraProps, depth + 1) : [], }, })); } +function createSetsWithObjects(length, extraProps, depth = 0) { + return Array.from({ length }, (_, i) => new Set([ + ...(extraProps ? [{}] : []), + { + simple: 'object', + number: i, + }, + ['array', 'with', 'values'], + new Set([[], {}, { nested: i }]), + ])); +} + function createSets(length, extraProps, depth = 0) { return Array.from({ length }, (_, i) => new Set([ 'yarp', - ...extraProps ? ['123', 1, 2] : [], + ...(extraProps ? ['123', 1, 2] : []), i + 3, null, { @@ -56,7 +69,7 @@ function createSets(length, extraProps, depth = 0) { function createMaps(length, extraProps, depth = 0) { return Array.from({ length }, (_, i) => new Map([ - ...extraProps ? [['primitiveKey', 'primitiveValue']] : [], + ...(extraProps ? [['primitiveKey', 'primitiveValue']] : []), [42, 'numberKey'], ['objectValue', { a: 1, b: i }], ['arrayValue', [1, 2, i]], @@ -114,16 +127,23 @@ function createTypedArrays(length, extraParts) { } function createArrayBuffers(length, extra) { - return Array.from({ length }, (_, n) => new ArrayBuffer(n + extra ? 1 : 0)); + return Array.from({ length }, (_, n) => { + const buffer = Buffer.alloc(n + (extra ? 1 : 0)); + for (let i = 0; i < n; i++) { + buffer.writeInt8(i % 128, i); + } + return buffer.buffer; + }); } function createDataViewArrayBuffers(length, extra) { - return Array.from({ length }, (_, n) => new DataView(new ArrayBuffer(n + extra ? 1 : 0))); + return createArrayBuffers(length, extra).map((buffer) => new DataView(buffer)); } const datasetMappings = { objects: createObjects, sets: createSets, + setsWithObjects: createSetsWithObjects, maps: createMaps, circularRefs: createCircularRefs, typedArrays: createTypedArrays, diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index dcb53f09d2d7cb..7eb9c72119eb92 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -243,7 +243,7 @@ function innerDeepEqual(val1, val2, mode, memos) { TypedArrayPrototypeGetSymbolToStringTag(val2)) { return false; } - if (mode === kPartial) { + if (mode === kPartial && val1.byteLength !== val2.byteLength) { if (!isPartialArrayBufferView(val1, val2)) { return false; } @@ -280,7 +280,7 @@ function innerDeepEqual(val1, val2, mode, memos) { if (!isAnyArrayBuffer(val2)) { return false; } - if (mode !== kPartial) { + if (mode !== kPartial || val1.byteLength === val2.byteLength) { if (!areEqualArrayBuffers(val1, val2)) { return false; } @@ -546,11 +546,8 @@ function partialObjectSetEquiv(a, b, mode, set, memo) { } function setObjectEquiv(a, b, mode, set, memo) { - if (mode === kPartial) { - return partialObjectSetEquiv(a, b, mode, set, memo); - } // Fast path for objects only - if (mode === kStrict && set.size === a.size) { + if (mode !== kLoose && set.size === a.size) { for (const val of a) { if (!setHasEqualElement(set, val, mode, memo)) { return false; @@ -558,6 +555,9 @@ function setObjectEquiv(a, b, mode, set, memo) { } return true; } + if (mode === kPartial) { + return partialObjectSetEquiv(a, b, mode, set, memo); + } for (const val of a) { // Primitive values have already been handled above. @@ -639,11 +639,8 @@ function partialObjectMapEquiv(a, b, mode, set, memo) { } function mapObjectEquivalence(a, b, mode, set, memo) { - if (mode === kPartial) { - return partialObjectMapEquiv(a, b, mode, set, memo); - } // Fast path for objects only - if (mode === kStrict && set.size === a.size) { + if (mode !== kLoose && set.size === a.size) { for (const { 0: key1, 1: item1 } of a) { if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) { return false; @@ -651,6 +648,9 @@ function mapObjectEquivalence(a, b, mode, set, memo) { } return true; } + if (mode === kPartial) { + return partialObjectMapEquiv(a, b, mode, set, memo); + } for (const { 0: key1, 1: item1 } of a) { if (typeof key1 === 'object' && key1 !== null) { if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) diff --git a/test/parallel/test-assert-partial-deep-equal.js b/test/parallel/test-assert-partial-deep-equal.js index 08031d4e0aa747..ce0c1e333580c9 100644 --- a/test/parallel/test-assert-partial-deep-equal.js +++ b/test/parallel/test-assert-partial-deep-equal.js @@ -280,6 +280,11 @@ describe('Object Comparison Tests', () => { [{ a: 1 }, 'value1'], ]), }, + { + description: 'throws for Maps with mixed unequal entries', + actual: new Map([[{ a: 2 }, 1], [1, 1], [{ b: 1 }, 1], [[], 1], [2, 1], [{ a: 1 }, 1]]), + expected: new Map([[{ a: 1 }, 1], [[], 1], [2, 1], [{ a: 1 }, 1]]), + }, { description: 'throws for sets with different object values', actual: new Set([ @@ -494,6 +499,11 @@ describe('Object Comparison Tests', () => { actual: new Float32Array([+0.0]), expected: new Float32Array([-0.0]), }, + { + description: 'throws when comparing two Uint8Array objects with non-matching entries', + actual: { typedArray: new Uint8Array([1, 2, 3, 4, 5]) }, + expected: { typedArray: new Uint8Array([1, 333, 2, 4]) }, + }, { description: 'throws when comparing two different urls', actual: new URL('http://foo'), @@ -713,7 +723,7 @@ describe('Object Comparison Tests', () => { { description: 'compares two Uint8Array objects', actual: { typedArray: new Uint8Array([1, 2, 3, 4, 5]) }, - expected: { typedArray: new Uint8Array([1, 2, 3]) }, + expected: { typedArray: new Uint8Array([1, 2, 3, 5]) }, }, { description: 'compares two Int16Array objects',