From 841279d79c1b03fef4b864ee6681716220fa93b2 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 9 Feb 2023 22:07:11 +0100 Subject: [PATCH 1/4] benchmark: rework assert benchmarks for correctness This reworks most assert benchmarks to provide more reliable test cases that also test more cases than before while keeping the runtime low. Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/46593 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- benchmark/assert/deepequal-buffer.js | 42 +++++++++---- benchmark/assert/deepequal-object.js | 33 +++++----- .../deepequal-prims-and-objs-big-loop.js | 60 ++++++++++++++++--- ...t.js => deepequal-simple-array-and-set.js} | 42 +++++-------- benchmark/assert/deepequal-typedarrays.js | 18 +++--- benchmark/assert/ok.js | 17 ------ benchmark/assert/throws.js | 42 ------------- 7 files changed, 123 insertions(+), 131 deletions(-) rename benchmark/assert/{deepequal-prims-and-objs-big-array-set.js => deepequal-simple-array-and-set.js} (54%) delete mode 100644 benchmark/assert/ok.js delete mode 100644 benchmark/assert/throws.js diff --git a/benchmark/assert/deepequal-buffer.js b/benchmark/assert/deepequal-buffer.js index 69cca91cc6d752..1b6aa3bee1d6ac 100644 --- a/benchmark/assert/deepequal-buffer.js +++ b/benchmark/assert/deepequal-buffer.js @@ -6,27 +6,47 @@ const bench = common.createBenchmark(main, { n: [2e4], len: [1e2, 1e3], strict: [0, 1], - method: ['deepEqual', 'notDeepEqual'], + arrayBuffer: [0, 1], + method: ['deepEqual', 'notDeepEqual', 'unequal_length'], +}, { + combinationFilter: (p) => { + return p.strict === 1 || p.method === 'deepEqual'; + }, }); -function main({ len, n, method, strict }) { - const data = Buffer.allocUnsafe(len + 1); - const actual = Buffer.alloc(len); - const expected = Buffer.alloc(len); - const expectedWrong = Buffer.alloc(len + 1); - data.copy(actual); - data.copy(expected); - data.copy(expectedWrong); +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'; + } + + for (let i = 0; i < len; i++) { + actual.writeInt8(i % 128, i); + expected.writeInt8(i % 128, i); + } + + if (method.includes('not')) { + const position = Math.floor(len / 2); + expected[position] = expected[position] + 1; + } if (strict) { method = method.replace('eep', 'eepStrict'); } + const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + if (arrayBuffer) { + actual = actual.buffer; + expected = expected.buffer; + } bench.start(); for (let i = 0; i < n; ++i) { - fn(actual, value2); + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/deepequal-object.js b/benchmark/assert/deepequal-object.js index 7418e2a745ba40..80bb50f7982529 100644 --- a/benchmark/assert/deepequal-object.js +++ b/benchmark/assert/deepequal-object.js @@ -4,14 +4,20 @@ const common = require('../common.js'); const assert = require('assert'); const bench = common.createBenchmark(main, { - n: [5e3], - size: [1e2, 1e3, 5e4], - strict: [0, 1], + n: [25, 2e2, 2e3], + size: [1e2, 1e3, 1e4], + strict: [1], method: ['deepEqual', 'notDeepEqual'], +}, { + combinationFilter: (p) => { + return p.size === 1e4 && p.n === 25 || + p.size === 1e3 && p.n === 2e2 || + p.size === 1e2 && p.n === 2e3; + }, }); -function createObj(source, add = '') { - return source.map((n) => ({ +function createObj(size, add = '') { + return Array.from({ length: size }, (n) => ({ foo: 'yarp', nope: { bar: `123${add}`, @@ -24,22 +30,17 @@ function createObj(source, add = '') { } function main({ size, n, method, strict }) { - const len = Math.min(Math.ceil(n / size), 20); - - const source = Array.apply(null, Array(size)); - const actual = createObj(source); - const expected = createObj(source); - const expectedWrong = createObj(source, '4'); - if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + const actual = createObj(size); + const expected = method.includes('not') ? createObj(size, '4') : createObj(size); bench.start(); - for (let i = 0; i < len; ++i) { - fn(actual, value2); + for (let i = 0; i < n; ++i) { + fn(actual, expected); } - bench.end(len); + bench.end(n); } diff --git a/benchmark/assert/deepequal-prims-and-objs-big-loop.js b/benchmark/assert/deepequal-prims-and-objs-big-loop.js index 2d01431b1fc563..1ab4ff4dd81f33 100644 --- a/benchmark/assert/deepequal-prims-and-objs-big-loop.js +++ b/benchmark/assert/deepequal-prims-and-objs-big-loop.js @@ -2,35 +2,77 @@ const common = require('../common.js'); const assert = require('assert'); +const circular = {}; +circular.circular = circular; +const circular2 = {}; +circular2.circular = circular2; +const notCircular = {}; +notCircular.circular = {}; + const primValues = { - 'string': 'a', - 'number': 1, - 'object': { 0: 'a' }, + 'string': 'abcdef', + 'number': 1_000, + 'boolean': true, + 'object': { property: 'abcdef' }, + 'object_other_property': { property: 'abcdef' }, + 'array': [1, 2, 3], + 'set_object': new Set([[1]]), + 'set_simple': new Set([1, 2, 3]), + 'circular': circular, + 'empty_object': {}, + 'regexp': /abc/i, + 'date': new Date(), +}; + +const primValues2 = { + 'object': { property: 'abcdef' }, 'array': [1, 2, 3], + 'set_object': new Set([[1]]), + 'set_simple': new Set([1, 3, 2]), + 'circular': circular2, + 'empty_object': {}, + 'regexp': /abc/i, + 'date': new Date(primValues.date), +}; + +const primValuesUnequal = { + 'string': 'abcdez', + 'number': 1_001, + 'boolean': false, + 'object': { property2: 'abcdef' }, + 'array': [1, 3, 2], + 'set_object': new Set([[2]]), + 'set_simple': new Set([1, 4, 2]), + 'circular': notCircular, + 'empty_object': [], + 'regexp': /abc/g, + 'date': new Date(primValues.date.getTime() + 1), }; const bench = common.createBenchmark(main, { primitive: Object.keys(primValues), - n: [2e4], + n: [1e5], strict: [0, 1], method: ['deepEqual', 'notDeepEqual'], +}, { + combinationFilter: (p) => { + return p.strict === 1 || p.method === 'deepEqual'; + }, }); function main({ n, primitive, method, strict }) { const prim = primValues[primitive]; - const actual = prim; - const expected = prim; - const expectedWrong = 'b'; + const actual = primValues2[primitive] ?? prim; + const expected = method.includes('not') ? primValuesUnequal[primitive] : prim; if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; bench.start(); for (let i = 0; i < n; ++i) { - fn([actual], [value2]); + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/deepequal-prims-and-objs-big-array-set.js b/benchmark/assert/deepequal-simple-array-and-set.js similarity index 54% rename from benchmark/assert/deepequal-prims-and-objs-big-array-set.js rename to benchmark/assert/deepequal-simple-array-and-set.js index ad049ded02ce9d..a1f6820696d7b8 100644 --- a/benchmark/assert/deepequal-prims-and-objs-big-array-set.js +++ b/benchmark/assert/deepequal-simple-array-and-set.js @@ -4,18 +4,10 @@ const common = require('../common.js'); const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } = require('assert'); -const primValues = { - 'string': 'a', - 'number': 1, - 'object': { 0: 'a' }, - 'array': [1, 2, 3], -}; - const bench = common.createBenchmark(main, { - primitive: Object.keys(primValues), - n: [25], - len: [2e4], - strict: [0, 1], + n: [5e2], + len: [1e4], + strict: [1], method: [ 'deepEqual_Array', 'notDeepEqual_Array', @@ -32,38 +24,32 @@ function run(fn, n, actual, expected) { bench.end(n); } -function main({ n, len, primitive, method, strict }) { - const prim = primValues[primitive]; +function main({ n, len, method, strict }) { const actual = []; const expected = []; - const expectedWrong = []; - for (let x = 0; x < len; x++) { - actual.push(prim); - expected.push(prim); - expectedWrong.push(prim); + for (let i = 0; i < len; i++) { + actual.push(i); + expected.push(i); + } + if (method.includes('not')) { + expected[len - 1] += 1; } - expectedWrong.pop(); - expectedWrong.push('b'); - - // Note: primitives are only added once to a set - const actualSet = new Set(actual); - const expectedSet = new Set(expected); - const expectedWrongSet = new Set(expectedWrong); switch (method) { case 'deepEqual_Array': run(strict ? deepStrictEqual : deepEqual, n, actual, expected); break; case 'notDeepEqual_Array': - run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expectedWrong); + run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected); break; case 'deepEqual_Set': - run(strict ? deepStrictEqual : deepEqual, n, actualSet, expectedSet); + run(strict ? deepStrictEqual : deepEqual, + n, new Set(actual), new Set(expected)); break; case 'notDeepEqual_Set': run(strict ? notDeepStrictEqual : notDeepEqual, - n, actualSet, expectedWrongSet); + n, new Set(actual), new Set(expected)); break; default: throw new Error(`Unsupported method "${method}"`); diff --git a/benchmark/assert/deepequal-typedarrays.js b/benchmark/assert/deepequal-typedarrays.js index 188cfce695ed61..c06ff8fb2f3099 100644 --- a/benchmark/assert/deepequal-typedarrays.js +++ b/benchmark/assert/deepequal-typedarrays.js @@ -7,8 +7,7 @@ const bench = common.createBenchmark(main, { 'Int8Array', 'Uint8Array', 'Float32Array', - 'Float64Array', - 'Uint8ClampedArray', + 'Uint32Array', ], n: [5e2], strict: [0, 1], @@ -23,21 +22,24 @@ function main({ type, n, len, method, strict }) { const clazz = global[type]; const actual = new clazz(len); const expected = new clazz(len); - const expectedWrong = new clazz(len); - const wrongIndex = Math.floor(len / 2); - expectedWrong[wrongIndex] = 123; if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + if (method.includes('not')) { + expected[Math.floor(len / 2)] = 123; + } bench.start(); for (let i = 0; i < n; ++i) { actual[0] = i; - value2[0] = i; - fn(actual, value2); + expected[0] = i; + const pos = Math.ceil(len / 2) + 1; + actual[pos] = i; + expected[pos] = i; + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/ok.js b/benchmark/assert/ok.js deleted file mode 100644 index 42fd2e89b78d1e..00000000000000 --- a/benchmark/assert/ok.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const common = require('../common.js'); -const assert = require('assert'); - -const bench = common.createBenchmark(main, { n: [1e5] }); - -function main({ n }) { - bench.start(); - for (let i = 0; i < n; ++i) { - if (i % 2 === 0) - assert(true); - else - assert(true, 'foo bar baz'); - } - bench.end(n); -} diff --git a/benchmark/assert/throws.js b/benchmark/assert/throws.js deleted file mode 100644 index 978ad2f1b8bef0..00000000000000 --- a/benchmark/assert/throws.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const common = require('../common.js'); -const { throws, doesNotThrow } = require('assert'); - -const bench = common.createBenchmark(main, { - n: [1e4], - method: [ 'doesNotThrow', 'throws_TypeError', 'throws_RegExp' ], -}); - -function main({ n, method }) { - const throwError = () => { throw new TypeError('foobar'); }; - const doNotThrowError = () => { return 'foobar'; }; - const regExp = /foobar/; - const message = 'failure'; - - switch (method) { - case 'doesNotThrow': - bench.start(); - for (let i = 0; i < n; ++i) { - doesNotThrow(doNotThrowError); - } - bench.end(n); - break; - case 'throws_TypeError': - bench.start(); - for (let i = 0; i < n; ++i) { - throws(throwError, TypeError, message); - } - bench.end(n); - break; - case 'throws_RegExp': - bench.start(); - for (let i = 0; i < n; ++i) { - throws(throwError, regExp, message); - } - bench.end(n); - break; - default: - throw new Error(`Unsupported method ${method}`); - } -} From a02b434966cd9e4ba1d4ed0d5d70c88c887991cd Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 10 Feb 2023 03:37:37 +0100 Subject: [PATCH 2/4] test: remove obsolete util.isDeepStrictEqual tests These tests are a copy of the assert tests that verify the deep equal comparison algorithm. There is no need to duplicate the tests as changing one would require to change the tests in two places without knowing which ones to actually rely upon. Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/46593 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- test/parallel/test-util-isDeepStrictEqual.js | 419 ------------------- 1 file changed, 419 deletions(-) diff --git a/test/parallel/test-util-isDeepStrictEqual.js b/test/parallel/test-util-isDeepStrictEqual.js index da266fc84ce0bb..48b3116061932f 100644 --- a/test/parallel/test-util-isDeepStrictEqual.js +++ b/test/parallel/test-util-isDeepStrictEqual.js @@ -7,82 +7,6 @@ require('../common'); const assert = require('assert'); const util = require('util'); -class MyDate extends Date { - constructor(...args) { - super(...args); - this[0] = '1'; - } -} - -class MyRegExp extends RegExp { - constructor(...args) { - super(...args); - this[0] = '1'; - } -} - -{ - const arr = new Uint8Array([120, 121, 122, 10]); - const buf = Buffer.from(arr); - // They have different [[Prototype]] - assert.strictEqual(util.isDeepStrictEqual(arr, buf), false); - - const buf2 = Buffer.from(arr); - buf2.prop = 1; - - assert.strictEqual(util.isDeepStrictEqual(buf2, buf), false); - - const arr2 = new Uint8Array([120, 121, 122, 10]); - arr2.prop = 5; - assert.strictEqual(util.isDeepStrictEqual(arr, arr2), false); -} - -{ - const date = new Date('2016'); - - const date2 = new MyDate('2016'); - - // deepStrictEqual checks own properties - assert.strictEqual(util.isDeepStrictEqual(date, date2), false); - assert.strictEqual(util.isDeepStrictEqual(date2, date), false); -} - -{ - const re1 = new RegExp('test'); - const re2 = new MyRegExp('test'); - - // deepStrictEqual checks all properties - assert.strictEqual(util.isDeepStrictEqual(re1, re2), false); -} - -{ - // For these cases, deepStrictEqual should throw. - const similar = new Set([ - { 0: '1' }, // Object - { 0: 1 }, // Object - new String('1'), // Object - ['1'], // Array - [1], // Array - new MyDate('2016'), // Date with this[0] = '1' - new MyRegExp('test'), // RegExp with this[0] = '1' - new Int8Array([1]), // Int8Array - new Uint8Array([1]), // Uint8Array - new Int16Array([1]), // Int16Array - new Uint16Array([1]), // Uint16Array - new Int32Array([1]), // Int32Array - new Uint32Array([1]), // Uint32Array - Buffer.from([1]), // Buffer - ]); - - for (const a of similar) { - for (const b of similar) { - if (a !== b) { - assert.strictEqual(util.isDeepStrictEqual(a, b), false); - } - } - } -} - function utilIsDeepStrict(a, b) { assert.strictEqual(util.isDeepStrictEqual(a, b), true); assert.strictEqual(util.isDeepStrictEqual(b, a), true); @@ -93,345 +17,6 @@ function notUtilIsDeepStrict(a, b) { assert.strictEqual(util.isDeepStrictEqual(b, a), false); } -// es6 Maps and Sets -utilIsDeepStrict(new Set(), new Set()); -utilIsDeepStrict(new Map(), new Map()); - -utilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3])); -notUtilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3, 4])); -notUtilIsDeepStrict(new Set([1, 2, 3, 4]), new Set([1, 2, 3])); -utilIsDeepStrict(new Set(['1', '2', '3']), new Set(['1', '2', '3'])); -utilIsDeepStrict(new Set([[1, 2], [3, 4]]), new Set([[3, 4], [1, 2]])); - -{ - const a = [ 1, 2 ]; - const b = [ 3, 4 ]; - const c = [ 1, 2 ]; - const d = [ 3, 4 ]; - - utilIsDeepStrict( - { a: a, b: b, s: new Set([a, b]) }, - { a: c, b: d, s: new Set([d, c]) } - ); -} - -utilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[1, 1], [2, 2]])); -utilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[2, 2], [1, 1]])); -notUtilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[1, 2], [2, 1]])); -notUtilIsDeepStrict( - new Map([[[1], 1], [{}, 2]]), - new Map([[[1], 2], [{}, 1]]) -); - -notUtilIsDeepStrict(new Set([1]), [1]); -notUtilIsDeepStrict(new Set(), []); -notUtilIsDeepStrict(new Set(), {}); - -notUtilIsDeepStrict(new Map([['a', 1]]), { a: 1 }); -notUtilIsDeepStrict(new Map(), []); -notUtilIsDeepStrict(new Map(), {}); - -notUtilIsDeepStrict(new Set(['1']), new Set([1])); - -notUtilIsDeepStrict(new Map([['1', 'a']]), new Map([[1, 'a']])); -notUtilIsDeepStrict(new Map([['a', '1']]), new Map([['a', 1]])); -notUtilIsDeepStrict(new Map([['a', '1']]), new Map([['a', 2]])); - -utilIsDeepStrict(new Set([{}]), new Set([{}])); - -// Ref: https://github.com/nodejs/node/issues/13347 -notUtilIsDeepStrict( - new Set([{ a: 1 }, { a: 1 }]), - new Set([{ a: 1 }, { a: 2 }]) -); -notUtilIsDeepStrict( - new Set([{ a: 1 }, { a: 1 }, { a: 2 }]), - new Set([{ a: 1 }, { a: 2 }, { a: 2 }]) -); -notUtilIsDeepStrict( - new Map([[{ x: 1 }, 5], [{ x: 1 }, 5]]), - new Map([[{ x: 1 }, 5], [{ x: 2 }, 5]]) -); - -notUtilIsDeepStrict(new Set([3, '3']), new Set([3, 4])); -notUtilIsDeepStrict(new Map([[3, 0], ['3', 0]]), new Map([[3, 0], [4, 0]])); - -notUtilIsDeepStrict( - new Set([{ a: 1 }, { a: 1 }, { a: 2 }]), - new Set([{ a: 1 }, { a: 2 }, { a: 2 }]) -); - -// Mixed primitive and object keys -utilIsDeepStrict( - new Map([[1, 'a'], [{}, 'a']]), - new Map([[1, 'a'], [{}, 'a']]) -); -utilIsDeepStrict( - new Set([1, 'a', [{}, 'a']]), - new Set([1, 'a', [{}, 'a']]) -); - -// This is an awful case, where a map contains multiple equivalent keys: -notUtilIsDeepStrict( - new Map([[1, 'a'], ['1', 'b']]), - new Map([['1', 'a'], [true, 'b']]) -); -notUtilIsDeepStrict( - new Set(['a']), - new Set(['b']) -); -utilIsDeepStrict( - new Map([[{}, 'a'], [{}, 'b']]), - new Map([[{}, 'b'], [{}, 'a']]) -); -notUtilIsDeepStrict( - new Map([[true, 'a'], ['1', 'b'], [1, 'a']]), - new Map([['1', 'a'], [1, 'b'], [true, 'a']]) -); -notUtilIsDeepStrict( - new Map([[true, 'a'], ['1', 'b'], [1, 'c']]), - new Map([['1', 'a'], [1, 'b'], [true, 'a']]) -); - -// Similar object keys -notUtilIsDeepStrict( - new Set([{}, {}]), - new Set([{}, 1]) -); -notUtilIsDeepStrict( - new Set([[{}, 1], [{}, 1]]), - new Set([[{}, 1], [1, 1]]) -); -notUtilIsDeepStrict( - new Map([[{}, 1], [{}, 1]]), - new Map([[{}, 1], [1, 1]]) -); -notUtilIsDeepStrict( - new Map([[{}, 1], [true, 1]]), - new Map([[{}, 1], [1, 1]]) -); - -// Similar primitive key / values -notUtilIsDeepStrict( - new Set([1, true, false]), - new Set(['1', 0, '0']) -); -notUtilIsDeepStrict( - new Map([[1, 5], [true, 5], [false, 5]]), - new Map([['1', 5], [0, 5], ['0', 5]]) -); - -// Undefined value in Map -utilIsDeepStrict( - new Map([[1, undefined]]), - new Map([[1, undefined]]) -); -notUtilIsDeepStrict( - new Map([[1, null]]), - new Map([['1', undefined]]) -); -notUtilIsDeepStrict( - new Map([[1, undefined]]), - new Map([[2, undefined]]) -); - -// null as key -utilIsDeepStrict( - new Map([[null, 3]]), - new Map([[null, 3]]) -); -notUtilIsDeepStrict( - new Map([[null, undefined]]), - new Map([[undefined, null]]) -); -notUtilIsDeepStrict( - new Set([null]), - new Set([undefined]) -); - -// GH-6416. Make sure circular refs don't throw. -{ - const b = {}; - b.b = b; - const c = {}; - c.b = c; - - utilIsDeepStrict(b, c); - - const d = {}; - d.a = 1; - d.b = d; - const e = {}; - e.a = 1; - e.b = {}; - - notUtilIsDeepStrict(d, e); -} - -// GH-14441. Circular structures should be consistent -{ - const a = {}; - const b = {}; - a.a = a; - b.a = {}; - b.a.a = a; - utilIsDeepStrict(a, b); -} - -{ - const a = new Set(); - const b = new Set(); - const c = new Set(); - a.add(a); - b.add(b); - c.add(a); - utilIsDeepStrict(b, c); -} - -// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects. -{ - const args = (function() { return arguments; })(); - notUtilIsDeepStrict([], args); -} - -// More checking that arguments objects are handled correctly -{ - // eslint-disable-next-line func-style - const returnArguments = function() { return arguments; }; - - const someArgs = returnArguments('a'); - const sameArgs = returnArguments('a'); - const diffArgs = returnArguments('b'); - - notUtilIsDeepStrict(someArgs, ['a']); - notUtilIsDeepStrict(someArgs, { '0': 'a' }); - notUtilIsDeepStrict(someArgs, diffArgs); - utilIsDeepStrict(someArgs, sameArgs); -} - -{ - const values = [ - 123, - Infinity, - 0, - null, - undefined, - false, - true, - {}, - [], - () => {}, - ]; - utilIsDeepStrict(new Set(values), new Set(values)); - utilIsDeepStrict(new Set(values), new Set(values.reverse())); - - const mapValues = values.map((v) => [v, { a: 5 }]); - utilIsDeepStrict(new Map(mapValues), new Map(mapValues)); - utilIsDeepStrict(new Map(mapValues), new Map(mapValues.reverse())); -} - -{ - const s1 = new Set(); - const s2 = new Set(); - s1.add(1); - s1.add(2); - s2.add(2); - s2.add(1); - utilIsDeepStrict(s1, s2); -} - -{ - const m1 = new Map(); - const m2 = new Map(); - const obj = { a: 5, b: 6 }; - m1.set(1, obj); - m1.set(2, 'hi'); - m1.set(3, [1, 2, 3]); - - m2.set(2, 'hi'); // different order - m2.set(1, obj); - m2.set(3, [1, 2, 3]); // Deep equal, but not reference equal. - - utilIsDeepStrict(m1, m2); -} - -{ - const m1 = new Map(); - const m2 = new Map(); - - // m1 contains itself. - m1.set(1, m1); - m2.set(1, new Map()); - - notUtilIsDeepStrict(m1, m2); -} - -{ - const map1 = new Map([[1, 1]]); - const map2 = new Map([[1, '1']]); - assert.strictEqual(util.isDeepStrictEqual(map1, map2), false); -} - -{ - // Two equivalent sets / maps with different key/values applied shouldn't be - // the same. This is a terrible idea to do in practice, but deepEqual should - // still check for it. - const s1 = new Set(); - const s2 = new Set(); - s1.x = 5; - notUtilIsDeepStrict(s1, s2); - - const m1 = new Map(); - const m2 = new Map(); - m1.x = 5; - notUtilIsDeepStrict(m1, m2); -} - -{ - // Circular references. - const s1 = new Set(); - s1.add(s1); - const s2 = new Set(); - s2.add(s2); - utilIsDeepStrict(s1, s2); - - const m1 = new Map(); - m1.set(2, m1); - const m2 = new Map(); - m2.set(2, m2); - utilIsDeepStrict(m1, m2); - - const m3 = new Map(); - m3.set(m3, 2); - const m4 = new Map(); - m4.set(m4, 2); - utilIsDeepStrict(m3, m4); -} - -// Handle sparse arrays -/* eslint-disable no-sparse-arrays */ -utilIsDeepStrict([1, , , 3], [1, , , 3]); -notUtilIsDeepStrict([1, , , 3], [1, , , 3, , , ]); -/* eslint-enable no-sparse-arrays */ - -// Handle different error messages -{ - const err1 = new Error('foo1'); - const err2 = new Error('foo2'); - const err3 = new TypeError('foo1'); - notUtilIsDeepStrict(err1, err2, assert.AssertionError); - notUtilIsDeepStrict(err1, err3, assert.AssertionError); - notUtilIsDeepStrict(err1, {}, assert.AssertionError); -} - -// Handle NaN -assert.strictEqual(util.isDeepStrictEqual(NaN, NaN), true); -assert.strictEqual(util.isDeepStrictEqual({ a: NaN }, { a: NaN }), true); -assert.strictEqual( - util.isDeepStrictEqual([ 1, 2, NaN, 4 ], [ 1, 2, NaN, 4 ]), - true -); - // Handle boxed primitives { const boxedString = new String('test'); @@ -478,10 +63,6 @@ assert.strictEqual( notUtilIsDeepStrict(symbolish, new String('fhqwhgads')); } -// Minus zero -notUtilIsDeepStrict(0, -0); -utilIsDeepStrict(-0, -0); - // Handle symbols (enumerable only) { const symbol1 = Symbol(); From 449e9f448937ed1843e4edd61b19590177925ea1 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 9 Feb 2023 22:12:25 +0100 Subject: [PATCH 3/4] assert,util: improve deep equal comparison performance This is mainly a performance improvement for a lot of simple cases. Diverging elements are detected earlier and equal entries are partially also detected faster. A small correctness patch is also included where recursions now stop as soon as either side has a circular structure. Before, both sides had to have a circular structure at the specific comparison which could have caused more checks that likely fail at a later point. Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/46593 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- benchmark/assert/deepequal-object.js | 3 +- doc/api/assert.md | 12 +- lib/internal/util/comparisons.js | 270 +++++++++++++++------------ test/parallel/test-assert-deep.js | 22 ++- 4 files changed, 175 insertions(+), 132 deletions(-) diff --git a/benchmark/assert/deepequal-object.js b/benchmark/assert/deepequal-object.js index 80bb50f7982529..4238129b292b45 100644 --- a/benchmark/assert/deepequal-object.js +++ b/benchmark/assert/deepequal-object.js @@ -12,7 +12,8 @@ const bench = common.createBenchmark(main, { combinationFilter: (p) => { return p.size === 1e4 && p.n === 25 || p.size === 1e3 && p.n === 2e2 || - p.size === 1e2 && p.n === 2e3; + p.size === 1e2 && p.n === 2e3 || + p.size === 1; }, }); diff --git a/doc/api/assert.md b/doc/api/assert.md index d40300cc3fdb1b..34107c7730fdd9 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -546,6 +546,10 @@ An alias of [`assert.ok()`][].