diff options
Diffstat (limited to 'ext/node/polyfills/_util/std_asserts.ts')
-rw-r--r-- | ext/node/polyfills/_util/std_asserts.ts | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/ext/node/polyfills/_util/std_asserts.ts b/ext/node/polyfills/_util/std_asserts.ts new file mode 100644 index 000000000..8c4c80078 --- /dev/null +++ b/ext/node/polyfills/_util/std_asserts.ts @@ -0,0 +1,293 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// vendored from std/testing/asserts.ts + +import { red } from "internal:deno_node/polyfills/_util/std_fmt_colors.ts"; +import { + buildMessage, + diff, + diffstr, +} from "internal:deno_node/polyfills/_util/std_testing_diff.ts"; + +/** Converts the input into a string. Objects, Sets and Maps are sorted so as to + * make tests less flaky */ +export function format(v: unknown): string { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + return typeof Deno?.inspect === "function" + ? Deno.inspect(v, { + depth: Infinity, + sorted: true, + trailingComma: true, + compact: false, + iterableLimit: Infinity, + // getters should be true in assertEquals. + getters: true, + }) + : `"${String(v).replace(/(?=["\\])/g, "\\")}"`; +} + +const CAN_NOT_DISPLAY = "[Cannot display]"; + +export class AssertionError extends Error { + override name = "AssertionError"; + constructor(message: string) { + super(message); + } +} + +function isKeyedCollection(x: unknown): x is Set<unknown> { + return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>)); +} + +/** Deep equality comparison used in assertions */ +export function equal(c: unknown, d: unknown): boolean { + const seen = new Map(); + return (function compare(a: unknown, b: unknown): boolean { + // Have to render RegExp & Date for string comparison + // unless it's mistreated as object + if ( + a && + b && + ((a instanceof RegExp && b instanceof RegExp) || + (a instanceof URL && b instanceof URL)) + ) { + return String(a) === String(b); + } + if (a instanceof Date && b instanceof Date) { + const aTime = a.getTime(); + const bTime = b.getTime(); + // Check for NaN equality manually since NaN is not + // equal to itself. + if (Number.isNaN(aTime) && Number.isNaN(bTime)) { + return true; + } + return aTime === bTime; + } + if (typeof a === "number" && typeof b === "number") { + return Number.isNaN(a) && Number.isNaN(b) || a === b; + } + if (Object.is(a, b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (a && b && !constructorsEqual(a, b)) { + return false; + } + if (a instanceof WeakMap || b instanceof WeakMap) { + if (!(a instanceof WeakMap && b instanceof WeakMap)) return false; + throw new TypeError("cannot compare WeakMap instances"); + } + if (a instanceof WeakSet || b instanceof WeakSet) { + if (!(a instanceof WeakSet && b instanceof WeakSet)) return false; + throw new TypeError("cannot compare WeakSet instances"); + } + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a || {}).length !== Object.keys(b || {}).length) { + return false; + } + seen.set(a, b); + if (isKeyedCollection(a) && isKeyedCollection(b)) { + if (a.size !== b.size) { + return false; + } + + let unmatchedEntries = a.size; + + for (const [aKey, aValue] of a.entries()) { + for (const [bKey, bValue] of b.entries()) { + /* Given that Map keys can be references, we need + * to ensure that they are also deeply equal */ + if ( + (aKey === aValue && bKey === bValue && compare(aKey, bKey)) || + (compare(aKey, bKey) && compare(aValue, bValue)) + ) { + unmatchedEntries--; + break; + } + } + } + + return unmatchedEntries === 0; + } + const merged = { ...a, ...b }; + for ( + const key of [ + ...Object.getOwnPropertyNames(merged), + ...Object.getOwnPropertySymbols(merged), + ] + ) { + type Key = keyof typeof merged; + if (!compare(a && a[key as Key], b && b[key as Key])) { + return false; + } + if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) { + return false; + } + } + if (a instanceof WeakRef || b instanceof WeakRef) { + if (!(a instanceof WeakRef && b instanceof WeakRef)) return false; + return compare(a.deref(), b.deref()); + } + return true; + } + return false; + })(c, d); +} + +// deno-lint-ignore ban-types +function constructorsEqual(a: object, b: object) { + return a.constructor === b.constructor || + a.constructor === Object && !b.constructor || + !a.constructor && b.constructor === Object; +} + +/** Make an assertion, error will be thrown if `expr` does not have truthy value. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new AssertionError(msg); + } +} + +/** Make an assertion that `actual` and `expected` are equal, deeply. If not + * deeply equal, then throw. */ +export function assertEquals<T>(actual: T, expected: T, msg?: string) { + if (equal(actual, expected)) { + return; + } + let message = ""; + const actualString = format(actual); + const expectedString = format(expected); + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not equal:\n${diffMsg}`; + } catch { + message = `\n${red(red(CAN_NOT_DISPLAY))} + \n\n`; + } + if (msg) { + message = msg; + } + throw new AssertionError(message); +} + +/** Make an assertion that `actual` and `expected` are not equal, deeply. + * If not then throw. */ +export function assertNotEquals<T>(actual: T, expected: T, msg?: string) { + if (!equal(actual, expected)) { + return; + } + let actualString: string; + let expectedString: string; + try { + actualString = String(actual); + } catch { + actualString = "[Cannot display]"; + } + try { + expectedString = String(expected); + } catch { + expectedString = "[Cannot display]"; + } + if (!msg) { + msg = `actual: ${actualString} expected not to be: ${expectedString}`; + } + throw new AssertionError(msg); +} + +/** Make an assertion that `actual` and `expected` are strictly equal. If + * not then throw. */ +export function assertStrictEquals<T>( + actual: unknown, + expected: T, + msg?: string, +): asserts actual is T { + if (Object.is(actual, expected)) { + return; + } + + let message: string; + + if (msg) { + message = msg; + } else { + const actualString = format(actual); + const expectedString = format(expected); + + if (actualString === expectedString) { + const withOffset = actualString + .split("\n") + .map((l) => ` ${l}`) + .join("\n"); + message = + `Values have the same structure but are not reference-equal:\n\n${ + red(withOffset) + }\n`; + } else { + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not strictly equal:\n${diffMsg}`; + } catch { + message = `\n${CAN_NOT_DISPLAY} + \n\n`; + } + } + } + + throw new AssertionError(message); +} + +/** Make an assertion that `actual` and `expected` are not strictly equal. + * If the values are strictly equal then throw. */ +export function assertNotStrictEquals<T>( + actual: T, + expected: T, + msg?: string, +) { + if (!Object.is(actual, expected)) { + return; + } + + throw new AssertionError( + msg ?? `Expected "actual" to be strictly unequal to: ${format(actual)}\n`, + ); +} + +/** Make an assertion that `actual` match RegExp `expected`. If not + * then throw. */ +export function assertMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (!expected.test(actual)) { + if (!msg) { + msg = `actual: "${actual}" expected to match: "${expected}"`; + } + throw new AssertionError(msg); + } +} + +/** Make an assertion that `actual` not match RegExp `expected`. If match + * then throw. */ +export function assertNotMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (expected.test(actual)) { + if (!msg) { + msg = `actual: "${actual}" expected to not match: "${expected}"`; + } + throw new AssertionError(msg); + } +} |