assert: make partialDeepStrictEqual work with ArrayBuffers · nodejs/node@cfbdff7
@@ -21,35 +21,44 @@
2121'use strict';
22222323const {
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;
54635564const {
@@ -65,6 +74,8 @@ const AssertionError = require('internal/assert/assertion_error');
6574const { inspect } = require('internal/util/inspect');
6675const { Buffer } = require('buffer');
6776const {
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');
7790const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
7891const { innerOk } = require('internal/assert/utils');
@@ -369,9 +382,161 @@ function isSpecial(obj) {
369382}
370383371384const typesToCallDeepStrictEqualWith = [
372-isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
385+isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer,
373386];
374387388+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
390555if (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+}
397558398-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}
408567409568for (const type of typesToCallDeepStrictEqualWith) {
@@ -415,68 +574,12 @@ function compareBranch(
415574416575// Check for Set object equality
417576if (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}
440579441580// Check if expected array is a subset of actual array
442581if (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}
481584482585// Comparison done when at least one of the values is not an object