◐ Shell
clean mode source ↗

assert: make partialDeepStrictEqual work with ArrayBuffers · nodejs/node@cfbdff7

@@ -21,35 +21,44 @@

2121

'use strict';

22222323

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;

54635564

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

}

370383371384

const 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

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+

}

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

}

408567409568

for (const type of typesToCallDeepStrictEqualWith) {

@@ -415,68 +574,12 @@ function compareBranch(

415574416575

// 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

}

440579441580

// 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

}

481584482585

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