From d20127898b5a882f6a5361e43c464f0516a944e6 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 10 Dec 2024 19:54:37 -0800 Subject: [PATCH 1/5] test(node): get `test-assert.js` working --- src/bun.js/bindings/ErrorCode.ts | 3 + src/codegen/bundle-modules.ts | 1 + src/js/internal/assert/assertion_error.ts | 426 ++++ src/js/internal/assert/calltracker.ts | 145 ++ src/js/internal/assert/myers_diff.ts | 172 ++ src/js/internal/assert/utils.ts | 294 +++ src/js/internal/primordials.js | 25 +- src/js/internal/util.ts | 7 + src/js/internal/util/colors.ts | 54 + src/js/node/assert.ts | 2175 +++++++++------------ test/js/node/harness.ts | 120 ++ test/js/node/test/common/index.js | 7 +- test/js/node/test/parallel/test-assert.js | 1602 +++++++++++++++ 13 files changed, 3802 insertions(+), 1229 deletions(-) create mode 100644 src/js/internal/assert/assertion_error.ts create mode 100644 src/js/internal/assert/calltracker.ts create mode 100644 src/js/internal/assert/myers_diff.ts create mode 100644 src/js/internal/assert/utils.ts create mode 100644 src/js/internal/util.ts create mode 100644 src/js/internal/util/colors.ts create mode 100644 test/js/node/test/parallel/test-assert.js diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index a2184f7215f807..41248d576ab591 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -17,9 +17,11 @@ export default [ ["ERR_INVALID_ARG_VALUE", TypeError, "TypeError"], ["ERR_INVALID_PROTOCOL", TypeError, "TypeError"], ["ERR_INVALID_THIS", TypeError, "TypeError"], + ["ERR_INVALID_RETURN_VALUE", TypeError, "TypeError"], ["ERR_IPC_CHANNEL_CLOSED", Error, "Error"], ["ERR_IPC_DISCONNECTED", Error, "Error"], ["ERR_MISSING_ARGS", TypeError, "TypeError"], + ["ERR_AMBIGUOUS_ARGUMENT", TypeError, "TypeError"], ["ERR_OUT_OF_RANGE", RangeError, "RangeError"], ["ERR_PARSE_ARGS_INVALID_OPTION_VALUE", TypeError, "TypeError"], ["ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL", TypeError, "TypeError"], @@ -46,6 +48,7 @@ export default [ ["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"], ["ERR_SOCKET_BAD_PORT", RangeError, "RangeError"], ["ERR_STREAM_RELEASE_LOCK", Error, "AbortError"], + ["ERR_UNAVAILABLE_DURING_EXIT", Error, "Error"], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"], diff --git a/src/codegen/bundle-modules.ts b/src/codegen/bundle-modules.ts index b98828c5e5b8b0..933297e5ff3c1c 100644 --- a/src/codegen/bundle-modules.ts +++ b/src/codegen/bundle-modules.ts @@ -449,6 +449,7 @@ writeIfNotChanged( (() => { let dts = ` // GENERATED TEMP FILE - DO NOT EDIT +// generated by ${import.meta.path} `; for (let i = 0; i < ErrorCode.length; i++) { diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts new file mode 100644 index 00000000000000..150c20c021b3d8 --- /dev/null +++ b/src/js/internal/assert/assertion_error.ts @@ -0,0 +1,426 @@ +"use strict"; + +const { + ArrayPrototypeJoin, + ArrayPrototypePop, + ArrayPrototypeSlice, + Error, + ErrorCaptureStackTrace, + ObjectAssign, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + String, + StringPrototypeRepeat, + StringPrototypeSlice, + StringPrototypeSplit, +} = require("internal/primordials");; + +const { isError } = require("internal/util"); +const prims = [ + ArrayPrototypeJoin, + ArrayPrototypePop, + ArrayPrototypeSlice, + Error, + ErrorCaptureStackTrace, + ObjectAssign, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + String, + StringPrototypeRepeat, + StringPrototypeSlice, + StringPrototypeSplit, +] +for (const p of prims) { + if (typeof p !== "function") { + throw new Error(`Expected ${p} to be a function`); + } +} + +const { inspect } = require("internal/util/inspect"); +const colors = require("internal/util/colors"); +const { validateObject } = require("internal/validators"); +// const { isErrorStackTraceLimitWritable } = require('internal/errors'); +const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require("internal/assert/myers_diff"); + +const kReadableOperator = { + deepStrictEqual: "Expected values to be strictly deep-equal:", + strictEqual: "Expected values to be strictly equal:", + strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', + deepEqual: "Expected values to be loosely deep-equal:", + notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', + notStrictEqual: 'Expected "actual" to be strictly unequal to:', + notStrictEqualObject: 'Expected "actual" not to be reference-equal to "expected":', + notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:', + notIdentical: "Values have same structure but are not reference-equal:", + notDeepEqualUnequal: "Expected values not to be loosely deep-equal:", +}; + +const kMaxShortStringLength = 12; +const kMaxLongStringLength = 512; + +function copyError(source) { + const target = ObjectAssign({ __proto__: ObjectGetPrototypeOf(source) }, source); + ObjectDefineProperty(target, "message", { + __proto__: null, + value: source.message, + }); + if (ObjectPrototypeHasOwnProperty(source, "cause")) { + let { cause } = source; + + if (isError(cause)) { + cause = copyError(cause); + } + + ObjectDefineProperty(target, "cause", { __proto__: null, value: cause }); + } + return target; +} + +function inspectValue(val) { + // The util.inspect default values could be changed. This makes sure the + // error messages contain the necessary information nevertheless. + return inspect(val, { + compact: false, + customInspect: false, + depth: 1000, + maxArrayLength: Infinity, + // Assert compares only enumerable properties (with a few exceptions). + showHidden: false, + // Assert does not detect proxies currently. + showProxy: false, + sorted: true, + // Inspect getters as we also check them when comparing entries. + getters: true, + }); +} + +function getErrorMessage(operator, message) { + return message || kReadableOperator[operator]; +} + +function checkOperator(actual, expected, operator) { + // In case both values are objects or functions explicitly mark them as not + // reference equal for the `strictEqual` operator. + if ( + operator === "strictEqual" && + ((typeof actual === "object" && actual !== null && typeof expected === "object" && expected !== null) || + (typeof actual === "function" && typeof expected === "function")) + ) { + operator = "strictEqualObject"; + } + + return operator; +} + +function getColoredMyersDiff(actual, expected) { + const header = `${colors.green}actual${colors.white} ${colors.red}expected${colors.white}`; + const skipped = false; + + const diff = myersDiff(StringPrototypeSplit(actual, ""), StringPrototypeSplit(expected, "")); + let message = printSimpleMyersDiff(diff); + + if (skipped) { + message += "..."; + } + + return { message, header, skipped }; +} + +function getStackedDiff(actual, expected) { + const isStringComparison = typeof actual === "string" && typeof expected === "string"; + + let message = `\n${colors.green}+${colors.white} ${actual}\n${colors.red}- ${colors.white}${expected}`; + const stringsLen = actual.length + expected.length; + const maxTerminalLength = process.stderr.isTTY ? process.stderr.columns : 80; + const showIndicator = isStringComparison && stringsLen <= maxTerminalLength; + + if (showIndicator) { + let indicatorIdx = -1; + + for (let i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) { + // Skip the indicator for the first 2 characters because the diff is immediately apparent + // It is 3 instead of 2 to account for the quotes + if (i >= 3) { + indicatorIdx = i; + } + break; + } + } + + if (indicatorIdx !== -1) { + message += `\n${StringPrototypeRepeat(" ", indicatorIdx + 2)}^`; + } + } + + return { message }; +} + +function getSimpleDiff(originalActual, actual, originalExpected, expected) { + let stringsLen = actual.length + expected.length; + // Accounting for the quotes wrapping strings + if (typeof originalActual === "string") { + stringsLen -= 2; + } + if (typeof originalExpected === "string") { + stringsLen -= 2; + } + if (stringsLen <= kMaxShortStringLength && (originalActual !== 0 || originalExpected !== 0)) { + return { message: `${actual} !== ${expected}`, header: "" }; + } + + const isStringComparison = typeof originalActual === "string" && typeof originalExpected === "string"; + // colored myers diff + if (isStringComparison && colors.hasColors) { + return getColoredMyersDiff(actual, expected); + } + + return getStackedDiff(actual, expected); +} + +function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) { + if (inspectedActual.length > 1 || inspectedExpected.length > 1) { + return false; + } + + return typeof actual !== "object" || actual === null || typeof expected !== "object" || expected === null; +} + +function createErrDiff(actual, expected, operator, customMessage) { + operator = checkOperator(actual, expected, operator); + + let skipped = false; + let message = ""; + const inspectedActual = inspectValue(actual); + const inspectedExpected = inspectValue(expected); + const inspectedSplitActual = StringPrototypeSplit(inspectedActual, "\n"); + const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, "\n"); + const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected); + let header = `${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`; + + if (showSimpleDiff) { + const simpleDiff = getSimpleDiff(actual, inspectedSplitActual[0], expected, inspectedSplitExpected[0]); + message = simpleDiff.message; + if (typeof simpleDiff.header !== "undefined") { + header = simpleDiff.header; + } + if (simpleDiff.skipped) { + skipped = true; + } + } else if (inspectedActual === inspectedExpected) { + // Handles the case where the objects are structurally the same but different references + operator = "notIdentical"; + if (inspectedSplitActual.length > 50) { + message = `${ArrayPrototypeJoin(ArrayPrototypeSlice(inspectedSplitActual, 0, 50), "\n")}\n...}`; + skipped = true; + } else { + message = ArrayPrototypeJoin(inspectedSplitActual, "\n"); + } + header = ""; + } else { + const checkCommaDisparity = actual != null && typeof actual === "object"; + const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity); + + const myersDiffMessage = printMyersDiff(diff); + message = myersDiffMessage.message; + + if (myersDiffMessage.skipped) { + skipped = true; + } + } + + const headerMessage = `${getErrorMessage(operator, customMessage)}\n${header}`; + const skippedMessage = skipped ? "\n... Skipped lines" : ""; + + return `${headerMessage}${skippedMessage}\n${message}\n`; +} + +function addEllipsis(string) { + const lines = StringPrototypeSplit(string, "\n", 11); + if (lines.length > 10) { + lines.length = 10; + return `${ArrayPrototypeJoin(lines, "\n")}\n...`; + } else if (string.length > kMaxLongStringLength) { + return `${StringPrototypeSlice(string, kMaxLongStringLength)}...`; + } + return string; +} + +class AssertionError extends Error { + constructor(options) { + validateObject(options, "options"); + const { + message, + operator, + stackStartFn, + details, + // Compatibility with older versions. + stackStartFunction, + } = options; + let { actual, expected } = options; + + const limit = Error.stackTraceLimit; + // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; + + if (message != null) { + if (operator === "deepStrictEqual" || operator === "strictEqual") { + super(createErrDiff(actual, expected, operator, message)); + } else { + super(String(message)); + } + } else { + // Reset colors on each call to make sure we handle dynamically set environment + // variables correct. + colors.refresh(); + // Prevent the error stack from being visible by duplicating the error + // in a very close way to the original in case both sides are actually + // instances of Error. + if ( + typeof actual === "object" && + actual !== null && + typeof expected === "object" && + expected !== null && + "stack" in actual && + actual instanceof Error && + "stack" in expected && + expected instanceof Error + ) { + actual = copyError(actual); + expected = copyError(expected); + } + + if (operator === "deepStrictEqual" || operator === "strictEqual") { + super(createErrDiff(actual, expected, operator, message)); + } else if (operator === "notDeepStrictEqual" || operator === "notStrictEqual") { + // In case the objects are equal but the operator requires unequal, show + // the first object and say A equals B + let base = kReadableOperator[operator]; + const res = StringPrototypeSplit(inspectValue(actual), "\n"); + + // In case "actual" is an object or a function, it should not be + // reference equal. + if ( + operator === "notStrictEqual" && + ((typeof actual === "object" && actual !== null) || typeof actual === "function") + ) { + base = kReadableOperator.notStrictEqualObject; + } + + // Only remove lines in case it makes sense to collapse those. + // TODO: Accept env to always show the full error. + if (res.length > 50) { + res[46] = `${colors.blue}...${colors.white}`; + while (res.length > 47) { + ArrayPrototypePop(res); + } + } + + // Only print a single input. + if (res.length === 1) { + super(`${base}${res[0].length > 5 ? "\n\n" : " "}${res[0]}`); + } else { + super(`${base}\n\n${ArrayPrototypeJoin(res, "\n")}\n`); + } + } else { + let res = inspectValue(actual); + let other = inspectValue(expected); + const knownOperator = kReadableOperator[operator]; + if (operator === "notDeepEqual" && res === other) { + res = `${knownOperator}\n\n${res}`; + if (res.length > 1024) { + res = `${StringPrototypeSlice(res, 0, 1021)}...`; + } + super(res); + } else { + if (res.length > kMaxLongStringLength) { + res = `${StringPrototypeSlice(res, 0, 509)}...`; + } + if (other.length > kMaxLongStringLength) { + other = `${StringPrototypeSlice(other, 0, 509)}...`; + } + if (operator === "deepEqual") { + res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`; + } else { + const newOp = kReadableOperator[`${operator}Unequal`]; + if (newOp) { + res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`; + } else { + other = ` ${operator} ${other}`; + } + } + super(`${res}${other}`); + } + } + } + + // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit; + + this.generatedMessage = !message; + ObjectDefineProperty(this, "name", { + __proto__: null, + value: "AssertionError [ERR_ASSERTION]", + enumerable: false, + writable: true, + configurable: true, + }); + this.code = "ERR_ASSERTION"; + if (details) { + this.actual = undefined; + this.expected = undefined; + this.operator = undefined; + for (let i = 0; i < details.length; i++) { + this["message " + i] = details[i].message; + this["actual " + i] = details[i].actual; + this["expected " + i] = details[i].expected; + this["operator " + i] = details[i].operator; + this["stack trace " + i] = details[i].stack; + } + } else { + this.actual = actual; + this.expected = expected; + this.operator = operator; + } + ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction); + // Create error message including the error code in the name. + this.stack; // eslint-disable-line no-unused-expressions + // Reset the name. + this.name = "AssertionError"; + } + + toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } + + [inspect.custom](recurseTimes, ctx) { + // Long strings should not be fully inspected. + const tmpActual = this.actual; + const tmpExpected = this.expected; + + if (typeof this.actual === "string") { + this.actual = addEllipsis(this.actual); + } + if (typeof this.expected === "string") { + this.expected = addEllipsis(this.expected); + } + + // This limits the `actual` and `expected` property default inspection to + // the minimum depth. Otherwise those values would be too verbose compared + // to the actual error message which contains a combined view of these two + // input values. + const result = inspect(this, { + ...ctx, + customInspect: false, + depth: 0, + }); + + // Reset the properties after inspection. + this.actual = tmpActual; + this.expected = tmpExpected; + + return result; + } +} + +export default AssertionError; diff --git a/src/js/internal/assert/calltracker.ts b/src/js/internal/assert/calltracker.ts new file mode 100644 index 00000000000000..0e207bb5f9939e --- /dev/null +++ b/src/js/internal/assert/calltracker.ts @@ -0,0 +1,145 @@ +"use strict"; + +const { + ArrayPrototypePush, + ArrayPrototypeSlice, + Error, + FunctionPrototype, + ObjectFreeze, + Proxy, + ReflectApply, + SafeSet, + SafeWeakMap, +} = require("internal/primordials"); + +// const { +// codes: { ERR_INVALID_ARG_VALUE, ERR_UNAVAILABLE_DURING_EXIT }, +// } = require("internal/errors"); +const AssertionError = require("internal/assert/assertion_error"); +const { validateUint32 } = require("internal/validators"); + +const noop = FunctionPrototype; + +class CallTrackerContext { + #expected; + #calls; + #name; + #stackTrace; + constructor({ expected, stackTrace, name }) { + this.#calls = []; + this.#expected = expected; + this.#stackTrace = stackTrace; + this.#name = name; + } + + track(thisArg, args) { + const argsClone = ObjectFreeze(ArrayPrototypeSlice(args)); + ArrayPrototypePush(this.#calls, ObjectFreeze({ thisArg, arguments: argsClone })); + } + + get delta() { + return this.#calls.length - this.#expected; + } + + reset() { + this.#calls = []; + } + getCalls() { + return ObjectFreeze(ArrayPrototypeSlice(this.#calls)); + } + + report() { + if (this.delta !== 0) { + const message = + `Expected the ${this.#name} function to be ` + + `executed ${this.#expected} time(s) but was ` + + `executed ${this.#calls.length} time(s).`; + return { + message, + actual: this.#calls.length, + expected: this.#expected, + operator: this.#name, + stack: this.#stackTrace, + }; + } + } +} + +class CallTracker { + #callChecks = new SafeSet(); + #trackedFunctions = new SafeWeakMap(); + + #getTrackedFunction(tracked) { + if (!this.#trackedFunctions.has(tracked)) { + throw $ERR_INVALID_ARG_VALUE("tracked", tracked, "is not a tracked function"); + } + return this.#trackedFunctions.get(tracked); + } + + reset(tracked) { + if (tracked === undefined) { + this.#callChecks.forEach(check => check.reset()); + return; + } + + this.#getTrackedFunction(tracked).reset(); + } + + getCalls(tracked) { + return this.#getTrackedFunction(tracked).getCalls(); + } + + calls(fn, expected = 1) { + if (process._exiting) throw $ERR_UNAVAILABLE_DURING_EXIT("Cannot call function in process exit handler"); + if (typeof fn === "number") { + expected = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + validateUint32(expected, "expected", true); + + const context = new CallTrackerContext({ + expected, + // eslint-disable-next-line no-restricted-syntax + stackTrace: new Error(), + name: fn.name || "calls", + }); + const tracked = new Proxy(fn, { + __proto__: null, + apply(fn, thisArg, argList) { + context.track(thisArg, argList); + return ReflectApply(fn, thisArg, argList); + }, + }); + this.#callChecks.add(context); + this.#trackedFunctions.set(tracked, context); + return tracked; + } + + report() { + const errors = []; + for (const context of this.#callChecks) { + const message = context.report(); + if (message !== undefined) { + ArrayPrototypePush(errors, message); + } + } + return errors; + } + + verify() { + const errors = this.report(); + if (errors.length === 0) { + return; + } + const message = errors.length === 1 ? errors[0].message : "Functions were not called the expected number of times"; + throw new AssertionError({ + message, + details: errors, + }); + } +} + +export default CallTracker; diff --git a/src/js/internal/assert/myers_diff.ts b/src/js/internal/assert/myers_diff.ts new file mode 100644 index 00000000000000..b7fc8286209d29 --- /dev/null +++ b/src/js/internal/assert/myers_diff.ts @@ -0,0 +1,172 @@ +"use strict"; + +const { + Array, + ArrayPrototypeFill, + ArrayPrototypePush, + ArrayPrototypeSlice, + StringPrototypeEndsWith, +} = require("internal/primordials"); + +const colors = require("internal/util/colors"); + +const kNopLinesToCollapse = 5; + +function areLinesEqual(actual, expected, checkCommaDisparity) { + if (actual === expected) { + return true; + } + if (checkCommaDisparity) { + return `${actual},` === expected || actual === `${expected},`; + } + return false; +} + +export function myersDiff(actual, expected, checkCommaDisparity = false) { + const actualLength = actual.length; + const expectedLength = expected.length; + const max = actualLength + expectedLength; + const v = ArrayPrototypeFill(Array(2 * max + 1), 0); + + const trace = []; + + for (let diffLevel = 0; diffLevel <= max; diffLevel++) { + const newTrace = ArrayPrototypeSlice(v); + ArrayPrototypePush(trace, newTrace); + + for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) { + let x; + if ( + diagonalIndex === -diffLevel || + (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max]) + ) { + x = v[diagonalIndex + 1 + max]; + } else { + x = v[diagonalIndex - 1 + max] + 1; + } + + let y = x - diagonalIndex; + + while (x < actualLength && y < expectedLength && areLinesEqual(actual[x], expected[y], checkCommaDisparity)) { + x++; + y++; + } + + v[diagonalIndex + max] = x; + + if (x >= actualLength && y >= expectedLength) { + return backtrack(trace, actual, expected, checkCommaDisparity); + } + } + } +} + +function backtrack(trace, actual, expected, checkCommaDisparity) { + const actualLength = actual.length; + const expectedLength = expected.length; + const max = actualLength + expectedLength; + + let x = actualLength; + let y = expectedLength; + const result = []; + + for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) { + const v = trace[diffLevel]; + const diagonalIndex = x - y; + let prevDiagonalIndex; + + if ( + diagonalIndex === -diffLevel || + (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max]) + ) { + prevDiagonalIndex = diagonalIndex + 1; + } else { + prevDiagonalIndex = diagonalIndex - 1; + } + + const prevX = v[prevDiagonalIndex + max]; + const prevY = prevX - prevDiagonalIndex; + + while (x > prevX && y > prevY) { + const value = + !checkCommaDisparity || StringPrototypeEndsWith(actual[x - 1], ",") ? actual[x - 1] : expected[y - 1]; + ArrayPrototypePush(result, { __proto__: null, type: "nop", value }); + x--; + y--; + } + + if (diffLevel > 0) { + if (x > prevX) { + ArrayPrototypePush(result, { __proto__: null, type: "insert", value: actual[x - 1] }); + x--; + } else { + ArrayPrototypePush(result, { __proto__: null, type: "delete", value: expected[y - 1] }); + y--; + } + } + } + + return result; +} + +export function printSimpleMyersDiff(diff) { + let message = ""; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + const { type, value } = diff[diffIdx]; + if (type === "insert") { + message += `${colors.green}${value}${colors.white}`; + } else if (type === "delete") { + message += `${colors.red}${value}${colors.white}`; + } else { + message += `${colors.white}${value}${colors.white}`; + } + } + + return `\n${message}`; +} + +export function printMyersDiff(diff, simple = false) { + let message = ""; + let skipped = false; + let nopCount = 0; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + const { type, value } = diff[diffIdx]; + const previousType = diffIdx < diff.length - 1 ? diff[diffIdx + 1].type : null; + const typeChanged = previousType && type !== previousType; + + if (typeChanged && previousType === "nop") { + // Avoid grouping if only one line would have been grouped otherwise + if (nopCount === kNopLinesToCollapse + 1) { + message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + } else if (nopCount === kNopLinesToCollapse + 2) { + message += `${colors.white} ${diff[diffIdx + 2].value}\n`; + message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + } + if (nopCount >= kNopLinesToCollapse + 3) { + message += `${colors.blue}...${colors.white}\n`; + message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + skipped = true; + } + nopCount = 0; + } + + if (type === "insert") { + message += `${colors.green}+${colors.white} ${value}\n`; + } else if (type === "delete") { + message += `${colors.red}-${colors.white} ${value}\n`; + } else if (type === "nop") { + if (nopCount < kNopLinesToCollapse) { + message += `${colors.white} ${value}\n`; + } + nopCount++; + } + } + + message = message.trimEnd(); + + return { message: `\n${message}`, skipped }; +} + +// export default { myersDiff, printMyersDiff, printSimpleMyersDiff }; diff --git a/src/js/internal/assert/utils.ts b/src/js/internal/assert/utils.ts new file mode 100644 index 00000000000000..1f58c43d93a2ce --- /dev/null +++ b/src/js/internal/assert/utils.ts @@ -0,0 +1,294 @@ +/* prettier-ignore */ +'use strict'; + +const { + // ArrayPrototypeShift, + // Error, + ErrorCaptureStackTrace, + // FunctionPrototypeBind, + // RegExpPrototypeSymbolReplace, + // SafeMap, + // StringPrototypeCharCodeAt, + // StringPrototypeIncludes, + // StringPrototypeIndexOf, + // StringPrototypeReplace, + // StringPrototypeSlice, + // StringPrototypeSplit, + // StringPrototypeStartsWith, +} = require('internal/primordials'); + +const { isError } = require('internal/util'); +// const { Buffer } = require('node:buffer'); +// const { +// isErrorStackTraceLimitWritable, +// overrideStackTrace, +// } = require('internal/errors'); +const AssertionError = require('internal/assert/assertion_error'); +// const { openSync, closeSync, readSync } = require('node:fs'); +// // const { EOL } = require('internal/constants'); +// // const { BuiltinModule } = require('internal/bootstrap/realm'); +// // const { isError } = require('internal/util'); + + +// const errorCache = new SafeMap(); +// // const { fileURLToPath } = require('internal/url'); + +// let parseExpressionAt; +// let findNodeAround; +// let tokenizer; +// let decoder; + +// // Escape control characters but not \n and \t to keep the line breaks and +// // indentation intact. +// // eslint-disable-next-line no-control-regex +// const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g; +// const meta = [ +// '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', +// '\\u0005', '\\u0006', '\\u0007', '\\b', '', +// '', '\\u000b', '\\f', '', '\\u000e', +// '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013', +// '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', +// '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d', +// '\\u001e', '\\u001f', +// ]; + +// const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)]; + +// function findColumn(fd, column: number, code: string) { +// if (code.length > column + 100) { +// try { +// return parseCode(code, column); +// } catch { +// // End recursion in case no code could be parsed. The expression should +// // have been found after 2500 characters, so stop trying. +// if (code.length - column > 2500) { +// // eslint-disable-next-line no-throw-literal +// throw null; +// } +// } +// } +// // Read up to 2500 bytes more than necessary in columns. That way we address +// // multi byte characters and read enough data to parse the code. +// const bytesToRead = column - code.length + 2500; +// const buffer = Buffer.allocUnsafe(bytesToRead); +// const bytesRead = readSync(fd, buffer, 0, bytesToRead); +// code += decoder.write(buffer.slice(0, bytesRead)); +// // EOF: fast path. +// if (bytesRead < bytesToRead) { +// return parseCode(code, column); +// } +// // Read potentially missing code. +// return findColumn(fd, column, code); +// } + +// function getCode(fd, line: number, column: number) { +// let bytesRead = 0; +// if (line === 0) { +// // Special handle line number one. This is more efficient and simplifies the +// // rest of the algorithm. Read more than the regular column number in bytes +// // to prevent multiple reads in case multi byte characters are used. +// return findColumn(fd, column, ''); +// } +// let lines = 0; +// // Prevent blocking the event loop by limiting the maximum amount of +// // data that may be read. +// let maxReads = 32; // bytesPerRead * maxReads = 512 KiB +// const bytesPerRead = 16384; +// // Use a single buffer up front that is reused until the call site is found. +// let buffer = Buffer.allocUnsafe(bytesPerRead); +// while (maxReads-- !== 0) { +// // Only allocate a new buffer in case the needed line is found. All data +// // before that can be discarded. +// buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead); +// bytesRead = readSync(fd, buffer, 0, bytesPerRead); +// // Read the buffer until the required code line is found. +// for (let i = 0; i < bytesRead; i++) { +// if (buffer[i] === 10 && ++lines === line) { +// // If the end of file is reached, directly parse the code and return. +// if (bytesRead < bytesPerRead) { +// return parseCode(buffer.toString('utf8', i + 1, bytesRead), column); +// } +// // Check if the read code is sufficient or read more until the whole +// // expression is read. Make sure multi byte characters are preserved +// // properly by using the decoder. +// const code = decoder.write(buffer.slice(i + 1, bytesRead)); +// return findColumn(fd, column, code); +// } +// } +// } +// } + +// TODO: parse source to get assertion message +// function parseCode(code, offset) { +// // Lazy load acorn. +// if (parseExpressionAt === undefined) { +// const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser; +// ({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk')); + +// parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser); +// tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser); +// } +// let node; +// let start; +// // Parse the read code until the correct expression is found. +// for (const token of tokenizer(code, { ecmaVersion: 'latest' })) { +// start = token.start; +// if (start > offset) { +// // No matching expression found. This could happen if the assert +// // expression is bigger than the provided buffer. +// break; +// } +// try { +// node = parseExpressionAt(code, start, { ecmaVersion: 'latest' }); +// // Find the CallExpression in the tree. +// node = findNodeAround(node, offset, 'CallExpression'); +// if (node?.node.end >= offset) { +// return [ +// node.node.start, +// StringPrototypeReplace(StringPrototypeSlice(code, +// node.node.start, node.node.end), +// escapeSequencesRegExp, escapeFn), +// ]; +// } +// // eslint-disable-next-line no-unused-vars +// } catch (err) { +// continue; +// } +// } +// // eslint-disable-next-line no-throw-literal +// throw null; +// } + +function getErrMessage(message: string, value: unknown, fn: Function): string | undefined { + // const tmpLimit = Error.stackTraceLimit; + // const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable(); + // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it + // does to much work. + // if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1; + // We only need the stack trace. To minimize the overhead use an object + // instead of an error. + // const err = {}; + // ErrorCaptureStackTrace(err, fn); + // if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit; + + // overrideStackTrace.set(err, (_, stack) => stack); + // const call = err.stack[0]; + // + if (fn.name === 'ok') { + if (message === null) throw new Error('what the fuck'); // message = "null" + return `The expression evaluated to a falsy value:\n\n assert.ok(${value})\n`; + } + + // let filename = call.getFileName(); + // const line = call.getLineNumber() - 1; + // let column = call.getColumnNumber() - 1; + // let identifier; + // let code; + + // if (filename) { + // identifier = `${filename}${line}${column}`; + + // // Skip Node.js modules! + // if (StringPrototypeStartsWith(filename, 'node:') && + // BuiltinModule.exists(StringPrototypeSlice(filename, 5))) { + // errorCache.set(identifier, undefined); + // return; + // } + // } else { + // return message; + // } + + // if (errorCache.has(identifier)) { + // return errorCache.get(identifier); + // } + + // let fd; + // try { + // // Set the stack trace limit to zero. This makes sure unexpected token + // // errors are handled faster. + // if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0; + + // if (filename) { + // if (decoder === undefined) { + // const { StringDecoder } = require('string_decoder'); + // decoder = new StringDecoder('utf8'); + // } + + // // ESM file prop is a file proto. Convert that to path. + // // This ensure opensync will not throw ENOENT for ESM files. + // const fileProtoPrefix = 'file://'; + // if (StringPrototypeStartsWith(filename, fileProtoPrefix)) { + // filename = Bun.fileURLToPath(filename); + // } + + // fd = openSync(filename, 'r', 0o666); + // // Reset column and message. + // ({ 0: column, 1: message } = getCode(fd, line, column)); + // // Flush unfinished multi byte characters. + // decoder.end(); + // } else { + // for (let i = 0; i < line; i++) { + // code = StringPrototypeSlice(code, + // StringPrototypeIndexOf(code, '\n') + 1); + // } + // // ({ 0: column, 1: message } = parseCode(code, column)); + // throw new Error("todo: parseCode"); + // } + // // Always normalize indentation, otherwise the message could look weird. + // if (StringPrototypeIncludes(message, '\n')) { + // if (process.platform === 'win32') { + // message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n'); + // } + // const frames = StringPrototypeSplit(message, '\n'); + // message = ArrayPrototypeShift(frames); + // for (const frame of frames) { + // let pos = 0; + // while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) { + // pos++; + // } + // message += `\n ${StringPrototypeSlice(frame, pos)}`; + // } + // } + // message = `The expression evaluated to a falsy value:\n\n ${message}\n`; + // // Make sure to always set the cache! No matter if the message is + // // undefined or not + // errorCache.set(identifier, message); + + // return message; + // } catch { + // // Invalidate cache to prevent trying to read this part again. + // errorCache.set(identifier, undefined); + // } finally { + // // Reset limit. + // if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit; + // if (fd !== undefined) + // closeSync(fd); + // } +} + +export function innerOk(fn, argLen, value, message) { + if (!value) { + let generatedMessage = false; + + if (argLen === 0) { + generatedMessage = true; + message = 'No value argument passed to `assert.ok()`'; + } else if (message == null) { + generatedMessage = true; + message = getErrMessage(message, value, fn); + // TODO: message + } else if (isError(message)) { + throw message; + } + + const err = new AssertionError({ + actual: value, + expected: true, + message, + operator: '==', + stackStartFn: fn, + }); + err.generatedMessage = generatedMessage; + throw err; + } +} diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index e68d6d6fe3f6c5..83a93e1e587be6 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -76,10 +76,10 @@ const StringIterator = uncurryThis(String.prototype[Symbol.iterator]); const StringIteratorPrototype = Reflect.getPrototypeOf(StringIterator("")); const ArrayPrototypeForEach = uncurryThis(Array.prototype.forEach); -function ErrorCaptureStackTrace(targetObject) { - const stack = new Error().stack; +function ErrorCaptureStackTrace(targetObject, maybeStartStackFn) { + Error.captureStackTrace(targetObject, maybeStartStackFn); // Remove the second line, which is this function - targetObject.stack = stack.replace(/.*\n.*/, "$1"); + targetObject.stack = targetObject.stack.replace(/.*\n.*/, "$1"); } const arrayProtoPush = Array.prototype.push; @@ -94,6 +94,7 @@ export default { ArrayPrototypeFlat: uncurryThis(Array.prototype.flat), ArrayPrototypeFilter: uncurryThis(Array.prototype.filter), ArrayPrototypeForEach, + ArrayPrototypeFill: uncurryThis(Array.prototype.fill), ArrayPrototypeIncludes: uncurryThis(Array.prototype.includes), ArrayPrototypeIndexOf: uncurryThis(Array.prototype.indexOf), ArrayPrototypeJoin: uncurryThis(Array.prototype.join), @@ -110,10 +111,16 @@ export default { DatePrototypeGetTime: uncurryThis(Date.prototype.getTime), DatePrototypeToISOString: uncurryThis(Date.prototype.toISOString), DatePrototypeToString: uncurryThis(Date.prototype.toString), + Error, ErrorCaptureStackTrace, ErrorPrototypeToString: uncurryThis(Error.prototype.toString), FunctionPrototypeToString: uncurryThis(Function.prototype.toString), + FunctionPrototypeCall: uncurryThis(Function.prototype["call"]), JSONStringify: JSON.stringify, + MapPrototypeDelete: uncurryThis(Map.prototype.delete), + MapPrototypeGet: uncurryThis(Map.prototype.get), + MapPrototypeHas: uncurryThis(Map.prototype.has), + MapPrototypeSet: uncurryThis(Map.prototype.set), MapPrototypeGetSize: getGetter(Map, "size"), MapPrototypeEntries: uncurryThis(Map.prototype.entries), MapPrototypeValues: uncurryThis(Map.prototype.values), @@ -144,10 +151,13 @@ export default { ObjectIs: Object.is, ObjectKeys: Object.keys, ObjectPrototypeHasOwnProperty: uncurryThis(Object.prototype.hasOwnProperty), + ObjectPrototypeIsPrototypeOf: uncurryThis(Object.prototype.isPrototypeOf), ObjectPrototypePropertyIsEnumerable: uncurryThis(Object.prototype.propertyIsEnumerable), ObjectPrototypeToString: uncurryThis(Object.prototype.toString), ObjectSeal: Object.seal, ObjectSetPrototypeOf: Object.setPrototypeOf, + ReflectApply: Reflect["apply"], + ReflectHas: Reflect.has, ReflectOwnKeys: Reflect.ownKeys, RegExp, RegExpPrototypeExec: uncurryThis(RegExp.prototype.exec), @@ -172,12 +182,21 @@ export default { } }, ), + SafeWeakSet: makeSafe( + WeakSet, + class SafeWeakSet extends WeakSet { + constructor(i) { + super(i); + } + }, + ), DatePrototypeGetMilliseconds: uncurryThis(Date.prototype.getMilliseconds), DatePrototypeToUTCString: uncurryThis(Date.prototype.toUTCString), SetPrototypeGetSize: getGetter(Set, "size"), SetPrototypeEntries: uncurryThis(Set.prototype.entries), SetPrototypeValues: uncurryThis(Set.prototype.values), String, + StringPrototypeAt: uncurryThis(String.prototype.at), StringPrototypeCharCodeAt: uncurryThis(String.prototype.charCodeAt), StringPrototypeCodePointAt: uncurryThis(String.prototype.codePointAt), StringPrototypeEndsWith: uncurryThis(String.prototype.endsWith), diff --git a/src/js/internal/util.ts b/src/js/internal/util.ts new file mode 100644 index 00000000000000..a04d5847e5cb31 --- /dev/null +++ b/src/js/internal/util.ts @@ -0,0 +1,7 @@ +const { isNativeError } = require("node:util/types"); + +/** + * @fixme errors thrown in a different VM context are neither `isNativeError` + * nor `instanceof Error`. + */ +export const isError = (err: unknown): err is Error => isNativeError(err) || err instanceof Error || (typeof err === 'object' && err !== null && 'name' in err && 'message' in err && 'stack' in err); diff --git a/src/js/internal/util/colors.ts b/src/js/internal/util/colors.ts new file mode 100644 index 00000000000000..e1f037c70dc478 --- /dev/null +++ b/src/js/internal/util/colors.ts @@ -0,0 +1,54 @@ +// Taken from Node - lib/internal/util/colors.js +"use strict"; + +// TODO: add internal/tty module. +// let internalTTy; +// function lazyInternalTTY() { +// internalTTy ??= require("internal/tty"); +// return internalTTy; +// } + +let exports = { + blue: "", + green: "", + white: "", + yellow: "", + red: "", + gray: "", + clear: "", + reset: "", + hasColors: false, + shouldColorize(stream) { + // if (process.env.FORCE_COLOR !== undefined) { + // return lazyInternalTTY().getColorDepth() > 2; + // } + return stream?.isTTY && (typeof stream.getColorDepth === "function" ? stream.getColorDepth() > 2 : true); + }, + refresh(): void { + if (exports.shouldColorize(process.stderr)) { + exports.blue = "\u001b[34m"; + exports.green = "\u001b[32m"; + exports.white = "\u001b[39m"; + exports.yellow = "\u001b[33m"; + exports.red = "\u001b[31m"; + exports.gray = "\u001b[90m"; + exports.clear = "\u001bc"; + exports.reset = "\u001b[0m"; + exports.hasColors = true; + } else { + exports.blue = ""; + exports.green = ""; + exports.white = ""; + exports.yellow = ""; + exports.red = ""; + exports.gray = ""; + exports.clear = ""; + exports.reset = ""; + exports.hasColors = false; + } + }, +}; + +exports.refresh(); + +export default exports; diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index e5343bd18e495b..fb8d482b2c8633 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -1,1271 +1,998 @@ -// Hardcoded module "node:assert" -const util = require("node:util"); +// Copied from Node.js (src/lib/assert.js) +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -var isDeepEqual = Bun.deepEquals; -var __commonJS = (cb, mod: typeof module | undefined = undefined) => - function () { - return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; - }; +"use strict"; -// assert/build/internal/errors.js -var require_errors = __commonJS({ - "assert/build/internal/errors.js"(exports, module2) { - "use strict"; - function _typeof(obj) { - return ( - typeof Symbol == "function" && typeof Symbol.iterator == "symbol" - ? (_typeof = function (obj2) { - return typeof obj2; - }) - : (_typeof = function (obj2) { - return obj2 && typeof Symbol == "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype - ? "symbol" - : typeof obj2; - }), - _typeof(obj) - ); - } - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); - } - function _possibleConstructorReturn(self, call) { - return call && (_typeof(call) === "object" || typeof call == "function") ? call : _assertThisInitialized(self); - } - function _assertThisInitialized(self) { - if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - return self; - } - function _getPrototypeOf(o) { - return ( - (_getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function (o2) { - return o2.__proto__ || Object.getPrototypeOf(o2); - }), - _getPrototypeOf(o) - ); - } - function _inherits(subClass, superClass) { - if (typeof superClass != "function" && superClass !== null) - throw new TypeError("Super expression must either be null or a function"); - (subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { value: subClass, writable: !0, configurable: !0 }, - })), - superClass && _setPrototypeOf(subClass, superClass); - } - function _setPrototypeOf(o, p) { - return ( - (_setPrototypeOf = - Object.setPrototypeOf || - function (o2, p2) { - return (o2.__proto__ = p2), o2; - }), - _setPrototypeOf(o, p) - ); - } - var codes = {}, - assert, - util; - function createErrorType(code, message, Base) { - Base || (Base = Error); - function getMessage(arg1, arg2, arg3) { - return typeof message == "string" ? message : message(arg1, arg2, arg3); - } - var NodeError = /* @__PURE__ */ (function (_Base) { - _inherits(NodeError2, _Base); - function NodeError2(arg1, arg2, arg3) { - var _this; - return ( - _classCallCheck(this, NodeError2), - (_this = _possibleConstructorReturn( - this, - _getPrototypeOf(NodeError2).$call(this, getMessage(arg1, arg2, arg3)), - )), - (_this.code = code), - _this - ); - } - return NodeError2; - })(Base); - codes[code] = NodeError; - } - function oneOf(expected, thing) { - if (Array.isArray(expected)) { - var len = expected.length; - return ( - (expected = expected.map(function (i) { - return String(i); - })), - len > 2 - ? "one of ".concat(thing, " ").concat(expected.slice(0, len - 1).join(", "), ", or ") + expected[len - 1] - : len === 2 - ? "one of ".concat(thing, " ").concat(expected[0], " or ").concat(expected[1]) - : "of ".concat(thing, " ").concat(expected[0]) - ); - } else return "of ".concat(thing, " ").concat(String(expected)); - } - function startsWith(str, search, pos) { - return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; - } - function endsWith(str, search, this_len) { - return ( - (this_len === void 0 || this_len > str.length) && (this_len = str.length), - str.substring(this_len - search.length, this_len) === search - ); - } - function includes(str, search, start) { - return ( - typeof start != "number" && (start = 0), - start + search.length > str.length ? !1 : str.indexOf(search, start) !== -1 +const { + ArrayFrom, + ArrayIsArray, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + Error, + FunctionPrototypeCall, + MapPrototypeDelete, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + NumberIsNaN, + ObjectAssign, + ObjectIs, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + ReflectApply, + ReflectHas, + ReflectOwnKeys, + RegExpPrototypeExec, + SafeMap, + SafeSet, + SafeWeakSet, + StringPrototypeIndexOf, + StringPrototypeSlice, + StringPrototypeSplit, + SymbolIterator, +} = require("internal/primordials"); + +// const { +// ERR_AMBIGUOUS_ARGUMENT, +// ERR_INVALID_ARG_TYPE, +// ERR_INVALID_ARG_VALUE, +// ERR_INVALID_RETURN_VALUE, +// ERR_MISSING_ARGS, +// } = require("internal/errors"); +const AssertionError = require("internal/assert/assertion_error"); +const { inspect } = require("internal/util/inspect"); +const { Buffer } = require("node:buffer"); +const { deprecate } = require("node:util"); +const { isKeyObject, isPromise, isRegExp, isMap, isSet, isDate, isWeakSet, isWeakMap } = require("node:util/types"); +// const { isError, deprecate, emitExperimentalWarning } = require("internal/util"); +const { isError } = require("internal/util"); +const { innerOk } = require("internal/assert/utils"); + +const CallTracker = require("internal/assert/calltracker"); +const { validateFunction } = require("internal/validators"); + +// import type nodeAssert from "node:assert"; +type nodeAssert = typeof import("node:assert"); + +// let isDeepEqual = Bun.deepEquals +// let isDeepStrictEqual; + +// function lazyLoadComparison() { +// const comparison = require("internal/util/comparisons"); +// isDeepEqual = comparison.isDeepEqual; +// isDeepStrictEqual = comparison.isDeepStrictEqual; +// } +function isDeepEqual(a, b) { + return Bun.deepEquals(a, b, false); +} +function isDeepStrictEqual(a, b) { + return Bun.deepEquals(a, b, true); +} + +let warned = false; + +// The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +const assert: nodeAssert = ok as any; +export default assert; + +const NO_EXCEPTION_SENTINEL = {}; + +// All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function innerFail(obj) { + if (obj.message instanceof Error) throw obj.message; + + throw new AssertionError(obj); +} + +function fail(message?: string | Error): never; +/** @deprecated since v10.0.0 - use fail([message]) or other assert functions instead. */ +function fail( + actual: unknown, + expected: unknown, + message?: string | Error, + operator?: string, + // eslint-disable-next-line @typescript-eslint/ban-types + stackStartFn?: Function, +): never; +function fail( + actual: unknown, + expected: unknown, + message?: string | Error, + operator?: string, + stackStartFn?: Function, +) { + const argsLen = arguments.length; + + let internalMessage = false; + if (actual == null && argsLen <= 1) { + internalMessage = true; + message = "Failed"; + } else if (argsLen === 1) { + message = actual; + actual = undefined; + } else { + if (warned === false) { + warned = true; + process.emitWarning( + "assert.fail() with more than one argument is deprecated. " + + "Please use assert.strictEqual() instead or only pass a message.", + "DeprecationWarning", + "DEP0094", ); } - createErrorType("ERR_AMBIGUOUS_ARGUMENT", 'The "%s" argument is ambiguous. %s', TypeError); - createErrorType( - "ERR_INVALID_ARG_TYPE", - function (name, expected, actual) { - assert === void 0 && (assert = require_assert()), assert(typeof name == "string", "'name' must be a string"); - var determiner; - typeof expected == "string" && startsWith(expected, "not ") - ? ((determiner = "must not be"), (expected = expected.replace(/^not /, ""))) - : (determiner = "must be"); - var msg; - if (endsWith(name, " argument")) - msg = "The ".concat(name, " ").concat(determiner, " ").concat(oneOf(expected, "type")); - else { - var type = includes(name, ".") ? "property" : "argument"; - msg = 'The "'.concat(name, '" ').concat(type, " ").concat(determiner, " ").concat(oneOf(expected, "type")); - } - return (msg += ". Received type ".concat(_typeof(actual))), msg; - }, - TypeError, - ); - createErrorType( - "ERR_INVALID_ARG_VALUE", - function (name, value) { - var reason = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : "is invalid"; - var inspected = util.inspect(value); - return ( - inspected.length > 128 && (inspected = "".concat(inspected.slice(0, 128), "...")), - "The argument '".concat(name, "' ").concat(reason, ". Received ").concat(inspected) - ); - }, - TypeError, - RangeError, - ); - createErrorType( - "ERR_INVALID_RETURN_VALUE", - function (input, name, value) { - var type; - return ( - value && value.constructor && value.constructor.name - ? (type = "instance of ".concat(value.constructor.name)) - : (type = "type ".concat(_typeof(value))), - "Expected ".concat(input, ' to be returned from the "').concat(name, '"') + - " function but got ".concat(type, ".") - ); - }, - TypeError, - ); - createErrorType( - "ERR_MISSING_ARGS", - function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) - args[_key] = arguments[_key]; - assert === void 0 && (assert = require_assert()), - assert(args.length > 0, "At least one arg needs to be specified"); - var msg = "The ", - len = args.length; - switch ( - ((args = args.map(function (a) { - return '"'.concat(a, '"'); - })), - len) - ) { - case 1: - msg += "".concat(args[0], " argument"); - break; - case 2: - msg += "".concat(args[0], " and ").concat(args[1], " arguments"); - break; - default: - (msg += args.slice(0, len - 1).join(", ")), (msg += ", and ".concat(args[len - 1], " arguments")); - break; - } - return "".concat(msg, " must be specified"); - }, - TypeError, - ); - module2.exports.codes = codes; - }, -}); + if (argsLen === 2) operator = "!="; + } + + if (message instanceof Error) throw message; + + const errArgs = { + actual, + expected, + operator: operator === undefined ? "fail" : operator, + stackStartFn: stackStartFn || fail, + message, + }; + const err = new AssertionError(errArgs); + if (internalMessage) { + err.generatedMessage = true; + } + throw err; +} + +assert.fail = fail; + +// The AssertionError is defined in internal/error. +assert.AssertionError = AssertionError; + +/** + * Pure assertion tests whether a value is truthy, as determined + * by !!value. + * @param {...any} args + * @returns {void} + */ -// assert/build/internal/assert/assertion_error.js -var require_assertion_error = __commonJS({ - "assert/build/internal/assert/assertion_error.js"(exports, module2) { - "use strict"; - function _objectSpread(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] != null ? arguments[i] : {}, - ownKeys = Object.keys(source); - typeof Object.getOwnPropertySymbols == "function" && - (ownKeys = ownKeys.concat( - Object.getOwnPropertySymbols(source).filter(function (sym) { - return Object.getOwnPropertyDescriptor(source, sym).enumerable; - }), - )), - ownKeys.forEach(function (key) { - _defineProperty(target, key, source[key]); - }); +function ok(value: unknown, message?: string | Error): asserts value; +function ok(...args: unknown[]): void { + innerOk(ok, args.length, ...args); +} +assert.ok = ok; + +/** + * The equality assertion tests shallow, coercive equality with ==. + * @param actual + * @param expected + * @param message + * @returns {void} + */ +/* eslint-disable no-restricted-properties */ +assert.equal = function equal(actual: unknown, expected: unknown, message?: string | Error) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + // eslint-disable-next-line eqeqeq + // if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) { + if (actual != expected && !(isNaN(actual) && isNaN(expected))) { + innerFail({ + actual, + expected, + message, + operator: "==", + stackStartFn: equal, + }); + } +}; + +/** + * The non-equality assertion tests for whether two objects are not + * equal with !=. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.notEqual = function notEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + // eslint-disable-next-line eqeqeq + if (actual == expected || (NumberIsNaN(actual) && NumberIsNaN(expected))) { + innerFail({ + actual, + expected, + message, + operator: "!=", + stackStartFn: notEqual, + }); + } +}; + +/** + * The deep equivalence assertion tests a deep equality relation. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.deepEqual = function deepEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + // if (isDeepEqual === undefined) lazyLoadComparison(); + if (!isDeepEqual(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "deepEqual", + stackStartFn: deepEqual, + }); + } +}; + +/** + * The deep non-equivalence assertion tests for any deep inequality. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + if (isDeepEqual === undefined) lazyLoadComparison(); + if (isDeepEqual(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "notDeepEqual", + stackStartFn: notDeepEqual, + }); + } +}; +/* eslint-enable */ + +/** + * The deep strict equivalence assertion tests a deep strict equality + * relation. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + if (isDeepEqual === undefined) lazyLoadComparison(); + if (!isDeepStrictEqual(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "deepStrictEqual", + stackStartFn: deepStrictEqual, + }); + } +}; + +/** + * The deep strict non-equivalence assertion tests for any deep strict + * inequality. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.notDeepStrictEqual = notDeepStrictEqual; +function notDeepStrictEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + if (isDeepEqual === undefined) lazyLoadComparison(); + if (isDeepStrictEqual(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "notDeepStrictEqual", + stackStartFn: notDeepStrictEqual, + }); + } +} + +/** + * The strict equivalence assertion tests a strict equality relation. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.strictEqual = function strictEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + if (!ObjectIs(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "strictEqual", + stackStartFn: strictEqual, + }); + } +}; + +/** + * The strict non-equivalence assertion tests for any strict inequality. + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + if (ObjectIs(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "notStrictEqual", + stackStartFn: notStrictEqual, + }); + } +}; + +function isSpecial(obj) { + return obj == null || typeof obj !== "object" || isError(obj) || isRegExp(obj) || isDate(obj); +} + +const typesToCallDeepStrictEqualWith = [isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer]; + +/** + * Compares two objects or values recursively to check if they are equal. + * @param {any} actual - The actual value to compare. + * @param {any} expected - The expected value to compare. + * @param {Set} [comparedObjects=new Set()] - Set to track compared objects for handling circular references. + * @returns {boolean} - Returns `true` if the actual value matches the expected value, otherwise `false`. + * @example + * compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true + */ +function compareBranch(actual, expected, comparedObjects) { + // Check for Map object equality + if (isMap(actual) && isMap(expected)) { + if (actual.size !== expected.size) { + return false; + } + const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual); + + comparedObjects ??= new SafeWeakSet(); + + for (const { 0: key, 1: val } of safeIterator) { + if (!MapPrototypeHas(expected, key)) { + return false; } - return target; - } - function _defineProperty(obj, key, value) { - return ( - key in obj - ? Object.defineProperty(obj, key, { - value, - enumerable: !0, - configurable: !0, - writable: !0, - }) - : (obj[key] = value), - obj - ); - } - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); - } - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - (descriptor.enumerable = descriptor.enumerable || !1), - (descriptor.configurable = !0), - "value" in descriptor && (descriptor.writable = !0), - Object.defineProperty(target, descriptor.key, descriptor); + if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) { + return false; } } - function _createClass(Constructor, protoProps, staticProps) { - return ( - protoProps && _defineProperties(Constructor.prototype, protoProps), - staticProps && _defineProperties(Constructor, staticProps), - Constructor - ); - } - function _possibleConstructorReturn(self, call) { - return call && (_typeof(call) === "object" || typeof call == "function") ? call : _assertThisInitialized(self); - } - function _assertThisInitialized(self) { - if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - return self; - } - function _inherits(subClass, superClass) { - if (typeof superClass != "function" && superClass !== null) - throw new TypeError("Super expression must either be null or a function"); - (subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { value: subClass, writable: !0, configurable: !0 }, - })), - superClass && _setPrototypeOf(subClass, superClass); + return true; + } + + for (const type of typesToCallDeepStrictEqualWith) { + if (type(actual) || type(expected)) { + if (isDeepStrictEqual === undefined) lazyLoadComparison(); + return isDeepStrictEqual(actual, expected); } - function _wrapNativeSuper(Class) { - var _cache = typeof Map == "function" ? new Map() : void 0; - return ( - (_wrapNativeSuper = function (Class2) { - if (Class2 === null || !_isNativeFunction(Class2)) return Class2; - if (typeof Class2 != "function") throw new TypeError("Super expression must either be null or a function"); - if (typeof _cache != "undefined") { - if (_cache.has(Class2)) return _cache.get(Class2); - _cache.set(Class2, Wrapper); - } - function Wrapper() { - return _construct(Class2, arguments, _getPrototypeOf(this).constructor); - } - return ( - (Wrapper.prototype = Object.create(Class2.prototype, { - constructor: { - value: Wrapper, - enumerable: !1, - writable: !0, - configurable: !0, - }, - })), - _setPrototypeOf(Wrapper, Class2) - ); - }), - _wrapNativeSuper(Class) - ); + } + + // Check for Set object equality + if (isSet(actual) && isSet(expected)) { + if (expected.size > actual.size) { + return false; // `expected` can't be a subset if it has more elements } - function isNativeReflectConstruct() { - if (typeof Reflect == "undefined" || !Reflect.construct || Reflect.construct.sham) return !1; - if (typeof Proxy == "function") return !0; - try { - return Date.prototype.toString.$call(Reflect.construct(Date, [], function () {})), !0; - } catch { - return !1; + + if (isDeepEqual === undefined) lazyLoadComparison(); + + const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual)); + const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected); + const usedIndices = new SafeSet(); + + expectedIteration: for (const expectedItem of expectedIterator) { + for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) { + if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) { + usedIndices.add(actualIdx); + continue expectedIteration; + } } + return false; } - function _construct(Parent, args, Class) { - return ( - isNativeReflectConstruct() - ? (_construct = Reflect.construct) - : (_construct = function (Parent2, args2, Class2) { - var a = [null]; - a.push.$apply(a, args2); - var Constructor = Function.bind.$apply(Parent2, a), - instance = new Constructor(); - return Class2 && _setPrototypeOf(instance, Class2.prototype), instance; - }), - _construct.$apply(null, arguments) - ); - } - function _isNativeFunction(fn) { - return Function.toString.$call(fn).indexOf("[native code]") !== -1; - } - function _setPrototypeOf(o, p) { - return ( - (_setPrototypeOf = - Object.setPrototypeOf || - function (o2, p2) { - return (o2.__proto__ = p2), o2; - }), - _setPrototypeOf(o, p) - ); - } - function _getPrototypeOf(o) { - return ( - (_getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function (o2) { - return o2.__proto__ || Object.getPrototypeOf(o2); - }), - _getPrototypeOf(o) - ); - } - function _typeof(obj) { - return ( - typeof Symbol == "function" && typeof Symbol.iterator == "symbol" - ? (_typeof = function (obj2) { - return typeof obj2; - }) - : (_typeof = function (obj2) { - return obj2 && typeof Symbol == "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype - ? "symbol" - : typeof obj2; - }), - _typeof(obj) - ); - } - var inspect = util.inspect, - _require2 = require_errors(), - ERR_INVALID_ARG_TYPE = _require2.codes.ERR_INVALID_ARG_TYPE; - function endsWith(str, search, this_len) { - return ( - (this_len === void 0 || this_len > str.length) && (this_len = str.length), - str.substring(this_len - search.length, this_len) === search - ); - } - function repeat(str, count) { - if (((count = Math.floor(count)), str.length == 0 || count == 0)) return ""; - var maxCount = str.length * count; - for (count = Math.floor(Math.log(count) / Math.log(2)); count; ) (str += str), count--; - return (str += str.substring(0, maxCount - str.length)), str; - } - var blue = "", - green = "", - red = "", - white = "", - kReadableOperator = { - deepStrictEqual: "Expected values to be strictly deep-equal:", - strictEqual: "Expected values to be strictly equal:", - strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', - deepEqual: "Expected values to be loosely deep-equal:", - equal: "Expected values to be loosely equal:", - notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', - notStrictEqual: 'Expected "actual" to be strictly unequal to:', - notStrictEqualObject: 'Expected "actual" not to be reference-equal to "expected":', - notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:', - notEqual: 'Expected "actual" to be loosely unequal to:', - notIdentical: "Values identical but not reference-equal:", - }, - kMaxShortLength = 10; - function copyError(source) { - var keys = Object.keys(source), - target = Object.create(Object.getPrototypeOf(source)); - return ( - keys.forEach(function (key) { - target[key] = source[key]; - }), - Object.defineProperty(target, "message", { - value: source.message, - }), - target - ); - } - function inspectValue(val) { - return inspect(val, { - compact: !1, - customInspect: !1, - depth: 1e3, - maxArrayLength: 1 / 0, - showHidden: !1, - breakLength: 1 / 0, - showProxy: !1, - sorted: !0, - getters: !0, - }); + + return true; + } + + // Check if expected array is a subset of actual array + if (ArrayIsArray(actual) && ArrayIsArray(expected)) { + if (expected.length > actual.length) { + return false; } - function createErrDiff(actual, expected, operator) { - var other = "", - res = "", - lastPos = 0, - end = "", - skipped = !1, - actualInspected = inspectValue(actual), - actualLines = actualInspected.split(` -`), - expectedLines = inspectValue(expected).split(` -`), - i = 0, - indicator = ""; - if ( - (operator === "strictEqual" && - _typeof(actual) === "object" && - _typeof(expected) === "object" && - actual !== null && - expected !== null && - (operator = "strictEqualObject"), - actualLines.length === 1 && expectedLines.length === 1 && actualLines[0] !== expectedLines[0]) - ) { - var inputLength = actualLines[0].length + expectedLines[0].length; - if (inputLength <= kMaxShortLength) { - if ( - (_typeof(actual) !== "object" || actual === null) && - (_typeof(expected) !== "object" || expected === null) && - (actual !== 0 || expected !== 0) - ) - return ( - "".concat( - kReadableOperator[operator], - ` - -`, - ) + - "".concat(actualLines[0], " !== ").concat( - expectedLines[0], - ` -`, - ) - ); - } else if (operator !== "strictEqualObject") { - var maxLength = process.stderr && process.stderr.isTTY ? process.stderr.columns : 80; - if (inputLength < maxLength) { - for (; actualLines[0][i] === expectedLines[0][i]; ) i++; - i > 2 && - ((indicator = ` - `.concat(repeat(" ", i), "^")), - (i = 0)); - } + + if (isDeepEqual === undefined) lazyLoadComparison(); + + // Create a map to count occurrences of each element in the expected array + const expectedCounts = new SafeMap(); + for (const expectedItem of expected) { + let found = false; + for (const { 0: key, 1: count } of expectedCounts) { + if (isDeepStrictEqual(key, expectedItem)) { + MapPrototypeSet(expectedCounts, key, count + 1); + found = true; + break; } } - for ( - var a = actualLines[actualLines.length - 1], b = expectedLines[expectedLines.length - 1]; - a === b && - (i++ < 2 - ? (end = ` - ` - .concat(a) - .concat(end)) - : (other = a), - actualLines.pop(), - expectedLines.pop(), - !(actualLines.length === 0 || expectedLines.length === 0)); - - ) - (a = actualLines[actualLines.length - 1]), (b = expectedLines[expectedLines.length - 1]); - var maxLines = Math.max(actualLines.length, expectedLines.length); - if (maxLines === 0) { - var _actualLines = actualInspected.split(` -`); - if (_actualLines.length > 30) - for (_actualLines[26] = "".concat(blue, "...").concat(white); _actualLines.length > 27; ) _actualLines.pop(); - return "" - .concat( - kReadableOperator.notIdentical, - ` - -`, - ) - .concat( - _actualLines.join(` -`), - ` -`, - ); - } - i > 3 && - ((end = ` -` - .concat(blue, "...") - .concat(white) - .concat(end)), - (skipped = !0)), - other !== "" && - ((end = ` - ` - .concat(other) - .concat(end)), - (other = "")); - var printedLines = 0, - msg = - kReadableOperator[operator] + - ` -` - .concat(green, "+ actual") - .concat(white, " ") - .concat(red, "- expected") - .concat(white), - skippedMsg = " ".concat(blue, "...").concat(white, " Lines skipped"); - for (i = 0; i < maxLines; i++) { - var cur = i - lastPos; - if (actualLines.length < i + 1) - cur > 1 && - i > 2 && - (cur > 4 - ? ((res += ` -` - .concat(blue, "...") - .concat(white)), - (skipped = !0)) - : cur > 3 && - ((res += ` - `.concat(expectedLines[i - 2])), - printedLines++), - (res += ` - `.concat(expectedLines[i - 1])), - printedLines++), - (lastPos = i), - (other += ` -` - .concat(red, "-") - .concat(white, " ") - .concat(expectedLines[i])), - printedLines++; - else if (expectedLines.length < i + 1) - cur > 1 && - i > 2 && - (cur > 4 - ? ((res += ` -` - .concat(blue, "...") - .concat(white)), - (skipped = !0)) - : cur > 3 && - ((res += ` - `.concat(actualLines[i - 2])), - printedLines++), - (res += ` - `.concat(actualLines[i - 1])), - printedLines++), - (lastPos = i), - (res += ` -` - .concat(green, "+") - .concat(white, " ") - .concat(actualLines[i])), - printedLines++; - else { - var expectedLine = expectedLines[i], - actualLine = actualLines[i], - divergingLines = - actualLine !== expectedLine && (!endsWith(actualLine, ",") || actualLine.slice(0, -1) !== expectedLine); - divergingLines && - endsWith(expectedLine, ",") && - expectedLine.slice(0, -1) === actualLine && - ((divergingLines = !1), (actualLine += ",")), - divergingLines - ? (cur > 1 && - i > 2 && - (cur > 4 - ? ((res += ` -` - .concat(blue, "...") - .concat(white)), - (skipped = !0)) - : cur > 3 && - ((res += ` - `.concat(actualLines[i - 2])), - printedLines++), - (res += ` - `.concat(actualLines[i - 1])), - printedLines++), - (lastPos = i), - (res += ` -` - .concat(green, "+") - .concat(white, " ") - .concat(actualLine)), - (other += ` -` - .concat(red, "-") - .concat(white, " ") - .concat(expectedLine)), - (printedLines += 2)) - : ((res += other), - (other = ""), - (cur === 1 || i === 0) && - ((res += ` - `.concat(actualLine)), - printedLines++)); - } - if (printedLines > 20 && i < maxLines - 2) - return ( - "" - .concat(msg) - .concat( - skippedMsg, - ` -`, - ) - .concat( - res, - ` -`, - ) - .concat(blue, "...") - .concat(white) - .concat( - other, - ` -`, - ) + "".concat(blue, "...").concat(white) - ); + if (!found) { + MapPrototypeSet(expectedCounts, expectedItem, 1); } - return "" - .concat(msg) - .concat( - skipped ? skippedMsg : "", - ` -`, - ) - .concat(res) - .concat(other) - .concat(end) - .concat(indicator); } - var AssertionError = /* @__PURE__ */ (function (_Error) { - function AssertionError2(options) { - var _this; - if ((_classCallCheck(this, AssertionError2), _typeof(options) !== "object" || options === null)) - throw new ERR_INVALID_ARG_TYPE("options", "object", options); - var message = options.message, - operator = options.operator, - stackStartFn = options.stackStartFn, - actual = options.actual, - expected = options.expected, - limit = Error.stackTraceLimit; - if (((Error.stackTraceLimit = 0), message != null)) - _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError2).$call(this, String(message))); - else if ( - (process.stderr && - process.stderr.isTTY && - (process.stderr && process.stderr.getColorDepth && process.stderr.getColorDepth() !== 1 - ? ((blue = ""), (green = ""), (white = ""), (red = "")) - : ((blue = ""), (green = ""), (white = ""), (red = ""))), - _typeof(actual) === "object" && - actual !== null && - _typeof(expected) === "object" && - expected !== null && - "stack" in actual && - actual instanceof Error && - "stack" in expected && - expected instanceof Error && - ((actual = copyError(actual)), (expected = copyError(expected))), - operator === "deepStrictEqual" || operator === "strictEqual") - ) - _this = _possibleConstructorReturn( - this, - _getPrototypeOf(AssertionError2).$call(this, createErrDiff(actual, expected, operator)), - ); - else if (operator === "notDeepStrictEqual" || operator === "notStrictEqual") { - var base = kReadableOperator[operator], - res = inspectValue(actual).split(` -`); - if ( - (operator === "notStrictEqual" && - _typeof(actual) === "object" && - actual !== null && - (base = kReadableOperator.notStrictEqualObject), - res.length > 30) - ) - for (res[26] = "".concat(blue, "...").concat(white); res.length > 27; ) res.pop(); - res.length === 1 - ? (_this = _possibleConstructorReturn( - this, - _getPrototypeOf(AssertionError2).$call(this, "".concat(base, " ").concat(res[0])), - )) - : (_this = _possibleConstructorReturn( - this, - _getPrototypeOf(AssertionError2).$call( - this, - "" - .concat( - base, - ` - -`, - ) - .concat( - res.join(` -`), - ` -`, - ), - ), - )); - } else { - var _res = inspectValue(actual), - other = "", - knownOperators = kReadableOperator[operator]; - operator === "notDeepEqual" || operator === "notEqual" - ? ((_res = "" - .concat( - kReadableOperator[operator], - ` - -`, - ) - .concat(_res)), - _res.length > 1024 && (_res = "".concat(_res.slice(0, 1021), "..."))) - : ((other = "".concat(inspectValue(expected))), - _res.length > 512 && (_res = "".concat(_res.slice(0, 509), "...")), - other.length > 512 && (other = "".concat(other.slice(0, 509), "...")), - operator === "deepEqual" || operator === "equal" - ? (_res = "" - .concat( - knownOperators, - ` - -`, - ) - .concat( - _res, - ` - -should equal - -`, - )) - : (other = " ".concat(operator, " ").concat(other))), - (_this = _possibleConstructorReturn( - this, - _getPrototypeOf(AssertionError2).$call(this, "".concat(_res).concat(other)), - )); + + // Create a map to count occurrences of relevant elements in the actual array + for (const actualItem of actual) { + for (const { 0: key, 1: count } of expectedCounts) { + if (isDeepStrictEqual(key, actualItem)) { + if (count === 1) { + MapPrototypeDelete(expectedCounts, key); + } else { + MapPrototypeSet(expectedCounts, key, count - 1); + } + break; } - return ( - (Error.stackTraceLimit = limit), - (_this.generatedMessage = !message), - Object.defineProperty(_assertThisInitialized(_this), "name", { - value: "AssertionError [ERR_ASSERTION]", - enumerable: !1, - writable: !0, - configurable: !0, - }), - (_this.code = "ERR_ASSERTION"), - (_this.actual = actual), - (_this.expected = expected), - (_this.operator = operator), - Error.captureStackTrace && Error.captureStackTrace(_assertThisInitialized(_this), stackStartFn), - _this.stack, - (_this.name = "AssertionError"), - _possibleConstructorReturn(_this) - ); } - AssertionError2.prototype = {}; - _inherits(AssertionError2, _Error); - return ( - _createClass(AssertionError2, [ - { - key: "toString", - value: function () { - return "".concat(this.name, " [").concat(this.code, "]: ").concat(this.message); - }, - }, - { - key: inspect.custom, - value: function (recurseTimes, ctx) { - return inspect( - this, - _objectSpread({}, ctx, { - customInspect: !1, - depth: 0, - }), - ); - }, - }, - ]), - AssertionError2 - ); - })(_wrapNativeSuper(Error)); - module2.exports = AssertionError; - }, -}); - -// assert/build/assert.js -var require_assert = __commonJS({ - "assert/build/assert.js"(exports, module2) { - "use strict"; - function _typeof(obj) { - return ( - typeof Symbol == "function" && typeof Symbol.iterator == "symbol" - ? (_typeof = function (obj2) { - return typeof obj2; - }) - : (_typeof = function (obj2) { - return obj2 && typeof Symbol == "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype - ? "symbol" - : typeof obj2; - }), - _typeof(obj) - ); } - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); + + return !expectedCounts.size; + } + + // Comparison done when at least one of the values is not an object + if (isSpecial(actual) || isSpecial(expected)) { + if (isDeepEqual === undefined) { + lazyLoadComparison(); } + return isDeepStrictEqual(actual, expected); + } + + // Use Reflect.ownKeys() instead of Object.keys() to include symbol properties + const keysExpected = ReflectOwnKeys(expected); + + comparedObjects ??= new SafeWeakSet(); + + // Handle circular references + if (comparedObjects.has(actual)) { + return true; + } + comparedObjects.add(actual); - var _require = require_errors(), - _require$codes = _require.codes, - ERR_AMBIGUOUS_ARGUMENT = _require$codes.ERR_AMBIGUOUS_ARGUMENT, - ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE = _require$codes.ERR_INVALID_ARG_VALUE, - ERR_INVALID_RETURN_VALUE = _require$codes.ERR_INVALID_RETURN_VALUE, - ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS, - AssertionError = require_assertion_error(), - _require2 = util, - inspect = _require2.inspect, - _require$types = util.types, - isPromise = _require$types.isPromise, - isRegExp = _require$types.isRegExp, - objectAssign = Object.assign, - objectIs = Object.is, - errorCache = new Map(); - - var warned = !1, - assert = (module2.exports = ok), - NO_EXCEPTION_SENTINEL = {}; - function innerFail(obj) { - throw obj.message instanceof Error ? obj.message : new AssertionError(obj); + // Check if all expected keys and values match + for (let i = 0; i < keysExpected.length; i++) { + const key = keysExpected[i]; + assert( + ReflectHas(actual, key), + new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }), + ); + if (!compareBranch(actual[key], expected[key], comparedObjects)) { + return false; } - function fail(actual, expected, message, operator, stackStartFn) { - var argsLen = arguments.length, - internalMessage; - if (argsLen === 0) internalMessage = "Failed"; - else if (argsLen === 1) (message = actual), (actual = void 0); - else { - if (warned === !1) { - warned = !0; - var warn = process.emitWarning ? process.emitWarning : console.warn.bind(console); - warn( - "assert.fail() with more than one argument is deprecated. Please use assert.strictEqual() instead or only pass a message.", - "DeprecationWarning", - "DEP0094", - ); + } + + return true; +} + +/** + * The strict equivalence assertion test between two objects + * @param {any} actual + * @param {any} expected + * @param {string | Error} [message] + * @returns {void} + */ +assert.partialDeepStrictEqual = function partialDeepStrictEqual(actual, expected, message) { + // emitExperimentalWarning("assert.partialDeepStrictEqual"); + if (arguments.length < 2) { + throw $ERR_MISSING_ARGS("actual", "expected"); + } + + if (!compareBranch(actual, expected)) { + innerFail({ + actual, + expected, + message, + operator: "partialDeepStrictEqual", + stackStartFn: partialDeepStrictEqual, + }); + } +}; + +class Comparison { + constructor(obj, keys, actual) { + for (const key of keys) { + if (key in obj) { + if ( + actual !== undefined && + typeof actual[key] === "string" && + isRegExp(obj[key]) && + RegExpPrototypeExec(obj[key], actual[key]) !== null + ) { + this[key] = actual[key]; + } else { + this[key] = obj[key]; } - argsLen === 2 && (operator = "!="); } - if (message instanceof Error) throw message; - var errArgs = { + } + } +} + +function compareExceptionKey(actual, expected, key, message, keys, fn) { + if (!(key in actual) || !isDeepStrictEqual(actual[key], expected[key])) { + if (!message) { + // Create placeholder objects to create a nice output. + const a = new Comparison(actual, keys); + const b = new Comparison(expected, keys, actual); + + const err = new AssertionError({ + actual: a, + expected: b, + operator: "deepStrictEqual", + stackStartFn: fn, + }); + err.actual = actual; + err.expected = expected; + err.operator = fn.name; + throw err; + } + innerFail({ + actual, + expected, + message, + operator: fn.name, + stackStartFn: fn, + }); + } +} + +function expectedException(actual, expected, message, fn) { + let generatedMessage = false; + let throwError = false; + + if (typeof expected !== "function") { + // Handle regular expressions. + if (isRegExp(expected)) { + const str = String(actual); + if (RegExpPrototypeExec(expected, str) !== null) return; + + if (!message) { + generatedMessage = true; + message = + "The input did not match the regular expression " + `${inspect(expected)}. Input:\n\n${inspect(str)}\n`; + } + throwError = true; + // Handle primitives properly. + } else if (typeof actual !== "object" || actual === null) { + const err = new AssertionError({ actual, expected, - operator: operator === void 0 ? "fail" : operator, - stackStartFn: stackStartFn || fail, - }; - message !== void 0 && (errArgs.message = message); - var err = new AssertionError(errArgs); - throw (internalMessage && ((err.message = internalMessage), (err.generatedMessage = !0)), err); - } - assert.fail = fail; - assert.AssertionError = AssertionError; - function innerOk(fn, argLen, value, message) { - if (!value) { - var generatedMessage = !1; - if (argLen === 0) (generatedMessage = !0), (message = "No value argument passed to `assert.ok()`"); - else if (message instanceof Error) throw message; - var err = new AssertionError({ - actual: value, - expected: !0, - message, - operator: "==", - stackStartFn: fn, - }); - throw ((err.generatedMessage = generatedMessage), err); + message, + operator: "deepStrictEqual", + stackStartFn: fn, + }); + err.operator = fn.name; + throw err; + } else { + // Handle validation objects. + const keys = ObjectKeys(expected); + // Special handle errors to make sure the name and the message are + // compared as well. + if (expected instanceof Error) { + ArrayPrototypePush(keys, "name", "message"); + } else if (keys.length === 0) { + // throw $ERR_INVALID_ARG_VALUE("error", expected, "may not be an empty object"); + throw $ERR_INVALID_ARG_VALUE(`The argument 'error' may not be an empty object. Received: {}`); } - } - function ok() { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) - args[_key] = arguments[_key]; - innerOk.$apply(void 0, [ok, args.length].concat(args)); - } - assert.ok = ok; - assert.equal = function equal(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - actual != expected && - innerFail({ - actual, - expected, - message, - operator: "==", - stackStartFn: equal, - }); - }; - assert.notEqual = function notEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - actual == expected && - innerFail({ - actual, - expected, - message, - operator: "!=", - stackStartFn: notEqual, - }); - }; - assert.deepEqual = function deepEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - isDeepEqual(actual, expected, false) || - innerFail({ - actual, - expected, - message, - operator: "deepEqual", - stackStartFn: deepEqual, - }); - }; - assert.notDeepEqual = function notDeepEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - isDeepEqual(actual, expected, false) && - innerFail({ - actual, - expected, - message, - operator: "notDeepEqual", - stackStartFn: notDeepEqual, - }); - }; - assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - - isDeepEqual(actual, expected, true) || - innerFail({ - actual, - expected, - message, - operator: "deepStrictEqual", - stackStartFn: deepStrictEqual, - }); - }; - assert.notDeepStrictEqual = notDeepStrictEqual; - function notDeepStrictEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - - isDeepEqual(actual, expected, true) && - innerFail({ - actual, - expected, - message, - operator: "notDeepStrictEqual", - stackStartFn: notDeepStrictEqual, - }); - } - assert.strictEqual = function strictEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - objectIs(actual, expected) || - innerFail({ - actual, - expected, - message, - operator: "strictEqual", - stackStartFn: strictEqual, - }); - }; - assert.notStrictEqual = function notStrictEqual(actual, expected, message) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - objectIs(actual, expected) && - innerFail({ - actual, - expected, - message, - operator: "notStrictEqual", - stackStartFn: notStrictEqual, - }); - }; - var internalMatch = function (actual, expected, message, fn) { - if (arguments.length < 2) throw new ERR_MISSING_ARGS("actual", "expected"); - if (typeof actual !== "string") throw new ERR_INVALID_ARG_TYPE("actual", "string", actual); - if (!isRegExp(expected)) throw new ERR_INVALID_ARG_TYPE("expected", "RegExp", expected); - var match = fn === assert.match; - expected.test(actual) === match || - innerFail({ - actual, - expected, - message, - operator: fn.name, - stackStartFn: fn, - }); - }; - assert.doesNotMatch = function doesNotMatch(actual, expected, message) { - internalMatch(actual, expected, message, doesNotMatch); - }; - assert.match = function match(actual, expected, message) { - internalMatch(actual, expected, message, match); - }; - var Comparison = function Comparison2(obj, keys, actual) { - var _this = this; - _classCallCheck(this, Comparison2), - keys.forEach(function (key) { - key in obj && - (actual !== void 0 && typeof actual[key] == "string" && isRegExp(obj[key]) && obj[key].test(actual[key]) - ? (_this[key] = actual[key]) - : (_this[key] = obj[key])); - }); - }; - Comparison.prototype = {}; - function compareExceptionKey(actual, expected, key, message, keys, fn) { - if (!(key in actual) || !isDeepEqual(actual[key], expected[key], true)) { - if (!message) { - var a = new Comparison(actual, keys), - b = new Comparison(expected, keys, actual), - err = new AssertionError({ - actual: a, - expected: b, - operator: "deepStrictEqual", - stackStartFn: fn, - }); - throw ((err.actual = actual), (err.expected = expected), (err.operator = fn.name), err); + if (isDeepEqual === undefined) lazyLoadComparison(); + for (const key of keys) { + if ( + typeof actual[key] === "string" && + isRegExp(expected[key]) && + RegExpPrototypeExec(expected[key], actual[key]) !== null + ) { + continue; } - innerFail({ - actual, - expected, - message, - operator: fn.name, - stackStartFn: fn, - }); + compareExceptionKey(actual, expected, key, message, keys, fn); } - } - function expectedException(actual, expected, msg, fn) { - if (typeof expected != "function") { - if (isRegExp(expected)) return expected.test(actual); - if (arguments.length === 2) throw new ERR_INVALID_ARG_TYPE("expected", ["Function", "RegExp"], expected); - if (_typeof(actual) !== "object" || actual === null) { - var err = new AssertionError({ - actual, - expected, - message: msg, - operator: "deepStrictEqual", - stackStartFn: fn, - }); - throw ((err.operator = fn.name), err); + return; + } + // Guard instanceof against arrow functions as they don't have a prototype. + // Check for matching Error classes. + } else if (expected.prototype !== undefined && actual instanceof expected) { + return; + } else if (ObjectPrototypeIsPrototypeOf(Error, expected)) { + if (!message) { + generatedMessage = true; + message = "The error is expected to be an instance of " + `"${expected.name}". Received `; + if (isError(actual)) { + const name = actual.constructor?.name || actual.name; + if (expected.name === name) { + message += "an error with identical name but a different prototype."; + } else { + message += `"${name}"`; } - var keys = Object.keys(expected); - if (expected instanceof Error) keys.push("name", "message"); - else if (keys.length === 0) throw new ERR_INVALID_ARG_VALUE("error", expected, "may not be an empty object"); - return ( - keys.forEach(function (key) { - return ( - (typeof actual[key] == "string" && isRegExp(expected[key]) && expected[key].test(actual[key])) || - compareExceptionKey(actual, expected, key, msg, keys, fn) - ); - }), - !0 - ); + if (actual.message) { + message += `\n\nError message:\n\n${actual.message}`; + } + } else { + message += `"${inspect(actual, { depth: -1 })}"`; } - return expected.prototype !== void 0 && actual instanceof expected - ? !0 - : Error.isPrototypeOf(expected) - ? !1 - : expected.$call({}, actual) === !0; } - function getActual(fn) { - if (typeof fn != "function") throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); - try { - fn(); - } catch (e) { - return e; + throwError = true; + } else { + // Check validation functions return value. + const res = ReflectApply(expected, {}, [actual]); + if (res !== true) { + if (!message) { + generatedMessage = true; + const name = expected.name ? `"${expected.name}" ` : ""; + message = `The ${name}validation function is expected to return` + ` "true". Received ${inspect(res)}`; + + if (isError(actual)) { + message += `\n\nCaught error:\n\n${actual}`; + } } - return NO_EXCEPTION_SENTINEL; + throwError = true; } - function checkIsPromise(obj) { - return ( - isPromise(obj) || - (obj !== null && _typeof(obj) === "object" && typeof obj.then == "function" && typeof obj.catch == "function") - ); - } - function waitForActual(promiseFn) { - return Promise.resolve().then(function () { - var resultPromise; - if (typeof promiseFn == "function") { - if (((resultPromise = promiseFn()), !checkIsPromise(resultPromise))) - throw new ERR_INVALID_RETURN_VALUE("instance of Promise", "promiseFn", resultPromise); - } else if (checkIsPromise(promiseFn)) resultPromise = promiseFn; - else throw new ERR_INVALID_ARG_TYPE("promiseFn", ["Function", "Promise"], promiseFn); - return Promise.resolve() - .then(function () { - return resultPromise; - }) - .then(function () { - return NO_EXCEPTION_SENTINEL; - }) - .catch(function (e) { - return e; - }); - }); - } - function expectsError(stackStartFn, actual, error, message) { - if (typeof error == "string") { - if (arguments.length === 4) - throw new ERR_INVALID_ARG_TYPE("error", ["Object", "Error", "Function", "RegExp"], error); - if (_typeof(actual) === "object" && actual !== null) { - if (actual.message === error) - throw new ERR_AMBIGUOUS_ARGUMENT( - "error/message", - 'The error message "'.concat(actual.message, '" is identical to the message.'), - ); - } else if (actual === error) - throw new ERR_AMBIGUOUS_ARGUMENT( - "error/message", - 'The error "'.concat(actual, '" is identical to the message.'), - ); - (message = error), (error = void 0); - } else if (error != null && _typeof(error) !== "object" && typeof error != "function") - throw new ERR_INVALID_ARG_TYPE("error", ["Object", "Error", "Function", "RegExp"], error); - if (actual === NO_EXCEPTION_SENTINEL) { - var details = ""; - error && error.name && (details += " (".concat(error.name, ")")), - (details += message ? ": ".concat(message) : "."); - var fnType = stackStartFn.name === "rejects" ? "rejection" : "exception"; - innerFail({ - actual: void 0, - expected: error, - operator: stackStartFn.name, - message: "Missing expected ".concat(fnType).concat(details), - stackStartFn, - }); + } + + if (throwError) { + const err = new AssertionError({ + actual, + expected, + message, + operator: fn.name, + stackStartFn: fn, + }); + err.generatedMessage = generatedMessage; + throw err; + } +} + +function getActual(fn) { + validateFunction(fn, "fn"); + try { + fn(); + } catch (e) { + return e; + } + return NO_EXCEPTION_SENTINEL; +} + +function checkIsPromise(obj) { + // Accept native ES6 promises and promises that are implemented in a similar + // way. Do not accept thenables that use a function as `obj` and that have no + // `catch` handler. + return ( + isPromise(obj) || + (obj !== null && typeof obj === "object" && typeof obj.then === "function" && typeof obj.catch === "function") + ); +} + +async function waitForActual(promiseFn) { + let resultPromise; + if (typeof promiseFn === "function") { + // Return a rejected promise if `promiseFn` throws synchronously. + resultPromise = promiseFn(); + // Fail in case no promise is returned. + if (!checkIsPromise(resultPromise)) { + throw $ERR_INVALID_RETURN_VALUE("instance of Promise", "promiseFn", resultPromise); + } + } else if (checkIsPromise(promiseFn)) { + resultPromise = promiseFn; + } else { + throw $ERR_INVALID_ARG_TYPE("promiseFn", ["Function", "Promise"], promiseFn); + } + + try { + await resultPromise; + } catch (e) { + return e; + } + return NO_EXCEPTION_SENTINEL; +} + +function expectsError(stackStartFn: Function, actual: unknown, error: unknown, message?: string | Error) { + if (typeof error === "string") { + if (arguments.length === 4) { + throw $ERR_INVALID_ARG_TYPE("error", ["Object", "Error", "Function", "RegExp"], error); + } + if (typeof actual === "object" && actual !== null) { + if ((actual as { message?: unknown }).message === error) { + throw $ERR_AMBIGUOUS_ARGUMENT( + `The "error/message" argument is ambiguous. The error message "${(actual as { message?: unknown }).message}" is identical to the message.`, + ); } - if (error && !expectedException(actual, error, message, stackStartFn)) throw actual; - } - function expectsNoError(stackStartFn, actual, error, message) { - if (actual !== NO_EXCEPTION_SENTINEL) { - if ( - (typeof error == "string" && ((message = error), (error = void 0)), - !error || expectedException(actual, error)) - ) { - var details = message ? ": ".concat(message) : ".", - fnType = stackStartFn.name === "doesNotReject" ? "rejection" : "exception"; - innerFail({ - actual, - expected: error, - operator: stackStartFn.name, - message: - "Got unwanted ".concat(fnType).concat( - details, - ` -`, - ) + 'Actual message: "'.concat(actual && actual.message, '"'), - stackStartFn, - }); - } - throw actual; + if (Object.keys(error).length === 0) { + throw $ERR_INVALID_ARG_VALUE("error", error, "may not be an empty object"); } + } else if (actual === error) { + throw $ERR_AMBIGUOUS_ARGUMENT(`The "error/message" argument is ambiguous. The error "${actual}" is identical to the message.`); + } + message = error; + error = undefined; + } else if (error != null && typeof error !== "object" && typeof error !== "function") { + throw $ERR_INVALID_ARG_TYPE("error", ["Object", "Error", "Function", "RegExp"], error); + } + + if (actual === NO_EXCEPTION_SENTINEL) { + let details = ""; + if ((error as Error | undefined)?.name) { + details += ` (${(error as Error).name})`; + } + details += message ? `: ${message}` : "."; + const fnType = stackStartFn === assert.rejects ? "rejection" : "exception"; + innerFail({ + actual: undefined, + expected: error, + operator: stackStartFn.name, + message: `Missing expected ${fnType}${details}`, + stackStartFn, + }); + } + + if (!error) return; + + expectedException(actual, error, message, stackStartFn); +} + +function hasMatchingError(actual, expected) { + if (typeof expected !== "function") { + if (isRegExp(expected)) { + const str = String(actual); + return RegExpPrototypeExec(expected, str) !== null; + } + throw $ERR_INVALID_ARG_TYPE("expected", ["Function", "RegExp"], expected); + } + // Guard instanceof against arrow functions as they don't have a prototype. + if (expected.prototype !== undefined && actual instanceof expected) { + return true; + } + if (ObjectPrototypeIsPrototypeOf(Error, expected)) { + return false; + } + return ReflectApply(expected, {}, [actual]) === true; +} + +function expectsNoError(stackStartFn, actual, error, message) { + if (actual === NO_EXCEPTION_SENTINEL) return; + + if (typeof error === "string") { + message = error; + error = undefined; + } + + if (!error || hasMatchingError(actual, error)) { + const details = message ? `: ${message}` : "."; + const fnType = stackStartFn === assert.doesNotReject ? "rejection" : "exception"; + innerFail({ + actual, + expected: error, + operator: stackStartFn.name, + message: `Got unwanted ${fnType}${details}\n` + `Actual message: "${actual?.message}"`, + stackStartFn, + }); + } + throw actual; +} + +/** + * Expects the function `promiseFn` to throw an error. + * @param {() => any} promiseFn + * @param {...any} [args] + * @returns {void} + */ +assert.throws = function throws(promiseFn: () => Promise | Promise, ...args: unknown[]): void { + expectsError(throws, getActual(promiseFn), ...args); +}; + +/** + * Expects `promiseFn` function or its value to reject. + * @param {() => Promise} promiseFn + * @param {...any} [args] + * @returns {Promise} + */ +function rejects(block: (() => Promise) | Promise, message?: string | Error): Promise; +function rejects( + block: (() => Promise) | Promise, + error: nodeAssert.AssertPredicate, + message?: string | Error, +): Promise; +assert.rejects = async function rejects(promiseFn: () => Promise, ...args: any[]): Promise { + expectsError(rejects, await waitForActual(promiseFn), ...args); +}; + +/** + * Asserts that the function `fn` does not throw an error. + * @param {() => any} fn + * @param {...any} [args] + * @returns {void} + */ +assert.doesNotThrow = function doesNotThrow(fn: () => Promise, ...args: unknown[]): void { + expectsNoError(doesNotThrow, getActual(fn), ...args); +}; + +/** + * Expects `fn` or its value to not reject. + * @param {() => Promise} fn + * @param {...any} [args] + * @returns {Promise} + */ +assert.doesNotReject = async function doesNotReject(fn: () => Promise, ...argsa: unknown[]): Promise { + expectsNoError(doesNotReject, await waitForActual(fn), ...args); +}; + +/** + * Throws `value` if the value is not `null` or `undefined`. + * @param {any} err + * @returns {void} + */ +assert.ifError = function ifError(err: unknown): void { + if (err !== null && err !== undefined) { + let message = "ifError got unwanted exception: "; + if (typeof err === "object" && typeof err.message === "string") { + if (err.message.length === 0 && err.constructor) { + message += err.constructor.name; + } else { + message += err.message; + } + } else { + message += inspect(err); } - assert.throws = function throws(promiseFn) { - for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) - args[_key2 - 1] = arguments[_key2]; - expectsError.$apply(void 0, [throws, getActual(promiseFn)].concat(args)); - }; - assert.rejects = function rejects(promiseFn) { - for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) - args[_key3 - 1] = arguments[_key3]; - return waitForActual(promiseFn).then(function (result) { - return expectsError.$apply(void 0, [rejects, result].concat(args)); - }); - }; - assert.doesNotThrow = function doesNotThrow(fn) { - for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) - args[_key4 - 1] = arguments[_key4]; - expectsNoError.$apply(void 0, [doesNotThrow, getActual(fn)].concat(args)); - }; - assert.doesNotReject = function doesNotReject(fn) { - for (var _len5 = arguments.length, args = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) - args[_key5 - 1] = arguments[_key5]; - return waitForActual(fn).then(function (result) { - return expectsNoError.$apply(void 0, [doesNotReject, result].concat(args)); - }); - }; - assert.ifError = function ifError(err) { - if (err != null) { - var message = "ifError got unwanted exception: "; - _typeof(err) === "object" && typeof err.message == "string" - ? err.message.length === 0 && err.constructor - ? (message += err.constructor.name) - : (message += err.message) - : (message += inspect(err)); - var newErr = new AssertionError({ - actual: err, - expected: null, - operator: "ifError", - message, - stackStartFn: ifError, - }), - origStack = err.stack; - if (typeof origStack == "string") { - var tmp2 = origStack.split(` -`); - tmp2.shift(); - for ( - var tmp1 = newErr.stack.split(` -`), - i = 0; - i < tmp2.length; - i++ - ) { - var pos = tmp1.indexOf(tmp2[i]); - if (pos !== -1) { - tmp1 = tmp1.slice(0, pos); - break; - } + + const newErr = new AssertionError({ + actual: err, + expected: null, + operator: "ifError", + message, + stackStartFn: ifError, + }); + + // Make sure we actually have a stack trace! + const origStack = err.stack; + + if (typeof origStack === "string") { + // This will remove any duplicated frames from the error frames taken + // from within `ifError` and add the original error frames to the newly + // created ones. + const origStackStart = StringPrototypeIndexOf(origStack, "\n at"); + if (origStackStart !== -1) { + const originalFrames = StringPrototypeSplit(StringPrototypeSlice(origStack, origStackStart + 1), "\n"); + // Filter all frames existing in err.stack. + let newFrames = StringPrototypeSplit(newErr.stack, "\n"); + for (const errFrame of originalFrames) { + // Find the first occurrence of the frame. + const pos = ArrayPrototypeIndexOf(newFrames, errFrame); + if (pos !== -1) { + // Only keep new frames. + newFrames = ArrayPrototypeSlice(newFrames, 0, pos); + break; } - newErr.stack = "" - .concat( - tmp1.join(` -`), - ` -`, - ) - .concat( - tmp2.join(` -`), - ); } - throw newErr; + const stackStart = ArrayPrototypeJoin(newFrames, "\n"); + const stackEnd = ArrayPrototypeJoin(originalFrames, "\n"); + newErr.stack = `${stackStart}\n${stackEnd}`; } - }; - function strict() { - for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) - args[_key6] = arguments[_key6]; - innerOk.$apply(void 0, [strict, args.length].concat(args)); } - assert.strict = objectAssign(strict, assert, { - equal: assert.strictEqual, - deepEqual: assert.deepStrictEqual, - notEqual: assert.notStrictEqual, - notDeepEqual: assert.notDeepStrictEqual, + + throw newErr; + } +}; + +function internalMatch(string, regexp, message, fn) { + if (!isRegExp(regexp)) { + throw $ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp); + } + const match = fn === assert.match; + if (typeof string !== "string" || (RegExpPrototypeExec(regexp, string) !== null) !== match) { + if (message instanceof Error) { + throw message; + } + + const generatedMessage = !message; + + // 'The input was expected to not match the regular expression ' + + message ||= + typeof string !== "string" + ? 'The "string" argument must be of type string. Received type ' + `${typeof string} (${inspect(string)})` + : (match + ? "The input did not match the regular expression " + : "The input was expected to not match the regular expression ") + + `${inspect(regexp)}. Input:\n\n${inspect(string)}\n`; + const err = new AssertionError({ + actual: string, + expected: regexp, + message, + operator: fn.name, + stackStartFn: fn, }); - assert.strict.strict = assert.strict; - }, -}); -var assert_module = require_assert(); + err.generatedMessage = generatedMessage; + throw err; + } +} + +/** + * Expects the `string` input to match the regular expression. + * @param {string} string + * @param {RegExp} regexp + * @param {string | Error} [message] + * @returns {void} + */ +assert.match = function match(string, regexp, message) { + internalMatch(string, regexp, message, match); +}; -function CallTracker() { - throw new Error("CallTracker is not supported yet"); +/** + * Expects the `string` input not to match the regular expression. + * @param {string} string + * @param {RegExp} regexp + * @param {string | Error} [message] + * @returns {void} + */ +assert.doesNotMatch = function doesNotMatch(string, regexp, message) { + internalMatch(string, regexp, message, doesNotMatch); +}; + +assert.CallTracker = deprecate(CallTracker, "assert.CallTracker is deprecated.", "DEP0173"); +// assert.CallTracker = CallTracker + +/** + * Expose a strict only variant of assert. + * @param {...any} args + * @returns {void} + */ +function strict(...args) { + innerOk(strict, args.length, ...args); } -assert_module["CallTracker"] = CallTracker; +assert.strict = ObjectAssign(strict, assert, { + equal: assert.strictEqual, + deepEqual: assert.deepStrictEqual, + notEqual: assert.notStrictEqual, + notDeepEqual: assert.notDeepStrictEqual, +}); -export default assert_module; +assert.strict.strict = assert.strict; diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts index f8f20089a1d2a5..a25c9cd86c2fd7 100644 --- a/test/js/node/harness.ts +++ b/test/js/node/harness.ts @@ -262,3 +262,123 @@ export function createTest(path: string) { declare namespace Bun { function jest(path: string): typeof import("bun:test"); } + +if (Bun.main.includes("node/test/parallel")) { + function createMockNodeTestModule() { + + interface TestError extends Error { + testStack: string[]; + } + type Context = { + filename: string; + testStack: string[]; + failures: Error[]; + successes: number; + addFailure(err: unknown): TestError; + recordSuccess(): void; + } + const contexts: Record = {} + + // @ts-ignore + let activeSuite: Context = undefined; + + function createContext(key: string): Context { + return { + filename: key, // duplicate for ease-of-use + // entered each time describe, it, etc is called + testStack: [], + failures: [], + successes: 0, + addFailure(err: unknown) { + const error: TestError = (err instanceof Error ? err : new Error(err as any)) as any; + error.testStack = this.testStack; + const testMessage = `Test failed: ${this.testStack.join(" > ")}`; + error.message = testMessage + "\n" + error.message; + if (!error.stack) Error.captureStackTrace(error); + + this.failures.push(error); + console.error(error); + return error; + }, + recordSuccess() { + const fullname = this.testStack.join(" > "); + console.log("✅ Test passed:", fullname); + this.successes++; + } + } + } + + function getContext() { + const key: string = Bun.main;// module.parent?.filename ?? require.main?.filename ?? __filename; + activeSuite = (contexts[key] ??= createContext(key)); + return activeSuite + } + + function test(label: string | Function, fn?: Function | undefined) { + if (typeof fn !== "function" && typeof label === "function") { + fn = label; + label = fn.name; + } + if (typeof label !== "string" && typeof fn !== "function") { + throw new TypeError(`First argument to test() must be a string or a function. Got ${typeof label}, ${typeof fn}`); + } + const ctx = getContext(); + if (!ctx) throw new Error("invarian violation: test context is undefined."); + try { + ctx.testStack.push(label as string); + const res = fn(); + if (res instanceof Promise) { + throw new Error("test() does not support async functions right now."); + process.exit(1); + } + ctx.recordSuccess(); + } catch (err) { + ctx.addFailure(err); + } finally { + ctx.testStack.pop(); + } + } + + function describe(labelOrFn: string | Function, maybeFn?: Function) { + const [label, fn] = (typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn]); + if (typeof fn !== "function") throw new TypeError("Second argument to describe() must be a function."); + + getContext().testStack.push(label); + try { + fn(); + } catch (e) { + getContext().addFailure(e); + throw e + } finally { + getContext().testStack.pop(); + } + + const failures = getContext().failures.length; + const successes = getContext().successes; + console.error(`describe("${label}") finished with ${successes} passed and ${failures} failed tests.`); + if (failures > 0) { + throw new Error(`${failures} tests failed.`); + } + + } + + return { + test, + describe, + } + + } + + require.cache["node:test"] ??= { + exports: createMockNodeTestModule(), + loaded: true, + isPreloading: false, + id: "node:test", + parent: require.main, + filename: "node:test", + children: [], + path: "node:test", + paths: [], + require, + }; +} diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index 9a044595e82f34..2edb6d327a55e8 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -879,7 +879,7 @@ function runWithInvalidFD(func) { // A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. function invalidArgTypeHelper(input) { if (input == null) { - return ` Received ${input}`; + return ` Received: ${input}`; } if (typeof input === 'function') { return ` Received function ${input.name}`; @@ -890,11 +890,14 @@ function invalidArgTypeHelper(input) { } return ` Received ${inspect(input, { depth: -1 })}`; } + if (typeof input === 'string') { + return ` Received: "${input}"`; + } let inspected = inspect(input, { colors: false }); if (inspected.length > 28) { inspected = `${inspected.slice(inspected, 0, 25)}...`; } - return ` Received type ${typeof input} (${inspected})`; + return ` Received: ${inspected}`; } function skipIfDumbTerminal() { diff --git a/test/js/node/test/parallel/test-assert.js b/test/js/node/test/parallel/test-assert.js new file mode 100644 index 00000000000000..6672d30cfba1ad --- /dev/null +++ b/test/js/node/test/parallel/test-assert.js @@ -0,0 +1,1602 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const assert = require('node:assert'); + +require('../../harness'); + +const {invalidArgTypeHelper} = require('../common'); +const {inspect} = require('util'); +const {test} = require('bun:test'); +const vm = require('vm'); +// const { createTest } = require('node-harness'); +// const { test } = createTest(__filename); + +// Disable colored output to prevent color codes from breaking assertion +// message comparisons. This should only be an issue when process.stdout +// is a TTY. +if (process.stdout.isTTY) { + process.env.NODE_DISABLE_COLORS = '1'; +} + +const strictEqualMessageStart = 'Expected values to be strictly equal:\n'; +const start = 'Expected values to be strictly deep-equal:'; +const actExp = '+ actual - expected'; + +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-restricted-properties */ + +test('some basics', () => { + assert.ok(assert.AssertionError.prototype instanceof Error, + 'assert.AssertionError instanceof Error'); + + assert.throws(() => assert(false), assert.AssertionError, 'ok(false)'); + assert.throws(() => assert.ok(false), assert.AssertionError, 'ok(false)'); + assert(true); + assert('test', 'ok(\'test\')'); + assert.ok(true); + assert.ok('test'); + assert.throws(() => assert.equal(true, false), + assert.AssertionError, 'equal(true, false)'); + assert.equal(null, null); + assert.equal(undefined, undefined); + assert.equal(null, undefined); + assert.equal(true, true); + assert.equal(2, '2'); + assert.notEqual(true, false); + assert.notStrictEqual(2, '2'); +}); + +test('Throw message if the message is instanceof Error', () => { + let threw = false; + try { + assert.ok(false, new Error('ok(false)')); + } catch (e) { + threw = true; + assert.ok(e instanceof Error); + } + assert.ok(threw, 'Error: ok(false)'); +}); + +test('Errors created in different contexts are handled as any other custom error', () => { + assert('createContext' in vm, 'vm.createContext is available'); + const context = vm.createContext(); + const error = vm.runInContext('new SyntaxError("custom error")', context); + + assert.throws(() => assert(false, error), { + message: 'custom error', + name: 'SyntaxError' + }); +}); + +test('assert.throws()', () => { + assert.throws(() => assert.notEqual(true, true), + assert.AssertionError, 'notEqual(true, true)'); + + assert.throws(() => assert.strictEqual(2, '2'), + assert.AssertionError, 'strictEqual(2, \'2\')'); + + assert.throws(() => assert.strictEqual(null, undefined), + assert.AssertionError, 'strictEqual(null, undefined)'); + + assert.throws( + () => assert.notStrictEqual(2, 2), + { + message: 'Expected "actual" to be strictly unequal to: 2', + name: 'AssertionError' + } + ); + + assert.throws( + () => assert.notStrictEqual('a '.repeat(30), 'a '.repeat(30)), + { + message: 'Expected "actual" to be strictly unequal to:\n\n' + + `'${'a '.repeat(30)}'`, + name: 'AssertionError' + } + ); + + assert.throws( + () => assert.notEqual(1, 1), + { + message: '1 != 1', + operator: '!=' + } + ); + + // Testing the throwing. + function thrower(errorConstructor) { + throw new errorConstructor({}); + } + + // The basic calls work. + assert.throws(() => thrower(assert.AssertionError), assert.AssertionError, 'message'); + assert.throws(() => thrower(assert.AssertionError), assert.AssertionError); + assert.throws(() => thrower(assert.AssertionError)); + + // If not passing an error, catch all. + assert.throws(() => thrower(TypeError)); + + // When passing a type, only catch errors of the appropriate type. + assert.throws( + () => assert.throws(() => thrower(TypeError), assert.AssertionError), + { + generatedMessage: true, + actual: new TypeError({}), + expected: assert.AssertionError, + code: 'ERR_ASSERTION', + name: 'AssertionError', + operator: 'throws', + message: 'The error is expected to be an instance of "AssertionError". ' + + 'Received "TypeError"\n\nError message:\n\n[object Object]' + } + ); + + // doesNotThrow should pass through all errors. + { + let threw = false; + try { + assert.doesNotThrow(() => thrower(TypeError), assert.AssertionError); + } catch (e) { + threw = true; + assert.ok(e instanceof TypeError); + } + assert(threw, 'assert.doesNotThrow with an explicit error is eating extra errors'); + } + + // Key difference is that throwing our correct error makes an assertion error. + { + let threw = false; + try { + assert.doesNotThrow(() => thrower(TypeError), TypeError); + } catch (e) { + threw = true; + assert.ok(e instanceof assert.AssertionError); + assert.ok(!e.stack.includes('at Function.doesNotThrow')); + } + assert.ok(threw, 'assert.doesNotThrow is not catching type matching errors'); + } + + assert.throws( + () => assert.doesNotThrow(() => thrower(Error), 'user message'), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + operator: 'doesNotThrow', + message: 'Got unwanted exception: user message\n' + + 'Actual message: "[object Object]"' + } + ); + + assert.throws( + () => assert.doesNotThrow(() => thrower(Error)), + { + code: 'ERR_ASSERTION', + message: 'Got unwanted exception.\nActual message: "[object Object]"' + } + ); + + assert.throws( + () => assert.doesNotThrow(() => thrower(Error), /\[[a-z]{6}\s[A-z]{6}\]/g, 'user message'), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + operator: 'doesNotThrow', + message: 'Got unwanted exception: user message\n' + + 'Actual message: "[object Object]"' + } + ); + + // Make sure that validating using constructor really works. + { + let threw = false; + try { + assert.throws( + () => { + throw ({}); // eslint-disable-line no-throw-literal + }, + Array + ); + } catch { + threw = true; + } + assert.ok(threw, 'wrong constructor validation'); + } + + // Use a RegExp to validate the error message. + { + assert.throws(() => thrower(TypeError), /\[object Object\]/); + + const symbol = Symbol('foo'); + assert.throws(() => { + throw symbol; + }, /foo/); + + assert.throws(() => { + assert.throws(() => { + throw symbol; + }, /abc/); + }, { + message: 'The input did not match the regular expression /abc/. ' + + "Input:\n\n'Symbol(foo)'\n", + code: 'ERR_ASSERTION', + operator: 'throws', + actual: symbol, + expected: /abc/ + }); + } + + // Use a fn to validate the error object. + assert.throws(() => thrower(TypeError), (err) => { + if ((err instanceof TypeError) && /\[object Object\]/.test(err)) { + return true; + } + }); + + // https://github.com/nodejs/node/issues/3188 + { + let actual; + assert.throws( + () => { + const ES6Error = class extends Error {}; + const AnotherErrorType = class extends Error {}; + + assert.throws(() => { + actual = new AnotherErrorType('foo'); + throw actual; + }, ES6Error); + }, + (err) => { + assert.strictEqual( + err.message, + 'The error is expected to be an instance of "ES6Error". ' + + 'Received "AnotherErrorType"\n\nError message:\n\nfoo' + ); + assert.strictEqual(err.actual, actual); + return true; + } + ); + } + + assert.throws( + () => assert.strictEqual(new Error('foo'), new Error('foobar')), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected "actual" to be reference-equal to "expected":\n' + + '+ actual - expected\n' + + '\n' + + '+ [Error: foo] {\n' + + '+ originalColumn: 53,\n' + + '- [Error: foobar] {\n' + + '- originalColumn: 71,\n' + + ' originalLine: 184\n' + + ' }\n', + } + ); +}); + +test('Check messages from assert.throws()', () => { + const noop = () => {}; + assert.throws( + () => {assert.throws((noop));}, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception.', + operator: 'throws', + actual: undefined, + expected: undefined + }); + + assert.throws( + () => {assert.throws(noop, TypeError);}, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception (TypeError).', + actual: undefined, + expected: TypeError + }); + + assert.throws( + () => {assert.throws(noop, 'fhqwhgads');}, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception: fhqwhgads', + actual: undefined, + expected: undefined + }); + + assert.throws( + () => {assert.throws(noop, TypeError, 'fhqwhgads');}, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception (TypeError): fhqwhgads', + actual: undefined, + expected: TypeError + }); + + let threw = false; + try { + assert.throws(noop); + } catch (e) { + threw = true; + assert.ok(e instanceof assert.AssertionError); + assert.ok(!e.stack.includes('at Function.throws')); + } + assert.ok(threw); +}); + +test('Test assertion messages', () => { + const circular = {y: 1}; + circular.x = circular; + + function testAssertionMessage(actual, expected, msg) { + assert.throws( + () => assert.strictEqual(actual, ''), + { + generatedMessage: true, + message: msg || `Expected values to be strictly equal:\n\n${expected} !== ''\n` + } + ); + } + + function testLongAssertionMessage(actual, expected) { + testAssertionMessage(actual, expected, 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + `+ ${expected}\n` + + "- ''\n"); + } + + function testShortAssertionMessage(actual, expected) { + testAssertionMessage(actual, expected, strictEqualMessageStart + `\n${inspect(actual)} !== ''\n`); + } + + testShortAssertionMessage(null, 'null'); + testShortAssertionMessage(true, 'true'); + testShortAssertionMessage(false, 'false'); + testShortAssertionMessage(100, '100'); + testShortAssertionMessage(NaN, 'NaN'); + testShortAssertionMessage(Infinity, 'Infinity'); + testShortAssertionMessage('a', '\'a\''); + testShortAssertionMessage('foo', '\'foo\''); + testShortAssertionMessage(0, '0'); + testShortAssertionMessage(Symbol(), 'Symbol()'); + testShortAssertionMessage(undefined, 'undefined'); + testShortAssertionMessage(-Infinity, '-Infinity'); + testShortAssertionMessage([], '[]'); + testShortAssertionMessage({}, '{}'); + testAssertionMessage(/a/, '/a/'); + testAssertionMessage(/abc/gim, '/abc/gim'); + testLongAssertionMessage(function f() {}, '[Function: f]'); + testLongAssertionMessage(function () {}, '[Function (anonymous)]'); + + assert.throws( + () => assert.strictEqual([1, 2, 3], ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ [\n' + + '+ 1,\n' + + '+ 2,\n' + + '+ 3\n' + + '+ ]\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual(circular, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ x: [Circular *1],\n' + + '+ y: 1\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual({a: undefined, b: null}, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ a: undefined,\n' + + '+ b: null\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual({a: NaN, b: Infinity, c: -Infinity}, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ a: NaN,\n' + + '+ b: Infinity,\n' + + '+ c: -Infinity\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); + + // https://github.com/nodejs/node-v0.x-archive/issues/5292 + assert.throws( + () => assert.strictEqual(1, 2), + { + message: 'Expected values to be strictly equal:\n\n1 !== 2\n', + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual(1, 2, 'oh no'), + { + message: 'oh no\n\n1 !== 2\n', + generatedMessage: false + } + ); +}); + +test('Custom errors', () => { + let threw = false; + const rangeError = new RangeError('my range'); + + // Verify custom errors. + try { + assert.strictEqual(1, 2, rangeError); + } catch (e) { + assert.strictEqual(e, rangeError); + threw = true; + assert.ok(e instanceof RangeError, 'Incorrect error type thrown'); + } + assert.ok(threw); + threw = false; + + // Verify AssertionError is the result from doesNotThrow with custom Error. + try { + assert.doesNotThrow(() => { + throw new TypeError('wrong type'); + }, TypeError, rangeError); + } catch (e) { + threw = true; + // assert.ok(e.message.includes(rangeError.message)); + assert.ok(e.actual instanceof TypeError); + assert.equal(e.expected, TypeError); + assert.ok(e instanceof assert.AssertionError); + assert.ok(!e.stack.includes('doesNotThrow'), e); + } + assert.ok(threw); +}); + +test('Verify that throws() and doesNotThrow() throw on non-functions', () => { + const testBlockTypeError = (method, fn) => { + assert.throws( + () => method(fn), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + // message: 'The "fn" argument must be of type function.' + + // invalidArgTypeHelper(fn) + } + ); + }; + + testBlockTypeError(assert.throws, 'string'); + testBlockTypeError(assert.doesNotThrow, 'string'); + testBlockTypeError(assert.throws, 1); + testBlockTypeError(assert.doesNotThrow, 1); + testBlockTypeError(assert.throws, true); + testBlockTypeError(assert.doesNotThrow, true); + testBlockTypeError(assert.throws, false); + testBlockTypeError(assert.doesNotThrow, false); + testBlockTypeError(assert.throws, []); + testBlockTypeError(assert.doesNotThrow, []); + testBlockTypeError(assert.throws, {}); + testBlockTypeError(assert.doesNotThrow, {}); + testBlockTypeError(assert.throws, /foo/); + testBlockTypeError(assert.doesNotThrow, /foo/); + testBlockTypeError(assert.throws, null); + testBlockTypeError(assert.doesNotThrow, null); + testBlockTypeError(assert.throws, undefined); + testBlockTypeError(assert.doesNotThrow, undefined); +}); + +test('https://github.com/nodejs/node/issues/3275', () => { + // eslint-disable-next-line no-throw-literal + assert.throws(() => {throw 'error';}, (err) => err === 'error'); + assert.throws(() => {throw new Error();}, (err) => err instanceof Error); +}); + +test('Long values should be truncated for display', () => { + assert.throws(() => { + assert.strictEqual('A'.repeat(1000), ''); + }, (err) => { + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.message, + `${strictEqualMessageStart}+ actual - expected\n\n` + + `+ '${'A'.repeat(1000)}'\n- ''\n`); + assert.strictEqual(err.actual.length, 1000); + assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`)); + return true; + }); +}); + +test('Output that extends beyond 10 lines should also be truncated for display', () => { + const multilineString = 'fhqwhgads\n'.repeat(15); + assert.throws(() => { + assert.strictEqual(multilineString, ''); + }, (err) => { + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.message.split('\n').length, 20); + assert.strictEqual(err.actual.split('\n').length, 16); + assert.ok(inspect(err).includes( + "actual: 'fhqwhgads\\n' +\n" + + " 'fhqwhgads\\n' +\n".repeat(9) + + " '...'")); + return true; + }); +}); + +test('Bad args to AssertionError constructor should throw TypeError.', () => { + const args = [1, true, false, '', null, Infinity, Symbol('test'), undefined]; + for (const input of args) { + assert.throws( + () => new assert.AssertionError(input), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object.' + + invalidArgTypeHelper(input) + }); + } +}); + +test('NaN is handled correctly', () => { + assert.equal(NaN, NaN); + assert.throws( + () => assert.notEqual(NaN, NaN), + assert.AssertionError + ); +}); + +test('Test strict assert', () => { + const {strict} = require('assert'); + + strict.throws(() => strict.equal(1, true), strict.AssertionError); + strict.notEqual(0, false); + strict.throws(() => strict.deepEqual(1, true), strict.AssertionError); + strict.notDeepEqual(0, false); + strict.equal(strict.strict, strict.strict.strict); + strict.equal(strict.equal, strict.strictEqual); + strict.equal(strict.deepEqual, strict.deepStrictEqual); + strict.equal(strict.notEqual, strict.notStrictEqual); + strict.equal(strict.notDeepEqual, strict.notDeepStrictEqual); + strict.equal(Object.keys(strict).length, Object.keys(assert).length); + strict(7); + strict.throws( + () => strict(...[]), + { + message: 'No value argument passed to `assert.ok()`', + name: 'AssertionError', + generatedMessage: true + } + ); + strict.throws( + () => assert(), + { + message: 'No value argument passed to `assert.ok()`', + name: 'AssertionError' + } + ); + + // Test setting the limit to zero and that assert.strict works properly. + const tmpLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + strict.throws( + () => { + strict.ok( + typeof 123 === 'string' + ); + }, + { + code: 'ERR_ASSERTION', + constructor: strict.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // "strict.ok(\n typeof 123 === 'string'\n )\n" + } + ); + Error.stackTraceLimit = tmpLimit; + + // Test error diffs. + let message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' [\n' + + ' [\n' + + ' 1,\n' + + ' 2,\n' + + '+ 3\n' + + "- '3'\n" + + ' ]\n' + + ' ],\n' + + ' 4,\n' + + ' 5\n' + + ' ]\n'; + strict.throws( + () => strict.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]), + {message}); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '... Skipped lines\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + ' 0,\n' + + '...\n' + + ' 1,\n' + + '+ 1\n' + + ' ]\n'; + strict.throws( + () => strict.deepEqual( + [1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1]), + {message}); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 2,\n' + + ' 3,\n' + + ' 4,\n' + + ' 5,\n' + + '+ 6,\n' + + '- 9,\n' + + ' 7\n' + + ' ]\n'; + + assert.throws( + () => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 9, 7]), + {message} + ); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 2,\n' + + ' 3,\n' + + ' 4,\n' + + ' 5,\n' + + ' 6,\n' + + '+ 7,\n' + + '- 9,\n' + + ' 8\n' + + ' ]\n'; + + assert.throws( + () => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 9, 8]), + {message} + ); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '... Skipped lines\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 2,\n' + + ' 3,\n' + + ' 4,\n' + + '...\n' + + ' 7,\n' + + '+ 8,\n' + + '- 0,\n' + + ' 9\n' + + ' ]\n'; + + assert.throws( + () => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 0, 9]), + {message} + ); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' 1,\n' + + '+ 2,\n' + + ' 1,\n' + + ' 1,\n' + + '- 1,\n' + + ' 0,\n' + + ' 1,\n' + + '+ 1\n' + + ' ]\n'; + strict.throws( + () => strict.deepEqual( + [1, 2, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1]), + {message}); + + message = [ + start, + actExp, + '', + '+ [', + '+ 1,', + '+ 2,', + '+ 1', + '+ ]', + '- undefined\n', + ].join('\n'); + strict.throws( + () => strict.deepEqual([1, 2, 1], undefined), + {message}); + + message = [ + start, + actExp, + '', + ' [', + '+ 1,', + ' 2,', + ' 1', + ' ]\n', + ].join('\n'); + strict.throws( + () => strict.deepEqual([1, 2, 1], [2, 1]), + {message}); + + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + '+ 1,\n'.repeat(10) + + '+ 3\n' + + '- 2,\n'.repeat(11) + + '- 4,\n' + + '- 4,\n' + + '- 4\n' + + ' ]\n'; + strict.throws( + () => strict.deepEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4]), + {message}); + + const obj1 = {}; + const obj2 = {loop: 'forever'}; + obj2[inspect.custom] = () => '{}'; + // No infinite loop and no custom inspect. + strict.throws(() => strict.deepEqual(obj1, obj2), { + message: `${start}\n` + + `${actExp}\n` + + '\n' + + '+ {}\n' + + '- {\n' + + '- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' + + "- loop: 'forever'\n" + + '- }\n' + }); + + // notDeepEqual tests + strict.throws( + () => strict.notDeepEqual([1], [1]), + { + message: 'Expected "actual" not to be strictly deep-equal to:\n\n' + + '[\n 1\n]\n' + } + ); + + message = 'Expected "actual" not to be strictly deep-equal to:' + + `\n\n[${'\n 1,'.repeat(45)}\n...\n`; + const data = Array(51).fill(1); + strict.throws( + () => strict.notDeepEqual(data, data), + {message}); + +}); + +test('Additional asserts', () => { + assert.throws( + () => assert.ok(null), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert.ok(null)\n' + } + ); + assert.throws( + () => { + // This test case checks if `try` left brace without a line break + // before the assertion causes any wrong assertion message. + // Therefore, don't reformat the following code. + // Refs: https://github.com/nodejs/node/issues/30872 + try { + assert.ok(0); // eslint-disable-line no-useless-catch, @stylistic/js/brace-style + } catch (err) { + throw err; + } + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert.ok(0)\n' + } + ); + assert.throws( + () => { + try { + throw new Error(); + // This test case checks if `catch` left brace without a line break + // before the assertion causes any wrong assertion message. + // Therefore, don't reformat the following code. + // Refs: https://github.com/nodejs/node/issues/30872 + } catch (err) {assert.ok(0);} // eslint-disable-line no-unused-vars + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert.ok(0)\n' + } + ); + assert.throws( + () => { + // This test case checks if `function` left brace without a line break + // before the assertion causes any wrong assertion message. + // Therefore, don't reformat the following code. + // Refs: https://github.com/nodejs/node/issues/30872 + function test() { + assert.ok(0); // eslint-disable-line @stylistic/js/brace-style + } + test(); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert.ok(0)\n' + } + ); + assert.throws( + () => assert(typeof 123n === 'string'), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // "assert(typeof 123n === 'string')\n" + } + ); + + assert.throws( + () => assert(false, Symbol('foo')), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: false, + message: 'Symbol(foo)' + } + ); + + assert.throws( + () => { + assert.strictEqual((() => 'string')(), 123 instanceof + Buffer); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'Expected values to be strictly equal:\n\n\'string\' !== false\n' + } + ); + + assert.throws( + () => { + assert.strictEqual((() => 'string')(), 123 instanceof + Buffer); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'Expected values to be strictly equal:\n\n\'string\' !== false\n' + } + ); + + /* eslint-disable @stylistic/js/indent */ + assert.throws(() => { + assert.strictEqual(( + () => 'string')(), 123 instanceof + Buffer); + }, { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'Expected values to be strictly equal:\n\n\'string\' !== false\n' + } + ); + /* eslint-enable @stylistic/js/indent */ + + assert.throws( + () => { + assert(true); assert(null, undefined); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert(null, undefined)\n' + } + ); + + assert.throws( + () => { + assert + .ok(null, undefined); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'ok(null, undefined)\n' + } + ); + + assert.throws( + // eslint-disable-next-line dot-notation, @stylistic/js/quotes + () => assert['ok']["apply"](null, [0]), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert[\'ok\']["apply"](null, [0])\n' + } + ); + + assert.throws( + () => { + const wrapper = (fn, value) => fn(value); + wrapper(assert, false); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n fn(value)\n' + } + ); + + assert.throws( + () => assert.ok.call(null, 0), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + // message: 'The expression evaluated to a falsy value:\n\n ' + + // 'assert.ok.call(null, 0)\n', + generatedMessage: true + } + ); + + assert.throws( + () => assert.ok.call(null, 0, 'test'), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'test', + generatedMessage: false + } + ); + + // Works in eval. + assert.throws( + () => new Function('assert', 'assert(1 === 2);')(assert), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'The expression evaluated to a falsy value:\n\n assert.ok(false)\n', + } + ); + assert.throws( + () => eval('console.log("FOO");\nassert.ok(1 === 2);'), + { + code: 'ERR_ASSERTION', + message: 'The expression evaluated to a falsy value:\n\n assert.ok(false)\n', + } + ); + + assert.throws( + () => assert.throws(() => {}, 'Error message', 'message'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "error" argument must be of type Object, Error, Function or RegExp. Received: "Error message"', + } + ); + + const inputs = [1, false, Symbol()]; + for (const input of inputs) { + assert.throws( + () => assert.throws(() => {}, input), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "error" argument must be of type Object, Error, Function or RegExp.' + invalidArgTypeHelper(input) + + } + ); + } +}); + +test('Throws accepts objects', () => { + assert.throws(() => { + // eslint-disable-next-line no-constant-binary-expression + assert.ok((() => Boolean('' === false))()); + }, { + code: 'ERR_ASSERTION', + // message: 'The expression evaluated to a falsy value:\n\n' + + // " assert.ok((() => Boolean('\\u0001' === false))())\n" + }); + + const errFn = () => { + const err = new TypeError('Wrong value'); + err.code = 404; + throw err; + }; + const errObj = { + name: 'TypeError', + message: 'Wrong value' + }; + assert.throws(errFn, errObj); + + errObj.code = 404; + assert.throws(errFn, errObj); + + // Fail in case a expected property is undefined and not existent on the + // error. + errObj.foo = undefined; + assert.throws( + () => assert.throws(errFn, errObj), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + ' code: 404,\n' + + '- foo: undefined,\n' + + " message: 'Wrong value',\n" + + " name: 'TypeError'\n" + + ' }\n' + } + ); + + // Show multiple wrong properties at the same time. + errObj.code = '404'; + assert.throws( + () => assert.throws(errFn, errObj), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + '+ code: 404,\n' + + "- code: '404',\n" + + '- foo: undefined,\n' + + " message: 'Wrong value',\n" + + " name: 'TypeError'\n" + + ' }\n' + } + ); + + assert.throws( + () => assert.throws(() => {throw new Error();}, {foo: 'bar'}, 'foobar'), + { + constructor: assert.AssertionError, + code: 'ERR_ASSERTION', + message: 'foobar' + } + ); + + assert.throws( + () => assert.doesNotThrow(() => {throw new Error();}, {foo: 'bar'}), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "expected" argument must be of type Function or ' + + 'RegExp. Received: [object Object]' + } + ); + + assert.throws(() => {throw new Error('e');}, new Error('e')); + assert.throws( + () => assert.throws(() => {throw new TypeError('e');}, new Error('e')), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + " message: 'e',\n" + + "+ name: 'TypeError'\n" + + "- name: 'Error'\n" + + ' }\n' + } + ); + assert.throws( + () => assert.throws(() => {throw new Error('foo');}, new Error('')), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + generatedMessage: true, + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + "+ message: 'foo',\n" + + "- message: '',\n" + + " name: 'Error'\n" + + ' }\n' + } + ); + + // eslint-disable-next-line no-throw-literal + assert.throws(() => {throw undefined;}, /undefined/); + assert.throws( + // eslint-disable-next-line no-throw-literal + () => assert.doesNotThrow(() => {throw undefined;}), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + message: 'Got unwanted exception.\nActual message: "undefined"' + } + ); +}); + +test('Additional assert', () => { + assert.throws( + () => assert.throws(() => {throw new Error();}, {}), + { + message: "The argument 'error' may not be an empty object. Received: {}", + code: 'ERR_INVALID_ARG_VALUE' + } + ); + + assert.throws( + () => assert.throws( + // eslint-disable-next-line no-throw-literal + () => {throw 'foo';}, + 'foo' + ), + { + code: 'ERR_AMBIGUOUS_ARGUMENT', + message: 'The "error/message" argument is ambiguous. ' + + 'The error "foo" is identical to the message.' + } + ); + + assert.throws( + () => assert.throws( + () => {throw new TypeError('foo');}, + 'foo' + ), + { + code: 'ERR_AMBIGUOUS_ARGUMENT', + message: 'The "error/message" argument is ambiguous. ' + + 'The error message "foo" is identical to the message.' + } + ); + + // Should not throw. + assert.throws(() => {throw null;}, 'foo'); // eslint-disable-line no-throw-literal + + assert.throws( + () => assert.strictEqual([], []), + { + message: 'Values have same structure but are not reference-equal:\n\n[]\n' + } + ); + + { + const args = (function () {return arguments;})('a'); + assert.throws( + () => assert.strictEqual(args, {0: 'a'}), + { + message: 'Expected "actual" to be reference-equal to "expected":\n' + + '+ actual - expected\n\n' + + "+ [Arguments] {\n- {\n '0': 'a'\n }\n" + } + ); + } + + assert.throws( + () => {throw new TypeError('foobar');}, + { + message: /foo/, + name: /^TypeError$/ + } + ); + + assert.throws( + () => assert.throws( + () => {throw new TypeError('foobar');}, + { + message: /fooa/, + name: /^TypeError$/ + } + ), + { + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + "+ message: 'foobar',\n" + + '- message: /fooa/,\n' + + " name: 'TypeError'\n" + + ' }\n' + } + ); + + { + let actual = null; + const expected = {message: 'foo'}; + assert.throws( + () => assert.throws( + () => {throw actual;}, + expected + ), + { + operator: 'throws', + actual, + expected, + generatedMessage: true, + message: `${start}\n${actExp}\n\n` + + '+ null\n' + + '- {\n' + + "- message: 'foo'\n" + + '- }\n' + } + ); + + actual = 'foobar'; + const message = 'message'; + assert.throws( + () => assert.throws( + () => {throw actual;}, + {message: 'foobar'}, + message + ), + { + actual, + message: "message\n+ actual - expected\n\n+ 'foobar'\n- {\n- message: 'foobar'\n- }\n", + operator: 'throws', + generatedMessage: false + } + ); + } + + // Indicate where the strings diverge. + assert.throws( + () => assert.strictEqual('test test', 'test foobar'), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + "+ 'test test'\n" + + "- 'test foobar'\n" + + ' ^\n', + } + ); + + // Check for reference-equal objects in `notStrictEqual()` + assert.throws( + () => { + const obj = {}; + assert.notStrictEqual(obj, obj); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected "actual" not to be reference-equal to "expected": {}' + } + ); + + assert.throws( + () => { + const obj = {a: true}; + assert.notStrictEqual(obj, obj); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected "actual" not to be reference-equal to "expected":\n\n' + + '{\n a: true\n}\n' + } + ); + + assert.throws( + () => { + assert.deepStrictEqual({a: true}, {a: false}, 'custom message'); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }\n' + } + ); + + { + let threw = false; + try { + assert.deepStrictEqual(Array(100).fill(1), 'foobar'); + } catch (err) { + threw = true; + assert.match(inspect(err), /actual: \[Array],\n {2}expected: 'foobar',/); + } + assert(threw); + } + + assert.throws( + () => assert.equal(1), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.deepEqual(/a/), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.notEqual(null), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.notDeepEqual('test'), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.strictEqual({}), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.deepStrictEqual(Symbol()), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.notStrictEqual(5n), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.notDeepStrictEqual(undefined), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.strictEqual(), + {code: 'ERR_MISSING_ARGS'} + ); + + assert.throws( + () => assert.deepStrictEqual(), + {code: 'ERR_MISSING_ARGS'} + ); + + // Verify that `stackStartFunction` works as alternative to `stackStartFn`. + { + (function hidden() { + const err = new assert.AssertionError({ + actual: 'foo', + operator: 'strictEqual', + stackStartFunction: hidden + }); + const err2 = new assert.AssertionError({ + actual: 'foo', + operator: 'strictEqual', + stackStartFn: hidden + }); + assert(!err.stack.includes('hidden')); + assert(!err2.stack.includes('hidden')); + })(); + } + + assert.throws( + () => assert.throws(() => {throw Symbol('foo');}, RangeError), + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "Symbol(foo)"' + } + ); + + assert.throws( + // eslint-disable-next-line no-throw-literal + () => assert.throws(() => {throw [1, 2];}, RangeError), + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "[Array]"' + } + ); + + { + const err = new TypeError('foo'); + const validate = (() => () => ({a: true, b: [1, 2, 3]}))(); + assert.throws( + () => assert.throws(() => {throw err;}, validate), + { + message: 'The validation function is expected to ' + + `return "true". Received ${inspect(validate())}\n\nCaught ` + + `error:\n\n${err}`, + code: 'ERR_ASSERTION', + actual: err, + expected: validate, + name: 'AssertionError', + operator: 'throws', + } + ); + } + + assert.throws( + () => { + const script = new vm.Script('new RangeError("foobar");'); + const context = vm.createContext(); + const err = script.runInContext(context); + assert.throws(() => {throw err;}, RangeError); + }, + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received an error with identical name but a different ' + + 'prototype.\n\nError message:\n\nfoobar' + } + ); + + // Multiple assert.match() tests. + { + assert.throws( + () => assert.match(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "regexp" argument must be of type RegExp. ' + + 'Received: "string"' + } + ); + assert.throws( + () => assert.match('string', /abc/), + { + actual: 'string', + expected: /abc/, + operator: 'match', + message: 'The input did not match the regular expression /abc/. ' + + "Input:\n\n'string'\n", + generatedMessage: true + } + ); + assert.throws( + () => assert.match('string', /abc/, 'foobar'), + { + actual: 'string', + expected: /abc/, + operator: 'match', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.match('string', /abc/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.match({abc: 123}, /abc/), + { + actual: {abc: 123}, + expected: /abc/, + operator: 'match', + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + generatedMessage: true + } + ); + assert.match('I will pass', /pass$/); + } + + // Multiple assert.doesNotMatch() tests. + { + assert.throws( + () => assert.doesNotMatch(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "regexp" argument must be of type RegExp. ' + + 'Received: "string"' + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + message: 'The input was expected to not match the regular expression ' + + "/string/. Input:\n\n'string'\n", + generatedMessage: true + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/, 'foobar'), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.doesNotMatch('string', /string/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.doesNotMatch({abc: 123}, /abc/), + { + actual: {abc: 123}, + expected: /abc/, + operator: 'doesNotMatch', + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + generatedMessage: true + } + ); + assert.doesNotMatch('I will pass', /different$/); + } +}); + +test('assert/strict exists', () => { + assert.strictEqual(require('assert/strict'), assert.strict); +}); + +/* eslint-enable no-restricted-syntax */ +/* eslint-enable no-restricted-properties */ + + From bdd3d151ab8a7c277cdd7af25078fea161ead0c7 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 10 Dec 2024 20:53:32 -0800 Subject: [PATCH 2/5] update tests --- test/js/node/assert/assert-doesNotMatch.test.cjs | 2 +- test/js/node/assert/assert-match.test.cjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/js/node/assert/assert-doesNotMatch.test.cjs b/test/js/node/assert/assert-doesNotMatch.test.cjs index 14ffd2eae22ae6..136ea095db0caf 100644 --- a/test/js/node/assert/assert-doesNotMatch.test.cjs +++ b/test/js/node/assert/assert-doesNotMatch.test.cjs @@ -6,7 +6,7 @@ test("doesNotMatch does not throw when not matching", () => { test("doesNotMatch throws when argument is not string", () => { expect(() => assert.doesNotMatch(123, /pass/)).toThrow( - 'The "actual" argument must be of type string. Received type number', + 'The "string" argument must be of type string. Received type number', ); }); diff --git a/test/js/node/assert/assert-match.test.cjs b/test/js/node/assert/assert-match.test.cjs index 4eb097e357c4bd..29f2d117ebba7c 100644 --- a/test/js/node/assert/assert-match.test.cjs +++ b/test/js/node/assert/assert-match.test.cjs @@ -5,7 +5,7 @@ test("match does not throw when matching", () => { }); test("match throws when argument is not string", () => { - expect(() => assert.match(123, /pass/)).toThrow('The "actual" argument must be of type string. Received type number'); + expect(() => assert.match(123, /pass/)).toThrow('The "string" argument must be of type string. Received type number'); }); test("match throws when not matching", () => { From dc8110f6fe078a69346662d37462ab85eef468a6 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 12 Dec 2024 21:18:33 -0800 Subject: [PATCH 3/5] Use native myers diff algorithm. `Diff` still needs serializing. Blocked by #15742. --- src/bun.js/base.zig | 2 +- src/bun.js/bindings/bindings.zig | 2 + src/bun.js/node/node_assert.zig | 15 ++ src/bun.js/node/node_assert_binding.zig | 58 +++++++ src/js/internal/assert/assertion_error.ts | 27 +--- src/js/internal/assert/calltracker.ts | 3 - src/js/internal/assert/myers_diff.ts | 177 ++++++---------------- src/js/internal/assert/utils.ts | 14 +- 8 files changed, 136 insertions(+), 162 deletions(-) create mode 100644 src/bun.js/node/node_assert.zig create mode 100644 src/bun.js/node/node_assert_binding.zig diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 3c010ea1b47a58..06da0a27022b60 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -75,7 +75,7 @@ pub fn toJS(globalObject: *JSC.JSGlobalObject, comptime ValueType: type, value: var array = JSC.JSValue.createEmptyArray(globalObject, value.len); for (value, 0..) |*item, i| { - const res = toJS(globalObject, *Child, item, lifetime); + const res = toJS(globalObject, *const Child, item, lifetime); if (res == .zero) return .zero; array.putIndex( globalObject, diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 173add280230e9..c7fcaaf42b5122 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3017,6 +3017,7 @@ pub const JSGlobalObject = opaque { return this.throwValue(this.createInvalidArgumentType(name_, field, typename)); } + /// "The argument must be of type . Received " pub fn throwInvalidArgumentTypeValue( this: *JSGlobalObject, argname: []const u8, @@ -3056,6 +3057,7 @@ pub const JSGlobalObject = opaque { return JSC.toTypeError(.ERR_MISSING_ARGS, "Not enough arguments to '" ++ name_ ++ "'. Expected {d}, got {d}.", .{ expected, got }, this); } + /// Not enough arguments passed to function named `name_` pub fn throwNotEnoughArguments( this: *JSGlobalObject, comptime name_: []const u8, diff --git a/src/bun.js/node/node_assert.zig b/src/bun.js/node/node_assert.zig new file mode 100644 index 00000000000000..5c95680c1f165f --- /dev/null +++ b/src/bun.js/node/node_assert.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig"); +const DiffError = DiffMatchPatch.DiffError; +pub const Diff = DiffMatchPatch.Diff; + +const dmp = DiffMatchPatch{ + .diff_timeout = 200, // ms +}; + +pub fn myersDiff(allocator: Allocator, actual: []const u8, expected: []const u8) DiffError!std.ArrayListUnmanaged(Diff) { + // TODO: this DMP impl allocates to much and needs improvement. + return dmp.diff(allocator, expected, actual, false); +} diff --git a/src/bun.js/node/node_assert_binding.zig b/src/bun.js/node/node_assert_binding.zig new file mode 100644 index 00000000000000..835dfd75eeee43 --- /dev/null +++ b/src/bun.js/node/node_assert_binding.zig @@ -0,0 +1,58 @@ +const std = @import("std"); +const bun = @import("root").bun; +const JSC = bun.JSC; +const assert = @import("./node_assert.zig"); +const Allocator = std.mem.Allocator; + +/// ```ts +/// const enum DiffType { +/// Insert = 0, +/// Delete = 1, +/// Equal = 2, +/// } +/// type Diff = { operation: DiffType, text: string }; +/// declare function myersDiff(actual: string, expected: string): Diff[]; +/// ``` +pub fn myersDiff(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { + if (callframe.argumentsCount() < 2) { + return global.throwNotEnoughArguments("printMyersDiff", 2, callframe.argumentsCount()); + } + + var stack_fallback = std.heap.stackFallback(1024 * 2, bun.default_allocator); + var arena = std.heap.ArenaAllocator.init(stack_fallback.get()); + defer arena.deinit(); + const alloc = arena.allocator(); + + const actual = try safeToString(global, alloc, callframe.argument(0)); + const expected = try safeToString(global, alloc, callframe.argument(1)); + + + const diff = assert.myersDiff(arena.allocator(), actual.slice(), expected.slice()) catch |e| { + return switch (e) { + error.OutOfMemory => return global.throwOutOfMemory(), + }; + }; + + return JSC.toJS(global, []const assert.Diff, diff.items, .allocated); +} + +fn safeToString(global: *JSC.JSGlobalObject, alloc: Allocator, argument: JSC.JSValue) bun.JSError!bun.JSC.ZigString.Slice { + if (!argument.isString()) { + return global.throwInvalidArgumentTypeValue("argument", "string", argument); + } + + const bunstring = argument.toBunString2(global) catch @panic("argument is string-like but could not be converted into a bun.String. This is a bug."); + return bunstring.toUTF8WithoutRef(alloc); +} + +pub fn generate(global: *JSC.JSGlobalObject) JSC.JSValue { + const exports = JSC.JSValue.createEmptyObject(global, 1); + + exports.put( + global, + bun.String.static("myersDiff"), + JSC.JSFunction.create(global, "myersDiff", myersDiff, 2, .{}), + ); + + return exports; +} diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts index 150c20c021b3d8..c94db76f6a1c32 100644 --- a/src/js/internal/assert/assertion_error.ts +++ b/src/js/internal/assert/assertion_error.ts @@ -14,29 +14,9 @@ const { StringPrototypeRepeat, StringPrototypeSlice, StringPrototypeSplit, -} = require("internal/primordials");; +} = require("internal/primordials"); const { isError } = require("internal/util"); -const prims = [ - ArrayPrototypeJoin, - ArrayPrototypePop, - ArrayPrototypeSlice, - Error, - ErrorCaptureStackTrace, - ObjectAssign, - ObjectDefineProperty, - ObjectGetPrototypeOf, - ObjectPrototypeHasOwnProperty, - String, - StringPrototypeRepeat, - StringPrototypeSlice, - StringPrototypeSplit, -] -for (const p of prims) { - if (typeof p !== "function") { - throw new Error(`Expected ${p} to be a function`); - } -} const { inspect } = require("internal/util/inspect"); const colors = require("internal/util/colors"); @@ -261,8 +241,9 @@ class AssertionError extends Error { } = options; let { actual, expected } = options; + // NOTE: stack trace is always writable. const limit = Error.stackTraceLimit; - // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; + Error.stackTraceLimit = 0; if (message != null) { if (operator === "deepStrictEqual" || operator === "strictEqual") { @@ -355,7 +336,7 @@ class AssertionError extends Error { } } - // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit; + Error.stackTraceLimit = limit; this.generatedMessage = !message; ObjectDefineProperty(this, "name", { diff --git a/src/js/internal/assert/calltracker.ts b/src/js/internal/assert/calltracker.ts index 0e207bb5f9939e..5d63a61b781bab 100644 --- a/src/js/internal/assert/calltracker.ts +++ b/src/js/internal/assert/calltracker.ts @@ -12,9 +12,6 @@ const { SafeWeakMap, } = require("internal/primordials"); -// const { -// codes: { ERR_INVALID_ARG_VALUE, ERR_UNAVAILABLE_DURING_EXIT }, -// } = require("internal/errors"); const AssertionError = require("internal/assert/assertion_error"); const { validateUint32 } = require("internal/validators"); diff --git a/src/js/internal/assert/myers_diff.ts b/src/js/internal/assert/myers_diff.ts index b7fc8286209d29..54771eaa8d4965 100644 --- a/src/js/internal/assert/myers_diff.ts +++ b/src/js/internal/assert/myers_diff.ts @@ -1,166 +1,89 @@ "use strict"; -const { - Array, - ArrayPrototypeFill, - ArrayPrototypePush, - ArrayPrototypeSlice, - StringPrototypeEndsWith, -} = require("internal/primordials"); - const colors = require("internal/util/colors"); -const kNopLinesToCollapse = 5; - -function areLinesEqual(actual, expected, checkCommaDisparity) { - if (actual === expected) { - return true; - } - if (checkCommaDisparity) { - return `${actual},` === expected || actual === `${expected},`; - } - return false; +const enum Operation { + Insert = 0, + Delete = 1, + Equal = 2, } - -export function myersDiff(actual, expected, checkCommaDisparity = false) { - const actualLength = actual.length; - const expectedLength = expected.length; - const max = actualLength + expectedLength; - const v = ArrayPrototypeFill(Array(2 * max + 1), 0); - - const trace = []; - - for (let diffLevel = 0; diffLevel <= max; diffLevel++) { - const newTrace = ArrayPrototypeSlice(v); - ArrayPrototypePush(trace, newTrace); - - for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) { - let x; - if ( - diagonalIndex === -diffLevel || - (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max]) - ) { - x = v[diagonalIndex + 1 + max]; - } else { - x = v[diagonalIndex - 1 + max] + 1; - } - - let y = x - diagonalIndex; - - while (x < actualLength && y < expectedLength && areLinesEqual(actual[x], expected[y], checkCommaDisparity)) { - x++; - y++; - } - - v[diagonalIndex + max] = x; - - if (x >= actualLength && y >= expectedLength) { - return backtrack(trace, actual, expected, checkCommaDisparity); - } - } - } +interface Diff { + operation: Operation; + text: string; } -function backtrack(trace, actual, expected, checkCommaDisparity) { - const actualLength = actual.length; - const expectedLength = expected.length; - const max = actualLength + expectedLength; - - let x = actualLength; - let y = expectedLength; - const result = []; - - for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) { - const v = trace[diffLevel]; - const diagonalIndex = x - y; - let prevDiagonalIndex; - - if ( - diagonalIndex === -diffLevel || - (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max]) - ) { - prevDiagonalIndex = diagonalIndex + 1; - } else { - prevDiagonalIndex = diagonalIndex - 1; - } - - const prevX = v[prevDiagonalIndex + max]; - const prevY = prevX - prevDiagonalIndex; - - while (x > prevX && y > prevY) { - const value = - !checkCommaDisparity || StringPrototypeEndsWith(actual[x - 1], ",") ? actual[x - 1] : expected[y - 1]; - ArrayPrototypePush(result, { __proto__: null, type: "nop", value }); - x--; - y--; - } +declare namespace Internal { + export function myersDiff(actual: string[], expected: string[], checkCommaDisparity?: boolean): Diff[]; +} - if (diffLevel > 0) { - if (x > prevX) { - ArrayPrototypePush(result, { __proto__: null, type: "insert", value: actual[x - 1] }); - x--; - } else { - ArrayPrototypePush(result, { __proto__: null, type: "delete", value: expected[y - 1] }); - y--; - } - } - } +const kNopLinesToCollapse = 5; - return result; -} +const { myersDiff } = $zig("node_assert_binding.zig", "generate") as typeof Internal; -export function printSimpleMyersDiff(diff) { +function printSimpleMyersDiff(diff: Diff[]) { let message = ""; for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { - const { type, value } = diff[diffIdx]; - if (type === "insert") { - message += `${colors.green}${value}${colors.white}`; - } else if (type === "delete") { - message += `${colors.red}${value}${colors.white}`; - } else { - message += `${colors.white}${value}${colors.white}`; + const { operation: type, text: value } = diff[diffIdx]; + switch (type) { + case Operation.Insert: + message += `${colors.green}${value}${colors.white}`; + break; + case Operation.Delete: + message += `${colors.red}${value}${colors.white}`; + break; + case Operation.Equal: + message += `${colors.white}${value}${colors.white}`; + break; + default: + throw new TypeError(`Invalid diff operation kind: ${type}`); // should be unreachable } } return `\n${message}`; } -export function printMyersDiff(diff, simple = false) { +function printMyersDiff(diff: Diff[], simple = false) { let message = ""; let skipped = false; let nopCount = 0; for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { - const { type, value } = diff[diffIdx]; - const previousType = diffIdx < diff.length - 1 ? diff[diffIdx + 1].type : null; + const { operation: type, text: value } = diff[diffIdx]; + const previousType = diffIdx < diff.length - 1 ? diff[diffIdx + 1].operation : null; const typeChanged = previousType && type !== previousType; - if (typeChanged && previousType === "nop") { + if (typeChanged && previousType === Operation.Equal) { // Avoid grouping if only one line would have been grouped otherwise if (nopCount === kNopLinesToCollapse + 1) { - message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + message += `${colors.white} ${diff[diffIdx + 1].operation}\n`; } else if (nopCount === kNopLinesToCollapse + 2) { - message += `${colors.white} ${diff[diffIdx + 2].value}\n`; - message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + message += `${colors.white} ${diff[diffIdx + 2].operation}\n`; + message += `${colors.white} ${diff[diffIdx + 1].operation}\n`; } if (nopCount >= kNopLinesToCollapse + 3) { message += `${colors.blue}...${colors.white}\n`; - message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + message += `${colors.white} ${diff[diffIdx + 1].operation}\n`; skipped = true; } nopCount = 0; } - if (type === "insert") { - message += `${colors.green}+${colors.white} ${value}\n`; - } else if (type === "delete") { - message += `${colors.red}-${colors.white} ${value}\n`; - } else if (type === "nop") { - if (nopCount < kNopLinesToCollapse) { - message += `${colors.white} ${value}\n`; - } - nopCount++; + switch (type) { + case Operation.Insert: + message += `${colors.green}+${colors.white} ${value}\n`; + break; + case Operation.Delete: + message += `${colors.red}-${colors.white} ${value}\n`; + break; + case Operation.Equal: + if (nopCount < kNopLinesToCollapse) { + message += `${colors.white} ${value}\n`; + } + nopCount++; + break; + default: + throw new TypeError(`Invalid diff operation kind: ${type}`); // should be unreachable } } @@ -169,4 +92,4 @@ export function printMyersDiff(diff, simple = false) { return { message: `\n${message}`, skipped }; } -// export default { myersDiff, printMyersDiff, printSimpleMyersDiff }; +export default { myersDiff, printMyersDiff, printSimpleMyersDiff }; diff --git a/src/js/internal/assert/utils.ts b/src/js/internal/assert/utils.ts index 1f58c43d93a2ce..a3353e689227ba 100644 --- a/src/js/internal/assert/utils.ts +++ b/src/js/internal/assert/utils.ts @@ -15,21 +15,20 @@ const { // StringPrototypeSlice, // StringPrototypeSplit, // StringPrototypeStartsWith, -} = require('internal/primordials'); +} = require("internal/primordials"); -const { isError } = require('internal/util'); +const { isError } = require("internal/util"); // const { Buffer } = require('node:buffer'); // const { // isErrorStackTraceLimitWritable, // overrideStackTrace, // } = require('internal/errors'); -const AssertionError = require('internal/assert/assertion_error'); +const AssertionError = require("internal/assert/assertion_error"); // const { openSync, closeSync, readSync } = require('node:fs'); // // const { EOL } = require('internal/constants'); // // const { BuiltinModule } = require('internal/bootstrap/realm'); // // const { isError } = require('internal/util'); - // const errorCache = new SafeMap(); // // const { fileURLToPath } = require('internal/url'); @@ -174,8 +173,7 @@ function getErrMessage(message: string, value: unknown, fn: Function): string | // overrideStackTrace.set(err, (_, stack) => stack); // const call = err.stack[0]; // - if (fn.name === 'ok') { - if (message === null) throw new Error('what the fuck'); // message = "null" + if (fn.name === "ok") { return `The expression evaluated to a falsy value:\n\n assert.ok(${value})\n`; } @@ -272,7 +270,7 @@ export function innerOk(fn, argLen, value, message) { if (argLen === 0) { generatedMessage = true; - message = 'No value argument passed to `assert.ok()`'; + message = "No value argument passed to `assert.ok()`"; } else if (message == null) { generatedMessage = true; message = getErrMessage(message, value, fn); @@ -285,7 +283,7 @@ export function innerOk(fn, argLen, value, message) { actual: value, expected: true, message, - operator: '==', + operator: "==", stackStartFn: fn, }); err.generatedMessage = generatedMessage; From 3b7a89d68f8286ac4001f88e651a92214a4fe691 Mon Sep 17 00:00:00 2001 From: DonIsaac Date: Fri, 13 Dec 2024 05:20:48 +0000 Subject: [PATCH 4/5] `bun run zig-format` --- src/bun.js/node/node_assert_binding.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bun.js/node/node_assert_binding.zig b/src/bun.js/node/node_assert_binding.zig index 835dfd75eeee43..589aeb81e31af1 100644 --- a/src/bun.js/node/node_assert_binding.zig +++ b/src/bun.js/node/node_assert_binding.zig @@ -26,7 +26,6 @@ pub fn myersDiff(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSE const actual = try safeToString(global, alloc, callframe.argument(0)); const expected = try safeToString(global, alloc, callframe.argument(1)); - const diff = assert.myersDiff(arena.allocator(), actual.slice(), expected.slice()) catch |e| { return switch (e) { error.OutOfMemory => return global.throwOutOfMemory(), @@ -41,7 +40,7 @@ fn safeToString(global: *JSC.JSGlobalObject, alloc: Allocator, argument: JSC.JSV return global.throwInvalidArgumentTypeValue("argument", "string", argument); } - const bunstring = argument.toBunString2(global) catch @panic("argument is string-like but could not be converted into a bun.String. This is a bug."); + const bunstring = argument.toBunString2(global) catch @panic("argument is string-like but could not be converted into a bun.String. This is a bug."); return bunstring.toUTF8WithoutRef(alloc); } From aa5245f6a1d73e76619c2b9beaea8395d5623eea Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 12 Dec 2024 22:11:28 -0800 Subject: [PATCH 5/5] wip --- src/bun.js/node/node_assert_binding.zig | 42 +++++++++++++++++++------ src/js/tsconfig.json | 3 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/bun.js/node/node_assert_binding.zig b/src/bun.js/node/node_assert_binding.zig index 589aeb81e31af1..e246b97ec410b4 100644 --- a/src/bun.js/node/node_assert_binding.zig +++ b/src/bun.js/node/node_assert_binding.zig @@ -2,6 +2,7 @@ const std = @import("std"); const bun = @import("root").bun; const JSC = bun.JSC; const assert = @import("./node_assert.zig"); +const Diff = assert.Diff; const Allocator = std.mem.Allocator; /// ```ts @@ -23,8 +24,8 @@ pub fn myersDiff(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSE defer arena.deinit(); const alloc = arena.allocator(); - const actual = try safeToString(global, alloc, callframe.argument(0)); - const expected = try safeToString(global, alloc, callframe.argument(1)); + const actual = try safeToString(global, alloc, callframe.argument(0), "actual"); + const expected = try safeToString(global, alloc, callframe.argument(1), "expected"); const diff = assert.myersDiff(arena.allocator(), actual.slice(), expected.slice()) catch |e| { return switch (e) { @@ -32,16 +33,39 @@ pub fn myersDiff(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSE }; }; - return JSC.toJS(global, []const assert.Diff, diff.items, .allocated); + // todo: replace with toJS + var array = JSC.JSValue.createEmptyArray(global, diff.items.len); + for (diff.items, 0..) |*line, i| { + var obj = JSC.JSValue.createEmptyObjectWithNullPrototype(global); + if (obj == .zero) return global.throwOutOfMemory(); + obj.put(global, bun.String.static("operation"), JSC.JSValue.jsNumber(@as(u32, @intFromEnum(line.operation)))); + obj.put(global, bun.String.static("text"), JSC.toJS(global, []const u8, line.text, .allocated)); + array.putIndex(global, @truncate(i), obj); + } + return array; } -fn safeToString(global: *JSC.JSGlobalObject, alloc: Allocator, argument: JSC.JSValue) bun.JSError!bun.JSC.ZigString.Slice { - if (!argument.isString()) { - return global.throwInvalidArgumentTypeValue("argument", "string", argument); +fn safeToString(global: *JSC.JSGlobalObject, arena: Allocator, argument: JSC.JSValue, comptime argname: []const u8) bun.JSError!bun.JSC.ZigString.Slice { + if (argument.isString()) { + const bunstring = argument.toBunString2(global) catch @panic("argument is string-like but could not be converted into a bun.String. This is a bug."); + return bunstring.toUTF8WithoutRef(arena); + } else if (argument.isObject()) { + const to_string: JSC.JSValue = (try argument.getFunction(global, "toString")) orelse { + return global.throwInvalidArgumentTypeValue(argname, "string or object with .toString()", argument); + }; + const js_string = try to_string.call( + global, + argument, + &[0]JSC.JSValue{}, + ); + const bun_string = bun.String.fromJS(js_string, global); + // TODO: does bunstring own its memory or does it increment a reference + // count? IF the former, it's saved in the arena and this won't leak, + // otherwise this will cause a UAF. + return bun_string.toUTF8WithoutRef(arena); + } else { + return global.throwInvalidArgumentTypeValue(argname, "string or object with .toString()", argument); } - - const bunstring = argument.toBunString2(global) catch @panic("argument is string-like but could not be converted into a bun.String. This is a bug."); - return bunstring.toUTF8WithoutRef(alloc); } pub fn generate(global: *JSC.JSGlobalObject) JSC.JSValue { diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json index a2fd51e7dcd8a7..279f79762a9a50 100644 --- a/src/js/tsconfig.json +++ b/src/js/tsconfig.json @@ -1,10 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "esModuleInterop": true, // Path remapping "baseUrl": ".", "paths": { - "internal/*": ["./js/internal/*"] //deprecated + "internal/*": ["./internal/*"] //deprecated } }, "include": ["**/*.ts", "**/*.tsx", "./builtins.d.ts", "./private.d.ts"]