Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024-12-01, Version 23.4.0 (Current), @aduh95 #28

Closed
wants to merge 53 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
882b70c
tools: bump cross-spawn from 7.0.3 to 7.0.5 in /tools/eslint
dependabot[bot] Nov 18, 2024
72eb710
tools: fix riscv64 build failed
luyahan Nov 18, 2024
f9d25ed
doc: add history entry for import assertion removal
aduh95 Nov 18, 2024
83e02dc
build: compile bundled ada conditionally
jirutka Nov 16, 2024
d8eb83c
build: compile bundled simdjson conditionally
jirutka Nov 16, 2024
fffabca
build: compile bundled simdutf conditionally
jirutka Nov 16, 2024
9c61038
deps: update simdutf to 5.6.2
nodejs-github-bot Nov 19, 2024
efb9f05
doc,lib,src,test: unflag sqlite module
cjihrig Nov 19, 2024
8a5d8c7
test_runner: mark context.plan() as stable
cjihrig Nov 19, 2024
8197815
test_runner: mark snapshot testing as stable
cjihrig Nov 19, 2024
219f5f2
doc: include git node release --promote to steps
RafaelGSS Nov 19, 2024
f711a48
doc: fix relative path mention in --allow-fs
RafaelGSS Nov 19, 2024
d093820
tools: lint js in `doc/**/*.md`
LiviaMedeiros Nov 20, 2024
497a9ae
src: fix kill signal on Windows
huseyinacacak-janea Nov 20, 2024
493e16c
test: fix determining lower priority
LiviaMedeiros Nov 20, 2024
5a2a757
doc: add esm examples to node:timers
mfdebian Nov 20, 2024
f48e289
build: fix GN build for sqlite
zcbenz Nov 21, 2024
7768b3d
deps: update simdjson to 3.10.1
nodejs-github-bot Nov 21, 2024
9d07880
doc: remove RedYetiDev from triagers team
Nov 21, 2024
d777d4a
sqlite: add `StatementSync.prototype.iterate` method
tpoisseau Nov 21, 2024
29362ce
test: make x509 crypto tests work with BoringSSL
codebytere Nov 22, 2024
c8bb8a6
doc: fix Node.js 23 column in CHANGELOG.md
richardlau Nov 22, 2024
475141e
tools: add linter for release commit proposals
aduh95 Nov 22, 2024
ccc5a6d
doc: document approach for building wasm in deps
mhdawson Nov 22, 2024
26ec996
build: use variable for crypto dep path
codebytere Nov 23, 2024
4be5047
module: do not warn when require(esm) comes from node_modules
joyeecheung Nov 23, 2024
ff48c29
doc: add esm example for zlib
peixotoleonardo Nov 23, 2024
cf3f7ac
deps: update zlib to 1.3.0.1-motley-7e2e4d7
nodejs-github-bot Aug 18, 2024
93d36bf
crypto: allow non-multiple of 8 in SubtleCrypto.deriveBits
panva Oct 6, 2024
1e0decb
doc: doc how to add message for promotion
mhdawson Nov 13, 2024
2023b09
build: add create release proposal action
RafaelGSS Nov 23, 2024
98f8f4a
doc: order `node:crypto` APIs alphabetically
badkeyy Nov 23, 2024
a4f57f0
assert: add partialDeepStrictEqual
puskin94 Nov 23, 2024
c157e02
test: convert readdir test to use test runner
tchetwin Nov 23, 2024
288416a
deps: upgrade npm to 10.9.1
npm-cli-bot Nov 24, 2024
f7567d4
test: make HTTP/1.0 connection test more robust
FliegendeWurst Nov 24, 2024
c048865
test_runner: simplify hook running logic
cjihrig Nov 25, 2024
30f26ba
lib: avoid excluding symlinks in recursive fs.readdir with filetypes
juanarbol Nov 25, 2024
95e8c4e
test_runner: refactor build Promise in Suite()
cjihrig Nov 22, 2024
7c3a4d4
test_runner: refactor Promise chain in run()
cjihrig Nov 22, 2024
32b1681
tools: use tokenless Codecov uploads
targos Nov 25, 2024
7705724
doc: add vetted courses to the ambassador benefits
mcollina Nov 25, 2024
a3f7db6
doc: add doc for PerformanceObserver.takeRecords()
skyclouds2001 Nov 25, 2024
5b0ce37
assert: optimize partial comparison of two `Set`s
aduh95 Nov 25, 2024
baed276
doc: deprecate passing invalid types in `fs.existsSync`
Ceres6 Nov 25, 2024
1fb30d6
quic: multiple updates to quic impl
jasnell Nov 23, 2024
d180a8a
deps: update simdutf to 5.6.3
nodejs-github-bot Nov 26, 2024
96e846d
deps: update ngtcp2 to 1.9.0
nodejs-github-bot Nov 26, 2024
f99f95f
deps: update corepack to 0.30.0
nodejs-github-bot Nov 26, 2024
9289374
http2: fix memory leak caused by premature listener removing
ywave620 Nov 26, 2024
ce53f16
build: set node_arch to target_cpu in GN
codebytere Nov 26, 2024
7133c04
build: avoid compiling with VS v17.12
StefanStojanovic Nov 26, 2024
2a8b3d3
2024-12-01, Version 23.4.0 (Current)
aduh95 Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
assert: add partialDeepStrictEqual
Fixes: nodejs#50399

Co-Authored-By: Cristian Barlutiu <cristian.barlutiu@gmail.com>
PR-URL: nodejs#54630
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Jithil P Ponnan <jithil@outlook.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
2 people authored and aduh95 committed Nov 26, 2024

Verified

This commit was signed with the committer’s verified signature.
aduh95 Antoine du Hamel
commit a4f57f0293b4f2b7ad3582a7c4944d7172cfdb5b
91 changes: 91 additions & 0 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
@@ -2548,6 +2548,96 @@ assert.throws(throwingFirst, /Second$/);
Due to the confusing error-prone notation, avoid a string as the second
argument.

## `assert.partialDeepStrictEqual(actual, expected[, message])`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development

* `actual` {any}
* `expected` {any}
* `message` {string|Error}

[`assert.partialDeepStrictEqual()`][] Asserts the equivalence between the `actual` and `expected` parameters through a
deep comparison, ensuring that all properties in the `expected` parameter are
present in the `actual` parameter with equivalent values, not allowing type coercion.
The main difference with [`assert.deepStrictEqual()`][] is that [`assert.partialDeepStrictEqual()`][] does not require
all properties in the `actual` parameter to be present in the `expected` parameter.
This method should always pass the same test cases as [`assert.deepStrictEqual()`][], behaving as a super set of it.

```mjs
import assert from 'node:assert';

assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
// OK

assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
// OK

assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
// OK

assert.partialDeepStrictEqual(new Set(['value1', 'value2']), new Set(['value1', 'value2']));
// OK

assert.partialDeepStrictEqual(new Map([['key1', 'value1']]), new Map([['key1', 'value1']]));
// OK

assert.partialDeepStrictEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]));
// OK

assert.partialDeepStrictEqual(/abc/, /abc/);
// OK

assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
// OK

assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
// OK

assert.partialDeepStrictEqual(new Date(0), new Date(0));
// OK

assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
// AssertionError

assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
// AssertionError

assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
// AssertionError
```

```cjs
const assert = require('node:assert');

assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
// OK

assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
// OK

assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
// OK

assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
// OK

assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
// OK

assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
// AssertionError

assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
// AssertionError

assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
// AssertionError
```

[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
@@ -2576,6 +2666,7 @@ argument.
[`assert.notEqual()`]: #assertnotequalactual-expected-message
[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message
[`assert.ok()`]: #assertokvalue-message
[`assert.partialDeepStrictEqual()`]: #assertpartialdeepstrictequalactual-expected-message
[`assert.strictEqual()`]: #assertstrictequalactual-expected-message
[`assert.throws()`]: #assertthrowsfn-error-message
[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv
212 changes: 210 additions & 2 deletions lib/assert.js
Original file line number Diff line number Diff line change
@@ -21,22 +21,35 @@
'use strict';

const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeIndexOf,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSlice,
Error,
FunctionPrototypeCall,
MapPrototypeDelete,
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeSet,
NumberIsNaN,
ObjectAssign,
ObjectIs,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
ReflectApply,
ReflectHas,
ReflectOwnKeys,
RegExpPrototypeExec,
SafeMap,
SafeSet,
SafeWeakSet,
String,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
SymbolIterator,
} = primordials;

const {
@@ -50,8 +63,18 @@ const {
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { inspect } = require('internal/util/inspect');
const { isPromise, isRegExp } = require('internal/util/types');
const { isError, deprecate } = require('internal/util');
const { Buffer } = require('buffer');
const {
isKeyObject,
isPromise,
isRegExp,
isMap,
isSet,
isDate,
isWeakSet,
isWeakMap,
} = require('internal/util/types');
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
const { innerOk } = require('internal/assert/utils');

const CallTracker = require('internal/assert/calltracker');
@@ -341,6 +364,191 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
}
};

function isSpecial(obj) {
return obj == null || typeof obj !== 'object' || isError(obj) || isRegExp(obj) || isDate(obj);
}

const typesToCallDeepStrictEqualWith = [
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
];

/**
* Compares two objects or values recursively to check if they are equal.
* @param {any} actual - The actual value to compare.
* @param {any} expected - The expected value to compare.
* @param {Set} [comparedObjects=new Set()] - Set to track compared objects for handling circular references.
* @returns {boolean} - Returns `true` if the actual value matches the expected value, otherwise `false`.
* @example
* compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true
*/
function compareBranch(
actual,
expected,
comparedObjects,
) {
// Check for Map object equality
if (isMap(actual) && isMap(expected)) {
if (actual.size !== expected.size) {
return false;
}
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);

comparedObjects ??= new SafeWeakSet();

for (const { 0: key, 1: val } of safeIterator) {
if (!MapPrototypeHas(expected, key)) {
return false;
}
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
return false;
}
}
return true;
}

for (const type of typesToCallDeepStrictEqualWith) {
if (type(actual) || type(expected)) {
if (isDeepStrictEqual === undefined) lazyLoadComparison();
return isDeepStrictEqual(actual, expected);
}
}

// Check for Set object equality
// TODO(aduh95): switch to `SetPrototypeIsSubsetOf` when it's available
if (isSet(actual) && isSet(expected)) {
if (expected.size > actual.size) {
return false; // `expected` can't be a subset if it has more elements
}

if (isDeepEqual === undefined) lazyLoadComparison();

const actualArray = ArrayFrom(actual);
const expectedArray = ArrayFrom(expected);
const usedIndices = new SafeSet();

for (let expectedIdx = 0; expectedIdx < expectedArray.length; expectedIdx++) {
const expectedItem = expectedArray[expectedIdx];
let found = false;

for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
usedIndices.add(actualIdx);
found = true;
break;
}
}

if (!found) {
return false;
}
}

return true;
}

// Check if expected array is a subset of actual array
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
if (expected.length > actual.length) {
return false;
}

if (isDeepEqual === undefined) lazyLoadComparison();

// Create a map to count occurrences of each element in the expected array
const expectedCounts = new SafeMap();
for (const expectedItem of expected) {
let found = false;
for (const { 0: key, 1: count } of expectedCounts) {
if (isDeepStrictEqual(key, expectedItem)) {
MapPrototypeSet(expectedCounts, key, count + 1);
found = true;
break;
}
}
if (!found) {
MapPrototypeSet(expectedCounts, expectedItem, 1);
}
}

// Create a map to count occurrences of relevant elements in the actual array
for (const actualItem of actual) {
for (const { 0: key, 1: count } of expectedCounts) {
if (isDeepStrictEqual(key, actualItem)) {
if (count === 1) {
MapPrototypeDelete(expectedCounts, key);
} else {
MapPrototypeSet(expectedCounts, key, count - 1);
}
break;
}
}
}

return !expectedCounts.size;
}

// Comparison done when at least one of the values is not an object
if (isSpecial(actual) || isSpecial(expected)) {
if (isDeepEqual === undefined) {
lazyLoadComparison();
}
return isDeepStrictEqual(actual, expected);
}

// Use Reflect.ownKeys() instead of Object.keys() to include symbol properties
const keysExpected = ReflectOwnKeys(expected);

comparedObjects ??= new SafeWeakSet();

// Handle circular references
if (comparedObjects.has(actual)) {
return true;
}
comparedObjects.add(actual);

// Check if all expected keys and values match
for (let i = 0; i < keysExpected.length; i++) {
const key = keysExpected[i];
assert(
ReflectHas(actual, key),
new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }),
);
if (!compareBranch(actual[key], expected[key], comparedObjects)) {
return false;
}
}

return true;
}

/**
* The strict equivalence assertion test between two objects
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.partialDeepStrictEqual = function partialDeepStrictEqual(
actual,
expected,
message,
) {
emitExperimentalWarning('assert.partialDeepStrictEqual');
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}

if (!compareBranch(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'partialDeepStrictEqual',
stackStartFn: partialDeepStrictEqual,
});
}
};

class Comparison {
constructor(obj, keys, actual) {
for (const key of keys) {
1 change: 1 addition & 0 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
@@ -119,6 +119,7 @@ function lazyAssertObject(harness) {
'notDeepStrictEqual',
'notEqual',
'notStrictEqual',
'partialDeepStrictEqual',
'rejects',
'strictEqual',
'throws',
Loading