diff --git a/benchmark/assert/partial-deep-strict-equal.js b/benchmark/assert/partial-deep-strict-equal.js new file mode 100644 index 00000000000000..199c21ae45093e --- /dev/null +++ b/benchmark/assert/partial-deep-strict-equal.js @@ -0,0 +1,89 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('assert'); + +const bench = common.createBenchmark(main, { + n: [25, 2e2], + size: [1e2, 1e4], + datasetName: ['objects', 'sets', 'maps', 'arrayBuffers'], +}, { + combinationFilter: (p) => { + return p.size === 1e4 && p.n === 25 || + p.size === 1e3 && p.n === 2e2 || + p.size === 1e2 && p.n === 2e3 || + p.size === 1; + }, +}); + +function createObjects(size, depth = 0) { + return Array.from({ length: size }, (n) => ({ + foo: 'yarp', + nope: { + bar: '123', + a: [1, 2, 3], + baz: n, + c: {}, + b: !depth ? createObjects(2, depth + 1) : [], + }, + })); +} + +function createSets(size, depth = 0) { + return Array.from({ length: size }, (n) => new Set([ + 'yarp', + { + bar: '123', + a: [1, 2, 3], + baz: n, + c: {}, + b: !depth ? createSets(2, depth + 1) : new Set(), + }, + ])); +} + +function createMaps(size, depth = 0) { + return Array.from({ length: size }, (n) => new Map([ + ['foo', 'yarp'], + ['nope', new Map([ + ['bar', '123'], + ['a', [1, 2, 3]], + ['baz', n], + ['c', {}], + ['b', !depth ? createMaps(2, depth + 1) : new Map()], + ])], + ])); +} + +function createArrayBuffers(size) { + return Array.from({ length: size }, (n) => { + if (n % 2) { + return new DataView(new ArrayBuffer(n)); + } + return new ArrayBuffer(n); + }); +} + +const datasetMappings = { + objects: createObjects, + sets: createSets, + maps: createMaps, + arrayBuffers: createArrayBuffers, +}; + +function getDatasets(datasetName, size) { + return { + actual: datasetMappings[datasetName](size), + expected: datasetMappings[datasetName](size), + }; +} + +function main({ size, n, datasetName }) { + const { actual, expected } = getDatasets(datasetName, size); + + bench.start(); + for (let i = 0; i < n; ++i) { + assert.partialDeepStrictEqual(actual, expected); + } + bench.end(n); +} diff --git a/lib/assert.js b/lib/assert.js index 16c06593601eac..2266a16d23f7d5 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -23,7 +23,6 @@ const { ArrayBufferIsView, ArrayBufferPrototypeGetByteLength, - ArrayFrom, ArrayIsArray, ArrayPrototypeIndexOf, ArrayPrototypeJoin, @@ -395,12 +394,11 @@ function partiallyCompareMaps(actual, expected, comparedObjects) { const expectedIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], expected); for (const { 0: key, 1: expectedValue } of expectedIterator) { - if (!MapPrototypeHas(actual, key)) { + const actualValue = MapPrototypeGet(actual, key); + if (actualValue === undefined && !MapPrototypeHas(actual, key)) { return false; } - const actualValue = MapPrototypeGet(actual, key); - if (!compareBranch(actualValue, expectedValue, comparedObjects)) { return false; } @@ -481,18 +479,30 @@ function partiallyCompareSets(actual, expected, comparedObjects) { if (isDeepEqual === undefined) lazyLoadComparison(); - const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual)); + // Create a map for faster lookups and counts + const actualMap = new SafeMap(); + const actualIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual); const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected); - const usedIndices = new SafeSet(); - expectedIteration: for (const expectedItem of expectedIterator) { - for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) { - if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) { - usedIndices.add(actualIdx); - continue expectedIteration; + for (const actualItem of actualIterator) { + actualMap.set(actualItem, (actualMap.get(actualItem) || 0) + 1); + } + + const actualMapIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actualMap); + + for (const expectedItem of expectedIterator) { + let foundMatch = false; + for (const { 0: actualItem, 1: count } of actualMapIterator) { + if (count > 0 && isDeepStrictEqual(actualItem, expectedItem)) { + actualMap.set(actualItem, count - 1); + foundMatch = true; + break; } } - return false; + + if (!foundMatch) { + return false; + } } return true; @@ -514,17 +524,15 @@ function partiallyCompareArrays(actual, expected, comparedObjects) { return false; } - if (isDeepEqual === undefined) lazyLoadComparison(); + if (typeof isDeepStrictEqual === 'undefined') lazyLoadComparison(); // Create a map to count occurrences of each element in the expected array const expectedCounts = new SafeMap(); - const safeExpected = new SafeArrayIterator(expected); - for (const expectedItem of safeExpected) { - // Check if the item is a zero or a -0, as these need to be handled separately + for (const expectedItem of new SafeArrayIterator(expected)) { if (expectedItem === 0) { const zeroKey = getZeroKey(expectedItem); - expectedCounts.set(zeroKey, (expectedCounts.get(zeroKey)?.count || 0) + 1); + expectedCounts.set(zeroKey, (expectedCounts.get(zeroKey) || 0) + 1); } else { let found = false; for (const { 0: key, 1: count } of expectedCounts) { @@ -540,10 +548,7 @@ function partiallyCompareArrays(actual, expected, comparedObjects) { } } - const safeActual = new SafeArrayIterator(actual); - - for (const actualItem of safeActual) { - // Check if the item is a zero or a -0, as these need to be handled separately + for (const actualItem of new SafeArrayIterator(actual)) { if (actualItem === 0) { const zeroKey = getZeroKey(actualItem); @@ -554,6 +559,7 @@ function partiallyCompareArrays(actual, expected, comparedObjects) { } else { expectedCounts.set(zeroKey, count - 1); } + continue; } } else { for (const { 0: expectedItem, 1: count } of expectedCounts) {