denoland / std

The Deno Standard Library
https://jsr.io/@std
MIT License
3.18k stars 623 forks source link

assertEquals doesn't work for large arrays #5942

Open sigmaSd opened 1 month ago

sigmaSd commented 1 month ago
import { assertEquals } from "jsr:@std/assert@1";
const a = Deno.readFileSync(Deno.execPath());
assertEquals(a, new Uint8Array());

deno -R a.ts

error: Uncaught (in promise) RangeError: Invalid array length
      if (Object.keys(a).length !== Object.keys(b).length) {
kt3k commented 1 month ago

Does the utility like this make more sense to such cases? https://github.com/fent/node-stream-equal

sigmaSd commented 1 month ago

I'm not sure it feels weird to me that an assert equal doesn't work after a certain size, also the error is not helpful

lionel-rowe commented 1 month ago

It'd probably make sense to have a separate path for iterables and/or array-likes.


type Iter<T> = { [Symbol.iterator](): Iterator<T> };
type TypedArrayLike = Iter<number | bigint>;
function isTypedArray(x: unknown): x is TypedArrayLike {
    return ArrayBuffer.isView(x) && Symbol.iterator in x;
}

function iterEqual<T>(a: Iter<T>, b: Iter<T>, eq: (a: T, b: T) => boolean): boolean {
    if (a === b) return true;
    if (a.constructor !== b.constructor) return false;
    // @ts-expect-error - if no `length` prop we just check both undefined
    if (a.length !== b.length) return false;

    const aIter = a[Symbol.iterator]();
    const bIter = b[Symbol.iterator]();

    while (true) {
        const aNext = aIter.next();
        const bNext = bIter.next();
        if (aNext.done !== bNext.done) return false;
        if (!eq(aNext.value, bNext.value)) return false;
        if (aNext.done) return true;
    }
}

function typedArrayEqual(a: TypedArrayLike, b: TypedArrayLike): boolean {
    return iterEqual(a, b, (a, b) => a === b);
}

const bytes = Deno.readFileSync(Deno.execPath());
const diffAtStart = bytes.slice();
++diffAtStart[0];
const diffAtEnd = bytes.slice();
++diffAtEnd[diffAtEnd.length - 1];

console.time();
// true
console.log(typedArrayEqual(bytes, bytes));
// 0ms
console.timeEnd();

console.time();
// true
console.log(typedArrayEqual(bytes, bytes.slice()));
// 3562ms
console.timeEnd();

console.time();
// false
console.log(typedArrayEqual(bytes, diffAtStart));
// 0ms
console.timeEnd();

console.time();
// false
console.log(typedArrayEqual(bytes, diffAtEnd));
// 3381ms
console.timeEnd();

console.time();
// false
console.log(typedArrayEqual(bytes, new Uint8Array(0)));
// 0ms
console.timeEnd();