Skip to content

Commit 4c13d8e

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

File tree

3 files changed

+353
-79
lines changed

3 files changed

+353
-79
lines changed

lib/assert.js

Lines changed: 179 additions & 76 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy