Skip to content

Commit dbfcbe3

Browse files
authored
assert: make partialDeepStrictEqual work with ArrayBuffers
Fixes: #56097 PR-URL: #56098 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 4f51d46 commit dbfcbe3

File tree

3 files changed

+353
-79
lines changed

3 files changed

+353
-79
lines changed

lib/assert.js

+179-76
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,44 @@
2121
'use strict';
2222

2323
const {
24+
ArrayBufferIsView,
25+
ArrayBufferPrototypeGetByteLength,
2426
ArrayFrom,
2527
ArrayIsArray,
2628
ArrayPrototypeIndexOf,
2729
ArrayPrototypeJoin,
2830
ArrayPrototypePush,
2931
ArrayPrototypeSlice,
32+
DataViewPrototypeGetBuffer,
33+
DataViewPrototypeGetByteLength,
34+
DataViewPrototypeGetByteOffset,
3035
Error,
3136
FunctionPrototypeCall,
32-
MapPrototypeDelete,
3337
MapPrototypeGet,
38+
MapPrototypeGetSize,
3439
MapPrototypeHas,
35-
MapPrototypeSet,
3640
NumberIsNaN,
3741
ObjectAssign,
3842
ObjectIs,
3943
ObjectKeys,
4044
ObjectPrototypeIsPrototypeOf,
45+
ObjectPrototypeToString,
4146
ReflectApply,
4247
ReflectHas,
4348
ReflectOwnKeys,
4449
RegExpPrototypeExec,
50+
SafeArrayIterator,
4551
SafeMap,
4652
SafeSet,
4753
SafeWeakSet,
54+
SetPrototypeGetSize,
4855
String,
4956
StringPrototypeIndexOf,
5057
StringPrototypeSlice,
5158
StringPrototypeSplit,
5259
SymbolIterator,
60+
TypedArrayPrototypeGetLength,
61+
Uint8Array,
5362
} = primordials;
5463

5564
const {
@@ -65,6 +74,8 @@ const AssertionError = require('internal/assert/assertion_error');
6574
const { inspect } = require('internal/util/inspect');
6675
const { Buffer } = require('buffer');
6776
const {
77+
isArrayBuffer,
78+
isDataView,
6879
isKeyObject,
6980
isPromise,
7081
isRegExp,
@@ -73,6 +84,8 @@ const {
7384
isDate,
7485
isWeakSet,
7586
isWeakMap,
87+
isSharedArrayBuffer,
88+
isAnyArrayBuffer,
7689
} = require('internal/util/types');
7790
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
7891
const { innerOk } = require('internal/assert/utils');
@@ -369,9 +382,161 @@ function isSpecial(obj) {
369382
}
370383

371384
const typesToCallDeepStrictEqualWith = [
372-
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
385+
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer,
373386
];
374387

388+
function compareMaps(actual, expected, comparedObjects) {
389+
if (MapPrototypeGetSize(actual) !== MapPrototypeGetSize(expected)) {
390+
return false;
391+
}
392+
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
393+
394+
comparedObjects ??= new SafeWeakSet();
395+
396+
for (const { 0: key, 1: val } of safeIterator) {
397+
if (!MapPrototypeHas(expected, key)) {
398+
return false;
399+
}
400+
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
401+
return false;
402+
}
403+
}
404+
return true;
405+
}
406+
407+
function partiallyCompareArrayBuffersOrViews(actual, expected) {
408+
let actualView, expectedView, expectedViewLength;
409+
410+
if (!ArrayBufferIsView(actual)) {
411+
let actualViewLength;
412+
413+
if (isArrayBuffer(actual) && isArrayBuffer(expected)) {
414+
actualViewLength = ArrayBufferPrototypeGetByteLength(actual);
415+
expectedViewLength = ArrayBufferPrototypeGetByteLength(expected);
416+
} else if (isSharedArrayBuffer(actual) && isSharedArrayBuffer(expected)) {
417+
actualViewLength = actual.byteLength;
418+
expectedViewLength = expected.byteLength;
419+
} else {
420+
// Cannot compare ArrayBuffers with SharedArrayBuffers
421+
return false;
422+
}
423+
424+
if (expectedViewLength > actualViewLength) {
425+
return false;
426+
}
427+
actualView = new Uint8Array(actual);
428+
expectedView = new Uint8Array(expected);
429+
430+
} else if (isDataView(actual)) {
431+
if (!isDataView(expected)) {
432+
return false;
433+
}
434+
const actualByteLength = DataViewPrototypeGetByteLength(actual);
435+
expectedViewLength = DataViewPrototypeGetByteLength(expected);
436+
if (expectedViewLength > actualByteLength) {
437+
return false;
438+
}
439+
440+
actualView = new Uint8Array(
441+
DataViewPrototypeGetBuffer(actual),
442+
DataViewPrototypeGetByteOffset(actual),
443+
actualByteLength,
444+
);
445+
expectedView = new Uint8Array(
446+
DataViewPrototypeGetBuffer(expected),
447+
DataViewPrototypeGetByteOffset(expected),
448+
expectedViewLength,
449+
);
450+
} else {
451+
if (ObjectPrototypeToString(actual) !== ObjectPrototypeToString(expected)) {
452+
return false;
453+
}
454+
actualView = actual;
455+
expectedView = expected;
456+
expectedViewLength = TypedArrayPrototypeGetLength(expected);
457+
458+
if (expectedViewLength > TypedArrayPrototypeGetLength(actual)) {
459+
return false;
460+
}
461+
}
462+
463+
for (let i = 0; i < expectedViewLength; i++) {
464+
if (actualView[i] !== expectedView[i]) {
465+
return false;
466+
}
467+
}
468+
469+
return true;
470+
}
471+
472+
function partiallyCompareSets(actual, expected, comparedObjects) {
473+
if (SetPrototypeGetSize(expected) > SetPrototypeGetSize(actual)) {
474+
return false; // `expected` can't be a subset if it has more elements
475+
}
476+
477+
if (isDeepEqual === undefined) lazyLoadComparison();
478+
479+
const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
480+
const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
481+
const usedIndices = new SafeSet();
482+
483+
expectedIteration: for (const expectedItem of expectedIterator) {
484+
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
485+
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
486+
usedIndices.add(actualIdx);
487+
continue expectedIteration;
488+
}
489+
}
490+
return false;
491+
}
492+
493+
return true;
494+
}
495+
496+
function partiallyCompareArrays(actual, expected, comparedObjects) {
497+
if (expected.length > actual.length) {
498+
return false;
499+
}
500+
501+
if (isDeepEqual === undefined) lazyLoadComparison();
502+
503+
// Create a map to count occurrences of each element in the expected array
504+
const expectedCounts = new SafeMap();
505+
for (const expectedItem of expected) {
506+
let found = false;
507+
for (const { 0: key, 1: count } of expectedCounts) {
508+
if (isDeepStrictEqual(key, expectedItem)) {
509+
expectedCounts.set(key, count + 1);
510+
found = true;
511+
break;
512+
}
513+
}
514+
if (!found) {
515+
expectedCounts.set(expectedItem, 1);
516+
}
517+
}
518+
519+
const safeActual = new SafeArrayIterator(actual);
520+
521+
// Create a map to count occurrences of relevant elements in the actual array
522+
for (const actualItem of safeActual) {
523+
for (const { 0: key, 1: count } of expectedCounts) {
524+
if (isDeepStrictEqual(key, actualItem)) {
525+
if (count === 1) {
526+
expectedCounts.delete(key);
527+
} else {
528+
expectedCounts.set(key, count - 1);
529+
}
530+
break;
531+
}
532+
}
533+
}
534+
535+
const { size } = expectedCounts;
536+
expectedCounts.clear();
537+
return size === 0;
538+
}
539+
375540
/**
376541
* Compares two objects or values recursively to check if they are equal.
377542
* @param {any} actual - The actual value to compare.
@@ -388,22 +553,16 @@ function compareBranch(
388553
) {
389554
// Check for Map object equality
390555
if (isMap(actual) && isMap(expected)) {
391-
if (actual.size !== expected.size) {
392-
return false;
393-
}
394-
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
395-
396-
comparedObjects ??= new SafeWeakSet();
556+
return compareMaps(actual, expected, comparedObjects);
557+
}
397558

398-
for (const { 0: key, 1: val } of safeIterator) {
399-
if (!MapPrototypeHas(expected, key)) {
400-
return false;
401-
}
402-
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
403-
return false;
404-
}
405-
}
406-
return true;
559+
if (
560+
ArrayBufferIsView(actual) ||
561+
isAnyArrayBuffer(actual) ||
562+
ArrayBufferIsView(expected) ||
563+
isAnyArrayBuffer(expected)
564+
) {
565+
return partiallyCompareArrayBuffersOrViews(actual, expected);
407566
}
408567

409568
for (const type of typesToCallDeepStrictEqualWith) {
@@ -415,68 +574,12 @@ function compareBranch(
415574

416575
// Check for Set object equality
417576
if (isSet(actual) && isSet(expected)) {
418-
if (expected.size > actual.size) {
419-
return false; // `expected` can't be a subset if it has more elements
420-
}
421-
422-
if (isDeepEqual === undefined) lazyLoadComparison();
423-
424-
const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
425-
const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
426-
const usedIndices = new SafeSet();
427-
428-
expectedIteration: for (const expectedItem of expectedIterator) {
429-
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
430-
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
431-
usedIndices.add(actualIdx);
432-
continue expectedIteration;
433-
}
434-
}
435-
return false;
436-
}
437-
438-
return true;
577+
return partiallyCompareSets(actual, expected, comparedObjects);
439578
}
440579

441580
// Check if expected array is a subset of actual array
442581
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
443-
if (expected.length > actual.length) {
444-
return false;
445-
}
446-
447-
if (isDeepEqual === undefined) lazyLoadComparison();
448-
449-
// Create a map to count occurrences of each element in the expected array
450-
const expectedCounts = new SafeMap();
451-
for (const expectedItem of expected) {
452-
let found = false;
453-
for (const { 0: key, 1: count } of expectedCounts) {
454-
if (isDeepStrictEqual(key, expectedItem)) {
455-
MapPrototypeSet(expectedCounts, key, count + 1);
456-
found = true;
457-
break;
458-
}
459-
}
460-
if (!found) {
461-
MapPrototypeSet(expectedCounts, expectedItem, 1);
462-
}
463-
}
464-
465-
// Create a map to count occurrences of relevant elements in the actual array
466-
for (const actualItem of actual) {
467-
for (const { 0: key, 1: count } of expectedCounts) {
468-
if (isDeepStrictEqual(key, actualItem)) {
469-
if (count === 1) {
470-
MapPrototypeDelete(expectedCounts, key);
471-
} else {
472-
MapPrototypeSet(expectedCounts, key, count - 1);
473-
}
474-
break;
475-
}
476-
}
477-
}
478-
479-
return !expectedCounts.size;
582+
return partiallyCompareArrays(actual, expected, comparedObjects);
480583
}
481584

482585
// Comparison done when at least one of the values is not an object

0 commit comments

Comments
 (0)