Skip to content

Commit

Permalink
fix(assert): add TypedArray, ArrayBuffer & DataView support in isEql
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanga-Ganapathy committed Mar 27, 2024
1 parent 7364825 commit d92933d
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-ears-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opentf/std": minor
---

Fixed isEql assertion.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,22 +245,24 @@ sortBy:
*Note: Here the Moderndash does not support passing object prop as string.

isEql:
┌───┬─────────────────────────────────────┬─────────┬───────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼───────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 927,921 │ 1077.676882954431 │ ±0.38% │ 92793 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 638,108 │ 1567.132684562446 │ ±1.42% │ 63813 │
│ 2 │ _.isEqual (Lodash) │ 142,592 │ 7013.003366058248 │ ±2.13% │ 14260 │
│ 3 │ R.equals (Ramda) │ 50,346 │ 19862.42184707051 │ ±1.83% │ 5035 │
│ 4 │ isEql │ 109,127 │ 9163.615962614227 │ ±1.28% │ 10913 │
└───┴─────────────────────────────────────┴─────────┴───────────────────┴────────┴─────────┘
┌───┬─────────────────────────────────────┬─────────┬────────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼────────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 950,686 │ 1051.871609041841 │ ±0.24% │ 95069 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 652,611 │ 1532.3058134904193 │ ±1.49% │ 65262 │
│ 2 │ dequal │ 120,791 │ 8278.7573675501 │ ±0.74% │ 12080 │
│ 3 │ _.isEqual (Lodash) │ 152,075 │ 6575.660376117521 │ ±2.02% │ 15208 │
│ 4 │ R.equals (Ramda) │ 51,496 │ 19418.976504855284 │ ±1.70% │ 5150 │
│ 5 │ isEql │ 104,355 │ 9582.655710998957 │ ±1.13% │ 10436 │
└───┴─────────────────────────────────────┴─────────┴────────────────────┴────────┴─────────┘

*Note:

- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & returns false for same invalid dates.
- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates.
- The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays.
- The lodash `isEqual` does not check `Map` ordering & object values in `Set`.
- The ramda `equals` does not check `Map` ordering & symbols.
- The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates.
```

### Running benchmarks
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@opentf/react-node-repl": "^0.9.0",
"@opentf/react-node-repl": "^0.12.0",
"next": "^14.1.1",
"next-sitemap": "^4.2.3",
"nextra": "^2.13.4",
Expand Down
35 changes: 24 additions & 11 deletions apps/docs/pages/Assert/isEql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,35 @@ import REPL from "../../components/REPL";
<Callout type="info">
The following type of values are supported:

- [Primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
- [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)

- Array
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)

- Plain Object
- [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)

- Map
- [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)

- Set
- [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)

- Date
- [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)

- Error
- [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) objects: but only plain objects (e.g. from object literals).

- RegExp
- [Primitive types](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)

- Typed Array
- [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)

- [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)

- [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
</Callout>

<Callout emoji="" type="info">
It handles the circular references.
</Callout>

<Callout type="info">
- It handles the circular references.
- Other types are checked by their references, Eg: Function.
Other types are checked by their references, Eg: Function.
</Callout>

**Related**
Expand Down Expand Up @@ -61,6 +68,12 @@ isEql(mapA, mapB); //=> false
const re = new RegExp('ab+c');
const re2 = new RegExp('ab+d');
isEql(re, re2); //=> false

const ta1 = new Uint8Array([42, 43]);
const ta2 = new Uint8Array([42, 43]);
const ta3 = new Uint8Array([42, 45]);
isEql(ta1, ta2); //=> true
isEql(ta2, ta3); //=> false
```

## Try
Expand Down
30 changes: 16 additions & 14 deletions apps/docs/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,24 @@ sortBy:
*Note: Here the Moderndash does not support passing object prop as string.

isEql:
┌───┬─────────────────────────────────────┬─────────┬───────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼───────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 927,921 │ 1077.676882954431 │ ±0.38% │ 92793 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 638,108 │ 1567.132684562446 │ ±1.42% │ 63813 │
│ 2 │ _.isEqual (Lodash) │ 142,592 │ 7013.003366058248 │ ±2.13% │ 14260 │
│ 3 │ R.equals (Ramda) │ 50,346 │ 19862.42184707051 │ ±1.83% │ 5035 │
│ 4 │ isEql │ 109,127 │ 9163.615962614227 │ ±1.28% │ 10913 │
└───┴─────────────────────────────────────┴─────────┴───────────────────┴────────┴─────────┘

*Note:

- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & returns false for same invalid dates.
- The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays.
┌───┬─────────────────────────────────────┬─────────┬────────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼────────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 950,686 │ 1051.871609041841 │ ±0.24% │ 95069 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 652,611 │ 1532.3058134904193 │ ±1.49% │ 65262 │
│ 2 │ dequal │ 120,791 │ 8278.7573675501 │ ±0.74% │ 12080 │
│ 3 │ _.isEqual (Lodash) │ 152,075 │ 6575.660376117521 │ ±2.02% │ 15208 │
│ 4 │ R.equals (Ramda) │ 51,496 │ 19418.976504855284 │ ±1.70% │ 5150 │
│ 5 │ isEql │ 104,355 │ 9582.655710998957 │ ±1.13% │ 10436 │
└───┴─────────────────────────────────────┴─────────┴────────────────────┴────────┴─────────┘

*Note:

- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates.
- The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays.
- The lodash `isEqual` does not check `Map` ordering & object values in `Set`.
- The ramda `equals` does not check `Map` ordering & symbols.
- The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates.
```

### Running benchmarks
Expand Down
10 changes: 7 additions & 3 deletions benchmark.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { isDeepStrictEqual } from "node:util";
import { Bench, hrtimeNow } from "tinybench";
import { clone, sortBy, isEql } from "./packages/utils/dist/index";
import { clone, sortBy, isEql } from "./packages/std/dist/index";
import _ from "lodash";
import * as R from "ramda";
import * as R2 from "remeda";
import { sort as mSort } from "moderndash";
import fastDeepEqual from "fast-deep-equal/es6";
import { dequal } from 'dequal';

async function cloneBench() {
console.log("clone:");
Expand Down Expand Up @@ -131,6 +132,9 @@ async function isEqlBench() {
.add("fastDeepEqual (fast-deep-equal/es6)", () => {
fastDeepEqual(obj, obj2);
})
.add("dequal", () => {
dequal(obj, obj2);
})
.add("_.isEqual (Lodash)", () => {
_.isEqual(obj, obj2);
})
Expand All @@ -147,6 +151,6 @@ async function isEqlBench() {
console.table(bench.table());
}

await cloneBench();
// await cloneBench();
// await sortByBench();
// await isEqlBench();
await isEqlBench();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"ramda": "^0.29.1",
"remeda": "^1.48.0",
"tinybench": "^2.6.0",
"turbo": "^1.12.2"
"turbo": "^1.12.2",
"dequal": "^2.0.3"
}
}
22 changes: 12 additions & 10 deletions packages/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,22 +245,24 @@ sortBy:
*Note: Here the Moderndash does not support passing object prop as string.

isEql:
┌───┬─────────────────────────────────────┬─────────┬───────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼───────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 927,921 │ 1077.676882954431 │ ±0.38% │ 92793 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 638,108 │ 1567.132684562446 │ ±1.42% │ 63813 │
│ 2 │ _.isEqual (Lodash) │ 142,592 │ 7013.003366058248 │ ±2.13% │ 14260 │
│ 3 │ R.equals (Ramda) │ 50,346 │ 19862.42184707051 │ ±1.83% │ 5035 │
│ 4 │ isEql │ 109,127 │ 9163.615962614227 │ ±1.28% │ 10913 │
└───┴─────────────────────────────────────┴─────────┴───────────────────┴────────┴─────────┘
┌───┬─────────────────────────────────────┬─────────┬────────────────────┬────────┬─────────┐
│ │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
├───┼─────────────────────────────────────┼─────────┼────────────────────┼────────┼─────────┤
│ 0 │ deepStrictEqual (Native) │ 950,686 │ 1051.871609041841 │ ±0.24% │ 95069 │
│ 1 │ fastDeepEqual (fast-deep-equal/es6) │ 652,611 │ 1532.3058134904193 │ ±1.49% │ 65262 │
│ 2 │ dequal │ 120,791 │ 8278.7573675501 │ ±0.74% │ 12080 │
│ 3 │ _.isEqual (Lodash) │ 152,075 │ 6575.660376117521 │ ±2.02% │ 15208 │
│ 4 │ R.equals (Ramda) │ 51,496 │ 19418.976504855284 │ ±1.70% │ 5150 │
│ 5 │ isEql │ 104,355 │ 9582.655710998957 │ ±1.13% │ 10436 │
└───┴─────────────────────────────────────┴─────────┴────────────────────┴────────┴─────────┘

*Note:

- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & returns false for same invalid dates.
- The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates.
- The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays.
- The lodash `isEqual` does not check `Map` ordering & object values in `Set`.
- The ramda `equals` does not check `Map` ordering & symbols.
- The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates.
```

### Running benchmarks
Expand Down
49 changes: 49 additions & 0 deletions packages/std/__tests__/assert/isEql.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,53 @@ describe('Object => isEql', () => {
obj2.self = obj2;
expect(isEql(obj1, obj2)).toBe(true);
});

test('TypedArray', () => {
const ta1 = new Uint8Array([42, 43]);
const ta2 = new Uint8Array([42, 43]);
const ta3 = new Uint8Array([42, 45]);
expect(isEql(ta1, ta2)).toBe(true);
expect(isEql(ta2, ta3)).toBe(false);

const obj1 = { ta: new Uint8Array(10) };
const obj2 = { ta: new Uint8Array(100) };
expect(isEql(obj1, obj2)).toBe(false);

const buffer = new ArrayBuffer(8);
const buffer2 = new ArrayBuffer(8);
const ta132 = new Uint32Array(buffer);
ta132[0] = 100;
const ta232 = new Uint32Array(buffer2, 4);
ta232[0] = 100;
expect(isEql(ta132, ta232)).toBe(false);
});

test('ArrayBuffer', () => {
const buffer = new ArrayBuffer(8);
const buffer2 = new ArrayBuffer(8);
const buffer3 = new ArrayBuffer(8);
const ta1 = new Uint8Array(buffer);
ta1[0] = 100;
const ta2 = new Uint8Array(buffer2);
ta2[0] = 100;
const ta3 = new Uint32Array(buffer3);
ta3[0] = 1000;
expect(isEql(buffer, buffer2)).toBe(true);
expect(isEql(buffer2, buffer3)).toBe(false);
});

test('DataView', () => {
const buf1 = new ArrayBuffer(8);
const buf2 = new ArrayBuffer(8);
const v1 = new DataView(buf1);
const v2 = new DataView(buf2);
v1.setInt8(0, 3);
v2.setInt8(0, 3);
expect(isEql(v1, v2)).toBe(true);
v1.setInt8(1, 5);
v2.setInt8(1, 6);
expect(isEql(v1, v2)).toBe(false);
v2.setInt8(1, 5);
expect(isEql(v1, v2)).toBe(true);
});
});
44 changes: 42 additions & 2 deletions packages/std/src/assert/isEql.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { IterableObj } from '../object/merge';
import size from '../object/size';
import isArr from '../types/isArr';
import isArrBuf from '../types/isArrBuf';
import isDataView from '../types/isDataView';
import isErr from '../types/isErr';
import isMap from '../types/isMap';
import isObj from '../types/isObj';
import isPureObj from '../types/isPureObj';
import isRegEx from '../types/isRegEx';
import isSet from '../types/isSet';
import isTypedArr from '../types/isTypedArr';
Expand All @@ -24,14 +27,17 @@ function isEqlVal(
objRefSet1: WeakSet<WeakKey>,
objRefSet2: WeakSet<WeakKey>
): boolean {
// Handles primitives
if (Object.is(val1, val2)) {
return true;
}

// For circular refs
if (objRefSet1.has(val1 as WeakKey) && objRefSet2.has(val2 as WeakKey)) {
return true;
}

// Check both has same type string tag
if (
Object.prototype.toString.call(val1) !==
Object.prototype.toString.call(val2)
Expand All @@ -43,15 +49,19 @@ function isEqlVal(
return false;
}

if (isPureObj(val1) && isPureObj(val2)) {
objRefSet1.add(val1 as WeakKey);
objRefSet2.add(val2 as WeakKey);
}

if (isArr(val1)) {
// For sparse arrays
if (size(Object.keys(val1)) !== size(Object.keys(val2 as IterableObj))) {
return false;
}
}

if (isArr(val1) || isObj(val1) || isTypedArr(val1)) {
objRefSet1.add(val1);
objRefSet2.add(val2 as WeakKey);
for (const key of Object.keys(val1)) {
if (
!isEqlVal(
Expand Down Expand Up @@ -137,6 +147,36 @@ function isEqlVal(
}
}

if (isArrBuf(val1)) {
const ta1 = new Uint8Array(val1);
const ta2 = new Uint8Array(val2 as ArrayBuffer);

for (const key of ta1.keys()) {
if (!isEqlVal(ta1[key], ta2[key], objRefSet1, objRefSet2)) {
return false;
}
}

return true;
}

if (isDataView(val1)) {
for (let i = 0; i < val1.byteLength; i++) {
if (
!isEqlVal(
val1.getUint8(i),
(val2 as DataView).getUint8(i),
objRefSet1,
objRefSet2
)
) {
return false;
}
}

return true;
}

return false;
}

Expand Down

0 comments on commit d92933d

Please sign in to comment.