Skip to content

Commit c912e20

Browse files
committed
assert: make partialDeepStrictEqual work with ArrayBuffers
Fixes: #56097
1 parent 3f9c6c0 commit c912e20

File tree

3 files changed

+231
-77
lines changed

3 files changed

+231
-77
lines changed

lib/assert.js

Lines changed: 161 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@
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,
3237
MapPrototypeDelete,
@@ -38,6 +43,7 @@ const {
3843
ObjectIs,
3944
ObjectKeys,
4045
ObjectPrototypeIsPrototypeOf,
46+
ObjectPrototypeToString,
4147
ReflectApply,
4248
ReflectHas,
4349
ReflectOwnKeys,
@@ -50,6 +56,8 @@ const {
5056
StringPrototypeSlice,
5157
StringPrototypeSplit,
5258
SymbolIterator,
59+
TypedArrayPrototypeGetLength,
60+
Uint8Array,
5361
} = primordials;
5462

5563
const {
@@ -65,6 +73,8 @@ const AssertionError = require('internal/assert/assertion_error');
6573
const { inspect } = require('internal/util/inspect');
6674
const { Buffer } = require('buffer');
6775
const {
76+
isArrayBuffer,
77+
isDataView,
6878
isKeyObject,
6979
isPromise,
7080
isRegExp,
@@ -73,6 +83,7 @@ const {
7383
isDate,
7484
isWeakSet,
7585
isWeakMap,
86+
isSharedArrayBuffer,
7687
} = require('internal/util/types');
7788
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
7889
const { innerOk } = require('internal/assert/utils');
@@ -369,9 +380,149 @@ function isSpecial(obj) {
369380
}
370381

371382
const typesToCallDeepStrictEqualWith = [
372-
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
383+
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer,
373384
];
374385

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

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;
545+
if (
546+
(ArrayBufferIsView(actual) && ArrayBufferIsView(expected)) ||
547+
(isArrayBuffer(actual) && isArrayBuffer(expected))
548+
) {
549+
return compareArrayBuffers(actual, expected);
407550
}
408551

409552
for (const type of typesToCallDeepStrictEqualWith) {
@@ -415,68 +558,12 @@ function compareBranch(
415558

416559
// Check for Set object equality
417560
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;
561+
return compareSets(actual, expected, comparedObjects);
439562
}
440563

441564
// Check if expected array is a subset of actual array
442565
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;
566+
return compareArrays(actual, expected, comparedObjects);
480567
}
481568

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

0 commit comments

Comments
 (0)