From 0372ca5c0a6f68c062e1ebda4f43ef1229a97c8a Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 9 Jan 2025 18:45:43 -0600 Subject: [PATCH] test(node): get `test-assert.js` working (#15698) Co-authored-by: Don Isaac Co-authored-by: DonIsaac --- bunfig.node-test.toml | 4 + scripts/runner.node.mjs | 25 +- src/bun.js/base.zig | 16 +- src/bun.js/bindings/ErrorCode.cpp | 4 +- src/bun.js/bindings/ErrorCode.ts | 13 +- src/bun.js/bindings/bindings.zig | 2 + src/bun.js/node/assert/myers_diff.zig | 631 +++++ src/bun.js/node/node_assert.zig | 132 + src/bun.js/node/node_assert_binding.zig | 86 + src/codegen/bundle-modules.ts | 1 + src/js/builtins.d.ts | 10 +- src/js/internal/assert/assertion_error.ts | 425 ++++ src/js/internal/assert/calltracker.ts | 141 ++ src/js/internal/assert/myers_diff.ts | 111 + src/js/internal/assert/utils.ts | 288 +++ src/js/internal/primordials.js | 31 +- src/js/internal/util/colors.ts | 59 + src/js/node/assert.ts | 2234 ++++++++--------- src/js/tsconfig.json | 1 + src/string.zig | 1 + .../node/assert/assert-doesNotMatch.test.cjs | 2 +- test/js/node/assert/assert-match.test.cjs | 2 +- test/js/node/bunfig.toml | 3 +- .../child_process/child_process-node.test.js | 2 +- test/js/node/harness.ts | 51 +- test/js/node/test/common/index.js | 2 +- .../node/test/parallel/needs-test/README.md | 8 + .../test/parallel/needs-test/test-assert.js | 1601 ++++++++++++ ...-child-process-exec-timeout-not-expired.js | 4 +- .../test-child-process-spawnsync-input.js | 2 + 30 files changed, 4588 insertions(+), 1304 deletions(-) create mode 100644 bunfig.node-test.toml create mode 100644 src/bun.js/node/assert/myers_diff.zig create mode 100644 src/bun.js/node/node_assert.zig create mode 100644 src/bun.js/node/node_assert_binding.zig 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/colors.ts create mode 100644 test/js/node/test/parallel/needs-test/README.md create mode 100644 test/js/node/test/parallel/needs-test/test-assert.js diff --git a/bunfig.node-test.toml b/bunfig.node-test.toml new file mode 100644 index 0000000000..284945e352 --- /dev/null +++ b/bunfig.node-test.toml @@ -0,0 +1,4 @@ +# FIXME: move this back to test/js/node +# https://github.com/oven-sh/bun/issues/16289 +[test] +preload = ["./test/js/node/harness.ts", "./test/preload.ts"] diff --git a/scripts/runner.node.mjs b/scripts/runner.node.mjs index b530ba9483..c66d8b6019 100755 --- a/scripts/runner.node.mjs +++ b/scripts/runner.node.mjs @@ -63,6 +63,7 @@ const { values: options, positionals: filters } = parseArgs({ type: "boolean", default: false, }, + /** Path to bun binary */ ["exec-path"]: { type: "string", default: "bun", @@ -252,15 +253,19 @@ async function runTests() { if (!failedResults.length) { for (const testPath of tests) { - const title = relative(cwd, join(testsPath, testPath)).replace(/\\/g, "/"); + const absoluteTestPath = join(testsPath, testPath); + const title = relative(cwd, absoluteTestPath).replaceAll(sep, "/"); if (isNodeParallelTest(testPath)) { + const subcommand = title.includes("needs-test") ? "test" : "run"; await runTest(title, async () => { const { ok, error, stdout } = await spawnBun(execPath, { cwd: cwd, - args: [title], + args: [subcommand, "--config=./bunfig.node-test.toml", absoluteTestPath], timeout: getNodeParallelTestTimeout(title), env: { FORCE_COLOR: "0", + NO_COLOR: "1", + BUN_DEBUG_QUIET_LOGS: "1", }, stdout: chunk => pipeTestStdout(process.stdout, chunk), stderr: chunk => pipeTestStdout(process.stderr, chunk), @@ -278,9 +283,9 @@ async function runTests() { stdoutPreview: stdoutPreview, }; }); - continue; + } else { + await runTest(title, async () => spawnBunTest(execPath, join("test", testPath))); } - await runTest(title, async () => spawnBunTest(execPath, join("test", testPath))); } } @@ -537,7 +542,7 @@ async function spawnSafe(options) { } /** - * @param {string} execPath + * @param {string} execPath Path to bun binary * @param {SpawnOptions} options * @returns {Promise} */ @@ -565,9 +570,11 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) { // Used in Node.js tests. TEST_TMPDIR: tmpdirPath, }; + if (env) { Object.assign(bunEnv, env); } + if (isWindows) { delete bunEnv["PATH"]; bunEnv["Path"] = path; @@ -862,7 +869,7 @@ function isJavaScriptTest(path) { * @returns {boolean} */ function isNodeParallelTest(testPath) { - return testPath.replaceAll(sep, "/").includes("js/node/test/parallel/") + return testPath.replaceAll(sep, "/").includes("js/node/test/parallel/"); } /** @@ -892,6 +899,9 @@ function isHidden(path) { return /node_modules|node.js/.test(dirname(path)) || /^\./.test(basename(path)); } +/** Files with these extensions are not treated as test cases */ +const IGNORED_EXTENSIONS = new Set([".md"]); + /** * @param {string} cwd * @returns {string[]} @@ -901,8 +911,9 @@ function getTests(cwd) { const dirname = join(cwd, path); for (const entry of readdirSync(dirname, { encoding: "utf-8", withFileTypes: true })) { const { name } = entry; + const ext = name.slice(name.lastIndexOf(".")); const filename = join(path, name); - if (isHidden(filename)) { + if (isHidden(filename) || IGNORED_EXTENSIONS.has(ext)) { continue; } if (entry.isFile() && isTest(filename)) { diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 3c010ea1b4..7bc75dc56a 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -31,6 +31,13 @@ pub const Lifetime = enum { allocated, temporary, }; + +/// Marshall a zig value into a JSValue using comptime reflection. +/// +/// - Primitives are converted to their JS equivalent. +/// - Types with `toJS` or `toJSNewlyCreated` methods have them called +/// - Slices are converted to JS arrays +/// - Enums are converted to 32-bit numbers. pub fn toJS(globalObject: *JSC.JSGlobalObject, comptime ValueType: type, value: ValueType, comptime lifetime: Lifetime) JSC.JSValue { const Type = comptime brk: { var CurrentType = ValueType; @@ -75,7 +82,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, @@ -94,6 +101,13 @@ pub fn toJS(globalObject: *JSC.JSGlobalObject, comptime ValueType: type, value: return value.toJS(globalObject); } + // must come after toJS check in case this enum implements its own serializer. + if (@typeInfo(Type) == .Enum) { + // FIXME: creates non-normalized integers (e.g. u2), which + // aren't handled by `jsNumberWithType` rn + return JSC.JSValue.jsNumberWithType(u32, @as(u32, @intFromEnum(value))); + } + @compileError("dont know how to convert " ++ @typeName(ValueType) ++ " to JS"); }, } diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 8fd7698a68..8852628317 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -351,10 +351,10 @@ WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* gl } else { for (unsigned i = 0; i < length - 1; i++) { JSValue expected_type = expected_types.at(i); + if (i > 0) result.append(", "_s); result.append(expected_type.toWTFString(globalObject)); - result.append(", "_s); } - result.append("or "_s); + result.append(" or "_s); result.append(expected_types.at(length - 1).toWTFString(globalObject)); } diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index 0a859c2417..bfe08a4f78 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -18,9 +18,11 @@ export default [ ["ERR_INVALID_ARG_VALUE", TypeError], ["ERR_INVALID_PROTOCOL", TypeError], ["ERR_INVALID_THIS", TypeError], + ["ERR_INVALID_RETURN_VALUE", TypeError], ["ERR_IPC_CHANNEL_CLOSED", Error], ["ERR_IPC_DISCONNECTED", Error], ["ERR_MISSING_ARGS", TypeError], + ["ERR_AMBIGUOUS_ARGUMENT", TypeError], ["ERR_OUT_OF_RANGE", RangeError], ["ERR_PARSE_ARGS_INVALID_OPTION_VALUE", TypeError], ["ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL", TypeError], @@ -47,11 +49,12 @@ export default [ ["ERR_UNKNOWN_SIGNAL", TypeError], ["ERR_SOCKET_BAD_PORT", RangeError], ["ERR_STREAM_RELEASE_LOCK", Error, "AbortError"], - ["ERR_INCOMPATIBLE_OPTION_PAIR", TypeError, "TypeError"], - ["ERR_INVALID_URI", URIError, "URIError"], - ["ERR_INVALID_IP_ADDRESS", TypeError, "TypeError"], - ["ERR_SCRIPT_EXECUTION_TIMEOUT", Error, "Error"], - ["ERR_SCRIPT_EXECUTION_INTERRUPTED", Error, "Error"], + ["ERR_INCOMPATIBLE_OPTION_PAIR", TypeError], + ["ERR_INVALID_IP_ADDRESS", TypeError], + ["ERR_UNAVAILABLE_DURING_EXIT", Error], + ["ERR_INVALID_URI", URIError], + ["ERR_SCRIPT_EXECUTION_TIMEOUT", Error], + ["ERR_SCRIPT_EXECUTION_INTERRUPTED", Error], ["ERR_UNHANDLED_ERROR", Error], ["ERR_UNKNOWN_CREDENTIAL", Error], ["ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET", Error], diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index a2ed61e0f9..1d88a6d235 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -2970,6 +2970,7 @@ pub const JSGlobalObject = opaque { return this.ERR_INVALID_ARG_VALUE("The \"{s}\" argument is invalid. Received {}", .{ argname, value.toFmt(&formatter) }).throw(); } + /// "The argument must be of type . Received " pub fn throwInvalidArgumentTypeValue( this: *JSGlobalObject, argname: []const u8, @@ -3009,6 +3010,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/assert/myers_diff.zig b/src/bun.js/node/assert/myers_diff.zig new file mode 100644 index 0000000000..d6eb316c21 --- /dev/null +++ b/src/bun.js/node/assert/myers_diff.zig @@ -0,0 +1,631 @@ +//! ## IMPORTANT NOTE +//! +//! Do _NOT_ import from "root" in this file! Do _NOT_ use the Bun object in this file! +//! +//! This file has tests defined in it which _cannot_ be run if `@import("root")` is used! +//! +//! Run tests with `:zig test %` +const std = @import("std"); +const builtin = @import("builtin"); +const mem = std.mem; +const Allocator = mem.Allocator; +const stackFallback = std.heap.stackFallback; +const assert = std.debug.assert; +const print = std.debug.print; + +/// Comptime diff configuration. Defaults are usually sufficient. +pub const Options = struct { + /// Guesstimate for the number of bytes `expected` and `actual` will be. + /// Defaults to 256. + /// + /// Used to reserve space on the stack for the edit graph. + avg_input_size: comptime_int = 256, + /// How much stack space to reserve for edit trace frames. Defaults to 64. + initial_trace_capacity: comptime_int = 64, + /// When `true`, string lines that are only different by a trailing comma + /// are considered equal. Not used when comparing chars. Defaults to + /// `false`. + check_comma_disparity: bool = false, +}; + +// By limiting maximum string and buffer lengths, we can store u32s in the +// edit graph instead of usize's, halving our memory footprint. The +// downside is that `(2 * (actual.len + expected.len))` must be less than +// 4Gb. If this becomes a problem in real user scenarios, we can adjust this. +// +// Note that overflows are much more likely to occur in real user scenarios +// than in our own testing, so overflow checks _must_ be handled. Do _not_ +// use `assert` unless you also use `@setRuntimeSafety(true)`. +// +// TODO: make this configurable in `Options`? +const MAXLEN = std.math.maxInt(u32); +// Type aliasing to make future refactors easier +const uint = u32; +const int = i64; // must be large enough to hold all valid values of `uint` w/o overflow. + +/// diffs two sets of lines, returning the minimal number of edits needed to +/// make them equal. +/// +/// Lines may be string slices or chars. Derived from node's implementation of +/// the Myers' diff algorithm. +/// +/// ## Example +/// ```zig +/// const myers_diff = @import("inode/assert/myers_diff.zig"); +/// const StrDiffer = myers_diff.Differ([]const u8, .{}); +/// const actual = &[_][]const u8{ +/// "foo", +/// "bar", +/// "baz", +/// }; +/// const expected = &[_][]const u8{ +/// "foo", +/// "barrr", +/// "baz", +/// }; +/// const diff = try StrDiffer.diff(allocator, actual, expected); +/// ``` +/// +/// TODO: support non-ASCII UTF-8 characters. +/// +/// ## References +/// - [Node- `myers_diff.js`](https://github.com/nodejs/node/blob/main/lib/internal/assert/myers_diff.js) +/// - [An O(ND) Difference Algorithm and Its Variations](http://www.xmailserver.org/diff2.pdf) +pub fn Differ(comptime Line: type, comptime opts: Options) type { + const eql: LineCmp(Line) = switch (Line) { + // char-by-char comparison. u16 is for utf16 + u8, u16 => blk: { + const gen = struct { + pub fn eql(a: Line, b: Line) bool { + return a == b; + } + }; + break :blk gen.eql; + }, + []const u8, + []u8, + [:0]const u8, + [:0]u8, + []const u16, + []u16, + [:0]const u16, + [:0]u16, + => blk: { + const gen = struct { + pub fn eql(a: Line, b: Line) bool { + return areStrLinesEqual(Line, a, b, opts.check_comma_disparity); + } + }; + break :blk gen.eql; + }, + else => @compileError("Differ can only compare lines of chars or strings. Received: " ++ @typeName(Line)), + }; + + return DifferWithEql(Line, opts, eql); +} + +/// Like `Differ`, but allows the user to provide a custom equality function. +pub fn DifferWithEql(comptime Line: type, comptime opts: Options, comptime areLinesEql: LineCmp(Line)) type { + + // `V = [-MAX, MAX]`. + const graph_initial_size = comptime guess: { + const size_wanted = 2 * opts.avg_input_size + 1; + break :guess size_wanted + (size_wanted % 8); // 8-byte align + }; + if (graph_initial_size > MAXLEN) @compileError("Input guess size is too large. The edit graph must be 32-bit addressable."); + + return struct { + pub const eql = areLinesEql; + pub const LineType = Line; + + /// Compute the shortest edit path (diff) between two sets of lines. + /// + /// Returned `Diff` objects borrow from the input slices. Both `actual` + /// and `expected` must outlive them. + /// + /// ## References + /// - [Node- `myers_diff.js`](https://github.com/nodejs/node/blob/main/lib/internal/assert/myers_diff.js) + /// - [An O(ND) Difference Algorithm and Its Variations](http://www.xmailserver.org/diff2.pdf) + pub fn diff(bun_allocator: Allocator, actual: []const Line, expected: []const Line) Error!DiffList(Line) { + + // Edit graph's allocator + var graph_stack_alloc = stackFallback(graph_initial_size, bun_allocator); + const graph_alloc = graph_stack_alloc.get(); + + // Match point trace's allocator + var trace_stack_alloc = stackFallback(opts.initial_trace_capacity, bun_allocator); + const trace_alloc = trace_stack_alloc.get(); + + // const MAX \in [0, M+N] + // let V: int array = [-MAX..MAX]. V is a flattened representation of the edit graph. + const max: uint, const graph_size: uint = blk: { + // This is to preserve overflow protections even when runtime safety + // checks are disabled. We don't know what kind of stuff users are + // diffing in the wild. + const _max: usize = actual.len + expected.len; + const _graph_size = (2 * _max) + 1; + + if (_max > MAXLEN) return Error.InputsTooLarge; + if (_graph_size > MAXLEN) return Error.DiffTooLarge; + + // const m: + + break :blk .{ @intCast(_max), @intCast(_graph_size) }; + }; + + var graph = try graph_alloc.alloc(uint, graph_size); + defer graph_alloc.free(graph); + @memset(graph, 0); + graph.len = graph_size; + + var trace = std.ArrayList([]const uint).init(trace_alloc); + // reserve enough space for each frame to avoid realloc on ptr list. Lists may end up in the heap, but + // this list is at the very from (and ∴ on stack). + try trace.ensureTotalCapacityPrecise(max + 1); + defer { + for (trace.items) |frame| { + trace_alloc.free(frame); + } + trace.deinit(); + } + + // ================================================================ + // ==================== actual implementation ===================== + // ================================================================ + + for (0..max + 1) |_diff_level| { + const diff_level: int = @intCast(_diff_level); // why is this always usize? + // const new_trace = try TraceFrame.initCapacity(trace_alloc, graph.len); + const new_trace = try trace_alloc.dupe(uint, graph); + trace.appendAssumeCapacity(new_trace); + + const diag_start: int = -@as(int, @intCast(diff_level)); + const diag_end: int = @intCast(diff_level); + + // for k ← -D in steps of 2 do + var diag_idx = diag_start; + while (diag_idx <= diag_end) : (diag_idx += 2) { + // if k = -D or K ≠ D and V[k-1] < V[k+1] then + // x ← V[k+1] + // else + // x ← V[k-1] + 1 + assert(diag_idx + max >= 0); // sanity check. Fine to be stripped in release. + const k: uint = u(diag_idx + max); + + const uk = u(k); + var x = if (diag_idx == diag_start or + (diag_idx != diag_end and graph[uk - 1] < graph[uk + 1])) + graph[uk + 1] + else + graph[uk - 1] + 1; + + // y = x - diag_idx + var y: usize = blk: { + const x2: int = @intCast(x); + const y: int = x2 - diag_idx; + assert(y >= 0 and y <= MAXLEN); // sanity check. Fine to be stripped in release. + break :blk @intCast(y); + }; + + while (x < actual.len and y < expected.len and eql(actual[x], expected[y])) { + x += 1; + y += 1; + } + graph[k] = @intCast(x); + if (x >= actual.len and y >= expected.len) { + // todo: arena + return backtrack(bun_allocator, &trace, actual, expected); + } + } + } + + @panic("unreachable. Diffing should always reach the end of either `actual` or `expected` first."); + } + + fn backtrack( + allocator: Allocator, + trace: *const std.ArrayList([]const uint), + actual: []const Line, + expected: []const Line, + ) Error!DiffList(Line) { + const max = i(actual.len + expected.len); + var x = i(actual.len); + var y = i(expected.len); + + var result = DiffList(Line).init(allocator); + if (trace.items.len == 0) return result; + + //for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) { + var diff_level: usize = trace.items.len; + while (diff_level > 0) { + diff_level -= 1; + const graph = trace.items[diff_level]; + const diagonal_index = x - y; + + const diag_offset = u(diagonal_index + max); + const prev_diagonal_index: int = if (diagonal_index == -i(diff_level) or + (diagonal_index != diff_level and graph[u(diag_offset - 1)] < graph[u(diag_offset + 1)])) + diagonal_index + 1 + else + diagonal_index - 1; + + const prev_x: int = i(graph[u(prev_diagonal_index + i(max))]); // v[prevDiagonalIndex + max] + const prev_y: int = i(prev_x) - prev_diagonal_index; + + try result.ensureUnusedCapacity(u(@max(x - prev_x, y - prev_y))); + while (x > prev_x and y > prev_y) { + const line: Line = blk: { + if (@typeInfo(Line) == .Pointer and comptime opts.check_comma_disparity) { + const actual_el = actual[u(x) - 1]; + // actual[x-1].endsWith(',') + break :blk if (actual_el[actual_el.len - 1] == ',') + actual[u(x) - 1] + else + expected[u(y) - 1]; + } else { + break :blk actual[u(x) - 1]; + } + }; + + result.appendAssumeCapacity(.{ .kind = .equal, .value = line }); + x -= 1; + y -= 1; + } + if (diff_level > 0) { + if (x > prev_x) { + try result.append(.{ .kind = .insert, .value = actual[u(x) - 1] }); + x -= 1; + } else { + try result.append(.{ .kind = .delete, .value = expected[u(y) - 1] }); + y -= 1; + } + } + } + + return result; + } + + // shorthands for int casting since I'm tired of writing `@as(int, @intCast(x))` everywhere + inline fn u(n: anytype) uint { + return @intCast(n); + } + inline fn us(n: anytype) usize { + return @intCast(n); + } + inline fn i(n: anytype) int { + return @intCast(n); + } + }; +} + +pub fn printDiff(T: type, diffs: std.ArrayList(Diff(T))) !void { + const stdout = if (builtin.is_test) + std.io.getStdErr().writer() + else + std.io.getStdOut().writer(); + + const specifier = switch (T) { + u8 => "c", + u32 => "u", + []const u8 => "s", + else => @compileError("printDiff can only print chars and strings. Received: " ++ @typeName(T)), + }; + + for (0..diffs.items.len) |idx| { + const d = diffs.items[diffs.items.len - (idx + 1)]; + const op: u8 = switch (d.kind) { + inline .equal => ' ', + inline .insert => '+', + inline .delete => '-', + }; + try stdout.writeByte(op); + try stdout.print(" {" ++ specifier ++ "}\n", .{d.value}); + } +} + +// ============================================================================= +// ============================ EQUALITY FUNCTIONS ============================ +// ============================================================================= + +fn areCharsEqual(comptime T: type, a: T, b: T) bool { + return a == b; +} + +fn areLinesEqual(comptime T: type, a: T, b: T, comptime check_comma_disparity: bool) bool { + return switch (T) { + u8, u32 => a == b, + []const u8, []u8, [:0]const u8, [:0]u8 => areStrLinesEqual(T, a, b, check_comma_disparity), + else => @compileError("areLinesEqual can only compare chars and strings. Received: " ++ @typeName(T)), + }; +} + +fn areStrLinesEqual(comptime T: type, a: T, b: T, comptime check_comma_disparity: bool) bool { + // Hypothesis: unlikely to be the same, since assert.equal, etc. is rarely + // used to compare the same object. May be true on shallow copies. + // TODO: check Godbolt + // if (a.ptr == b.ptr) return true; + + // []const u8 -> u8 + const info = @typeInfo(T); + const ChildType = info.Pointer.child; + + if (comptime !check_comma_disparity) { + return mem.eql(ChildType, a, b); + } + + const largest, const smallest = if (a.len > b.len) .{ a, b } else .{ b, a }; + return switch (largest.len - smallest.len) { + inline 0 => mem.eql(ChildType, a, b), + inline 1 => largest[largest.len - 1] == ',' and mem.eql(ChildType, largest[0..smallest.len], smallest), // 'foo,' == 'foo' + else => false, + }; +} + +// ============================================================================= +// =================================== TYPES =================================== +// ============================================================================= + +/// Generic equality function. Returns `true` if two lines are equal. +pub fn LineCmp(Line: type) type { + return fn (a: Line, b: Line) bool; +} + +pub const Error = error{ + DiffTooLarge, + InputsTooLarge, +} || Allocator.Error; + +const TraceFrame = std.ArrayListUnmanaged(u8); + +pub const DiffKind = enum { + insert, + delete, + equal, + + pub fn format(value: DiffKind, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + return switch (value) { + .insert => writer.writeByte('+'), + .delete => writer.writeByte('-'), + .equal => writer.writeByte(' '), + }; + } +}; + +pub fn Diff(comptime T: type) type { + return struct { + kind: DiffKind, + value: T, + + const Self = @This(); + pub fn eql(self: Self, other: Self) bool { + return self.kind == other.kind and mem.eql(T, self.value, other.value); + } + + /// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void + pub fn format(value: anytype, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + const specifier = switch (T) { + u8 => "c", + u32 => "u", + []const u8, [:0]const u8, []u8, [:0]u8 => "s", + else => @compileError("printDiff can only print chars and strings. Received: " ++ @typeName(T)), + }; + return writer.print("{} {" ++ specifier ++ "}", .{ value.kind, value.value }); + } + }; +} + +pub fn DiffList(comptime T: type) type { + return std.ArrayList(Diff(T)); +} + +// ============================================================================= + +const t = std.testing; +test areLinesEqual { + // check_comma_disparity is never respected when comparing chars + try t.expect(areLinesEqual(u8, 'a', 'a', false)); + try t.expect(areLinesEqual(u8, 'a', 'a', true)); + try t.expect(!areLinesEqual(u8, ',', 'a', false)); + try t.expect(!areLinesEqual(u8, ',', 'a', true)); + + // strings w/o comma check + try t.expect(areLinesEqual([]const u8, "", "", false)); + try t.expect(areLinesEqual([]const u8, "a", "a", false)); + try t.expect(areLinesEqual([]const u8, "Bun", "Bun", false)); + try t.expect(areLinesEqual([]const u8, "😤", "😤", false)); + // not equal + try t.expect(!areLinesEqual([]const u8, "", "a", false)); + try t.expect(!areLinesEqual([]const u8, "", " ", false)); + try t.expect(!areLinesEqual([]const u8, "\n", "\t", false)); + try t.expect(!areLinesEqual([]const u8, "bun", "Bun", false)); + try t.expect(!areLinesEqual([]const u8, "😤", "😩", false)); + + // strings w/ comma check + try t.expect(areLinesEqual([]const u8, "", "", true)); + try t.expect(areLinesEqual([]const u8, "", ",", true)); + try t.expect(areLinesEqual([]const u8, " ", " ,", true)); + try t.expect(areLinesEqual([]const u8, "I am speed", "I am speed", true)); + try t.expect(areLinesEqual([]const u8, "I am speed,", "I am speed", true)); + try t.expect(areLinesEqual([]const u8, "I am speed", "I am speed,", true)); + try t.expect(areLinesEqual([]const u8, "😤", "😤", false)); + // try t.expect(areLinesEqual([]const u8, "😤", "😤,", false)); + // try t.expect(areLinesEqual([]const u8, "😤,", "😤", false)); + // not equal + try t.expect(!areLinesEqual([]const u8, "", "Bun", true)); + try t.expect(!areLinesEqual([]const u8, "bun", "Bun", true)); + try t.expect(!areLinesEqual([]const u8, ",Bun", "Bun", true)); + try t.expect(!areLinesEqual([]const u8, "Bun", ",Bun", true)); + try t.expect(!areLinesEqual([]const u8, "", " ,", true)); + try t.expect(!areLinesEqual([]const u8, " ", " , ", true)); + try t.expect(!areLinesEqual([]const u8, "I, am speed", "I am speed", true)); + try t.expect(!areLinesEqual([]const u8, ",😤", "😤", true)); +} + +// const CharList = DiffList(u8); +// const CDiff = Diff(u8); +// const CharDiffer = Differ(u8, .{}); + +// fn testCharDiff(actual: []const u8, expected: []const u8, expected_diff: []const Diff(u8)) !void { +// const allocator = t.allocator; +// const actual_diff = try CharDiffer.diff(allocator, actual, expected); +// defer actual_diff.deinit(); +// try t.expectEqualSlices(Diff(u8), expected_diff, actual_diff.items); +// } + +// test CharDiffer { +// const TestCase = std.meta.Tuple(&[_]type{ []const CDiff, []const u8, []const u8 }); +// const test_cases = &[_]TestCase{ +// .{ &[_]CDiff{}, "foo", "foo" }, +// }; +// for (test_cases) |test_case| { +// const expected_diff, const actual, const expected = test_case; +// try testCharDiff(actual, expected, expected_diff); +// } +// } + +const StrDiffer = Differ([]const u8, .{ .check_comma_disparity = true }); +test StrDiffer { + const a = t.allocator; + inline for (.{ + // .{ "foo", "foo" }, + // .{ "foo", "bar" }, + .{ + // actual + \\[ + \\ 1, + \\ 2, + \\ 3, + \\ 4, + \\ 5, + \\ 6, + \\ 7 + \\] + , + // expected + \\[ + \\ 1, + \\ 2, + \\ 3, + \\ 4, + \\ 5, + \\ 9, + \\ 7 + \\] + }, + // // remove line + // .{ + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + // \\culpa qui officia deserunt mollit anim id est laborum. + // , + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + // \\culpa qui officia deserunt mollit anim id est laborum. + // , + // }, + // // add some line + // .{ + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + // \\culpa qui officia deserunt mollit anim id est laborum. + // , + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + // \\culpa qui officia deserunt mollit anim id est laborum. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // , + // }, + // // modify lines + // .{ + // \\foo + // \\bar + // \\baz + // , + // \\foo + // \\barrr + // \\baz + // }, + // .{ + // \\foooo + // \\bar + // \\baz + // , + // \\foo + // \\bar + // \\baz + // }, + // .{ + // \\foo + // \\bar + // \\baz + // , + // \\foo + // \\bar + // \\baz + // }, + // .{ + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + // \\culpa qui officia deserunt mollit anim id est laborum. + // , + // \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor modified + // \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + // \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + // \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + // \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in also modified + // \\culpa qui officia deserunt mollit anim id est laborum. + // , + // }, + }) |thing| { + var actual = try split(u8, a, thing[0]); + var expected = try split(u8, a, thing[1]); + defer { + actual.deinit(a); + expected.deinit(a); + } + var d = try StrDiffer.diff(a, actual.items, expected.items); + defer d.deinit(); + for (d.items) |diff| { + std.debug.print("{}\n", .{diff}); + } + } +} + +pub fn split( + comptime T: type, + alloc: Allocator, + s: []const T, +) Allocator.Error!std.ArrayListUnmanaged([]const T) { + comptime { + if (T != u8 and T != u16) { + @compileError("Split only supports latin1, utf8, and utf16. Received: " ++ @typeName(T)); + } + } + const newline: T = if (comptime T == u8) '\n' else '\n'; + // + // thing + var it = std.mem.splitScalar(T, s, newline); + var lines = std.ArrayListUnmanaged([]const T){}; + try lines.ensureUnusedCapacity(alloc, s.len >> 4); + errdefer lines.deinit(alloc); + while (it.next()) |l| { + try lines.append(alloc, l); + } + + return lines; +} diff --git a/src/bun.js/node/node_assert.zig b/src/bun.js/node/node_assert.zig new file mode 100644 index 0000000000..36d944975a --- /dev/null +++ b/src/bun.js/node/node_assert.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const bun = @import("root").bun; +const MyersDiff = @import("./assert/myers_diff.zig"); + +const Allocator = std.mem.Allocator; +const BunString = bun.String; + +const JSC = bun.JSC; +const JSValue = JSC.JSValue; + +const StringDiffList = MyersDiff.DiffList([]const u8); + +const print = std.debug.print; + +/// Compare `actual` and `expected`, producing a diff that would turn `actual` +/// into `expected`. +/// +/// Lines in the returned diff have the same encoding as `actual` and +/// `expected`. Lines borrow from these inputs, but the diff list itself must +/// be deallocated. +/// +/// Use an arena allocator, otherwise this will leak memory. +/// +/// ## Invariants +/// If not met, this function will panic. +/// - `actual` and `expected` are alive and have the same encoding. +pub fn myersDiff( + allocator: Allocator, + global: *JSC.JSGlobalObject, + actual: *const BunString, + expected: *const BunString, + // If true, strings that have a trailing comma but are otherwise equal are + // considered equal. + check_comma_disparity: bool, + // split `actual` and `expected` into lines before diffing + lines: bool, +) bun.JSError!JSC.JSValue { + // Short circuit on empty strings. Note that, in release builds where + // assertions are disabled, if `actual` and `expected` are both dead, this + // branch will be hit since dead strings have a length of 0. This should be + // moot since BunStrings with non-zero reference counds should never be + // dead. + if (actual.length() == 0 and expected.length() == 0) { + return JSC.JSValue.createEmptyArray(global, 0); + } + + const actual_encoding = actual.encoding(); + const expected_encoding = expected.encoding(); + + if (lines) { + if (actual_encoding != expected_encoding) { + const actual_utf8 = actual.toUTF8WithoutRef(allocator); + defer actual_utf8.deinit(); + const expected_utf8 = expected.toUTF8WithoutRef(allocator); + defer expected_utf8.deinit(); + + return diffLines(u8, allocator, global, actual_utf8.byteSlice(), expected_utf8.byteSlice(), check_comma_disparity); + } + + return switch (actual_encoding) { + .latin1, .utf8 => diffLines(u8, allocator, global, actual.byteSlice(), expected.byteSlice(), check_comma_disparity), + .utf16 => diffLines(u16, allocator, global, actual.utf16(), expected.utf16(), check_comma_disparity), + }; + } + + if (actual_encoding != expected_encoding) { + const actual_utf8 = actual.toUTF8WithoutRef(allocator); + defer actual_utf8.deinit(); + const expected_utf8 = expected.toUTF8WithoutRef(allocator); + defer expected_utf8.deinit(); + + return diffChars(u8, allocator, global, actual.byteSlice(), expected.byteSlice()); + } + + return switch (actual_encoding) { + .latin1, .utf8 => diffChars(u8, allocator, global, actual.byteSlice(), expected.byteSlice()), + .utf16 => diffChars(u16, allocator, global, actual.utf16(), expected.utf16()), + }; +} + +fn diffChars( + comptime T: type, + allocator: Allocator, + global: *JSC.JSGlobalObject, + actual: []const T, + expected: []const T, +) bun.JSError!JSC.JSValue { + const Differ = MyersDiff.Differ(T, .{ .check_comma_disparity = false }); + const diff: MyersDiff.DiffList(T) = Differ.diff(allocator, actual, expected) catch |err| return mapDiffError(global, err); + return diffListToJS(T, global, diff); +} + +fn diffLines( + comptime T: type, + allocator: Allocator, + global: *JSC.JSGlobalObject, + actual: []const T, + expected: []const T, + check_comma_disparity: bool, +) bun.JSError!JSC.JSValue { + var a = try MyersDiff.split(T, allocator, actual); + defer a.deinit(allocator); + var e = try MyersDiff.split(T, allocator, expected); + defer e.deinit(allocator); + + const diff: MyersDiff.DiffList([]const T) = blk: { + if (check_comma_disparity) { + const Differ = MyersDiff.Differ([]const T, .{ .check_comma_disparity = true }); + break :blk Differ.diff(allocator, a.items, e.items) catch |err| return mapDiffError(global, err); + } else { + const Differ = MyersDiff.Differ([]const T, .{ .check_comma_disparity = false }); + break :blk Differ.diff(allocator, a.items, e.items) catch |err| return mapDiffError(global, err); + } + }; + return diffListToJS([]const T, global, diff); +} + +fn diffListToJS(comptime T: type, global: *JSC.JSGlobalObject, diff_list: MyersDiff.DiffList(T)) bun.JSError!JSC.JSValue { + var array = JSC.JSValue.createEmptyArray(global, diff_list.items.len); + for (diff_list.items, 0..) |*line, i| { + array.putIndex(global, @truncate(i), JSC.JSObject.createNullProto(line.*, global).toJS()); + } + return array; +} + +fn mapDiffError(global: *JSC.JSGlobalObject, err: MyersDiff.Error) bun.JSError { + return switch (err) { + error.OutOfMemory => error.OutOfMemory, + error.DiffTooLarge => global.throwInvalidArguments("Diffing these two values would create a string that is too large. If this was intentional, please open a bug report on GitHub.", .{}), + error.InputsTooLarge => global.throwInvalidArguments("Input strings are too large to diff. Please open a bug report on GitHub.", .{}), + }; +} 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 0000000000..824e06bb7c --- /dev/null +++ b/src/bun.js/node/node_assert_binding.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const bun = @import("root").bun; +const assert = @import("./node_assert.zig"); +const DiffList = @import("./assert/myers_diff.zig").DiffList; +const Allocator = std.mem.Allocator; + +const JSC = bun.JSC; +const JSValue = JSC.JSValue; + +/// ```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 { + var stack_fallback = std.heap.stackFallback(1024 * 2, bun.default_allocator); + var arena = std.heap.ArenaAllocator.init(stack_fallback.get()); + defer arena.deinit(); + const allocator = arena.allocator(); + + const nargs = callframe.argumentsCount(); + if (nargs < 2) { + return global.throwNotEnoughArguments("printMyersDiff", 2, callframe.argumentsCount()); + } + + const actual_arg: JSValue = callframe.argument(0); + const expected_arg: JSValue = callframe.argument(1); + const check_comma_disparity: bool, const lines: bool = switch (nargs) { + 0, 1 => unreachable, + 2 => .{ false, false }, + 3 => .{ callframe.argument(2).isTruthy(), false }, + else => .{ callframe.argument(2).isTruthy(), callframe.argument(3).isTruthy() }, + }; + + if (!actual_arg.isString()) return global.throwInvalidArgumentTypeValue("actual", "string", actual_arg); + if (!expected_arg.isString()) return global.throwInvalidArgumentTypeValue("expected", "string", expected_arg); + + const actual_str = try actual_arg.toBunString2(global); + defer actual_str.deref(); + const expected_str = try expected_arg.toBunString2(global); + defer expected_str.deref(); + + bun.assertWithLocation(actual_str.tag != .Dead, @src()); + bun.assertWithLocation(expected_str.tag != .Dead, @src()); + + return assert.myersDiff( + allocator, + global, + &actual_str, + &expected_str, + check_comma_disparity, + lines, + ); +} + +const StrDiffList = DiffList([]const u8); +fn diffListToJS(global: *JSC.JSGlobalObject, diff_list: StrDiffList) bun.JSError!JSC.JSValue { + // todo: replace with toJS + var array = JSC.JSValue.createEmptyArray(global, diff_list.items.len); + for (diff_list.items, 0..) |*line, i| { + var obj = JSC.JSValue.createEmptyObjectWithNullPrototype(global); + if (obj == .zero) return global.throwOutOfMemory(); + obj.put(global, bun.String.static("kind"), JSC.JSValue.jsNumber(@as(u32, @intFromEnum(line.kind)))); + obj.put(global, bun.String.static("value"), JSC.toJS(global, []const u8, line.value, .allocated)); + array.putIndex(global, @truncate(i), obj); + } + return array; +} + +// ============================================================================= + +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/codegen/bundle-modules.ts b/src/codegen/bundle-modules.ts index 3427102121..6be58be40a 100644 --- a/src/codegen/bundle-modules.ts +++ b/src/codegen/bundle-modules.ts @@ -448,6 +448,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/builtins.d.ts b/src/js/builtins.d.ts index 5f5a28b04d..aa38f45245 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -10,8 +10,14 @@ type TODO = any; * This only works in debug builds, the log fn is completely removed in release builds. */ declare function $debug(...args: any[]): void; -/** $assert is a preprocessor macro that only runs in debug mode. it throws an error if the first argument is falsy. - * The source code passed to `check` is inlined in the message, but in addition you can pass additional messages. +/** + * Assert that a condition holds in debug builds. + * + * $assert is a preprocessor macro that only runs in debug mode. it throws an + * error if the first argument is falsy. The source code passed to `check` is + * inlined in the message, but in addition you can pass additional messages. + * + * @note gets removed in release builds. Do not put code with side effects in the `check`. */ declare function $assert(check: any, ...message: any[]): asserts check; diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts new file mode 100644 index 0000000000..3ee25df569 --- /dev/null +++ b/src/js/internal/assert/assertion_error.ts @@ -0,0 +1,425 @@ +"use strict"; + +const { + ArrayPrototypeJoin, + ArrayPrototypePop, + ArrayPrototypeSlice, + Error, + ErrorCaptureStackTrace, + ObjectAssign, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + String, + StringPrototypeRepeat, + StringPrototypeSlice, + StringPrototypeSplit, +} = require("internal/primordials"); + + +const { inspect } = require("internal/util/inspect"); +const colors = require("internal/util/colors"); +const { validateObject } = require("internal/validators"); + +declare namespace Internal { + const enum Operation { + Insert = 0, + Delete = 1, + Equal = 2, + } + interface Diff { + kind: Operation; + value: string; + } + + function myersDiff(actual: string, expected: string, checkCommaDisparity?: boolean, lines?: boolean): string; + // todo + + function printMyersDiff(...args: any[]): any; + function printSimpleMyersDiff(...args: any[]): any; +} + +const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require("internal/assert/myers_diff") as typeof Internal; + +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 (Error.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, "")); + const diff = myersDiff(actual, expected, false, false); + 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: string, originalExpected, expected: string) { + 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(inspectedActual, inspectedExpected, checkCommaDisparity, true); + + 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; + + // NOTE: stack trace is always writable. + const limit = Error.stackTraceLimit; + 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}`); + } + } + } + + 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 0000000000..25dea7baee --- /dev/null +++ b/src/js/internal/assert/calltracker.ts @@ -0,0 +1,141 @@ +"use strict"; + +const { + ArrayPrototypePush, + ArrayPrototypeSlice, + Error, + FunctionPrototype, + ObjectFreeze, + Proxy, + SafeSet, + SafeWeakMap, +} = require("internal/primordials"); + +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 fn.$apply(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 0000000000..63d0fb45d8 --- /dev/null +++ b/src/js/internal/assert/myers_diff.ts @@ -0,0 +1,111 @@ +/// +"use strict"; + +const colors = require("internal/util/colors"); + +const enum Operation { + Insert = 0, + Delete = 1, + Equal = 2, +} +interface Diff { + kind: Operation; + /** + * When diffing chars (that is, `line == false`, this is a char code.) + */ + value: string | number; +} + +declare namespace Internal { + export function myersDiff( + actual: string[], + expected: string[], + checkCommaDisparity?: boolean, + lines?: boolean, + ): Diff[]; +} + +const kNopLinesToCollapse = 5; + +const { myersDiff } = $zig("node_assert_binding.zig", "generate") as typeof Internal; + +function printSimpleMyersDiff(diff: Diff[]) { + let message = ""; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + let { kind, value } = diff[diffIdx]; + if (typeof value === "number") { + value = String.fromCharCode(value); + } + switch (kind) { + 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: ${kind}`); // should be unreachable + } + } + + return `\n${message}`; +} + +function printMyersDiff(diff: Diff[], simple = false) { + let message = ""; + let skipped = false; + let nopCount = 0; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + const { kind, value } = diff[diffIdx]; + $assert( + typeof value !== "number", + "printMyersDiff is only called for line diffs, which never return numeric char code values.", + ); + const previousType = diffIdx < diff.length - 1 ? diff[diffIdx + 1].kind : null; + const typeChanged = previousType && kind !== previousType; + + 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`; + } 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; + } + + switch (kind) { + 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: ${kind}`); // should be unreachable + } + } + + 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 0000000000..1f46df9a70 --- /dev/null +++ b/src/js/internal/assert/utils.ts @@ -0,0 +1,288 @@ +/* prettier-ignore */ +'use strict'; + +// const { +// ArrayPrototypeShift, +// Error, +// ErrorCaptureStackTrace, +// FunctionPrototypeBind, +// RegExpPrototypeSymbolReplace, +// SafeMap, +// StringPrototypeCharCodeAt, +// StringPrototypeIncludes, +// StringPrototypeIndexOf, +// StringPrototypeReplace, +// StringPrototypeSlice, +// StringPrototypeSplit, +// StringPrototypeStartsWith, +// } = require("internal/primordials"); + +var AssertionError; +function loadAssertionError() { + if (AssertionError === undefined) { + AssertionError = require("internal/assert/assertion_error"); + } +} + +// const { Buffer } = require('node:buffer'); +// const { +// isErrorStackTraceLimitWritable, +// overrideStackTrace, +// } = require('internal/errors'); +// 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") { + // 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 (Error.isError(message)) { + throw message; + } + + if (AssertionError === undefined) loadAssertionError(); + 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 bd54b95070..fe0a31e52b 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -76,10 +76,13 @@ 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; - // Remove the second line, which is this function - targetObject.stack = stack.replace(/.*\n.*/, "$1"); +function ErrorCaptureStackTrace(targetObject, maybeStartStackFn) { + Error.captureStackTrace(targetObject, maybeStartStackFn); + + if (maybeStartStackFn === undefined) { + // Remove the second line, which is this function + targetObject.stack = targetObject.stack.replace(/.*\n.*/, "$1"); + } } const arrayProtoPush = Array.prototype.push; @@ -94,6 +97,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,9 +114,15 @@ export default { DatePrototypeGetTime: uncurryThis(Date.prototype.getTime), DatePrototypeToISOString: uncurryThis(Date.prototype.toISOString), DatePrototypeToString: uncurryThis(Date.prototype.toString), + Error, ErrorCaptureStackTrace, ErrorPrototypeToString: uncurryThis(Error.prototype.toString), + FunctionPrototypeBind: uncurryThis(Function.prototype.bind), + FunctionPrototypeCall: uncurryThis(Function.prototype["call"]), + FunctionPrototypeToString: uncurryThis(Function.prototype.toString), JSONStringify: JSON.stringify, + MapPrototypeDelete: uncurryThis(Map.prototype.delete), + MapPrototypeSet: uncurryThis(Map.prototype.set), MapPrototypeGetSize: getGetter(Map, "size"), MapPrototypeEntries: uncurryThis(Map.prototype.entries), MapPrototypeValues: uncurryThis(Map.prototype.values), @@ -144,10 +154,12 @@ 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, + ReflectHas: Reflect.has, ReflectOwnKeys: Reflect.ownKeys, RegExp, RegExpPrototypeExec: uncurryThis(RegExp.prototype.exec), @@ -172,12 +184,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), @@ -200,8 +221,6 @@ export default { StringPrototypeValueOf: uncurryThis(String.prototype.valueOf), SymbolPrototypeToString: uncurryThis(Symbol.prototype.toString), SymbolPrototypeValueOf: uncurryThis(Symbol.prototype.valueOf), - FunctionPrototypeToString: uncurryThis(Function.prototype.toString), - FunctionPrototypeBind: uncurryThis(Function.prototype.bind), SymbolDispose: Symbol.dispose, SymbolAsyncDispose: Symbol.asyncDispose, SymbolIterator: Symbol.iterator, diff --git a/src/js/internal/util/colors.ts b/src/js/internal/util/colors.ts new file mode 100644 index 0000000000..1b4b5ecc62 --- /dev/null +++ b/src/js/internal/util/colors.ts @@ -0,0 +1,59 @@ +// Taken from Node - lib/internal/util/colors.js +"use strict"; + +type WriteStream = import("node:tty").WriteStream; +type GetColorDepth = (this: import("node:tty").WriteStream, env?: NodeJS.ProcessEnv) => number; + +let getColorDepth: undefined | GetColorDepth; +const lazyGetColorDepth = (): GetColorDepth => + (getColorDepth ??= require("node:tty").WriteStream.prototype.getColorDepth); + +let exports = { + blue: "", + green: "", + white: "", + yellow: "", + red: "", + gray: "", + clear: "", + reset: "", + hasColors: false, + shouldColorize(stream: WriteStream) { + if (stream?.isTTY) { + const depth = lazyGetColorDepth().$call(stream); + console.error("stream is a tty with color depth", depth); + return depth > 2; + } + + // do not cache these since users may update them as the process runs + const { NO_COLOR, NODE_DISABLE_COLORS, FORCE_COLOR } = process.env; + return NO_COLOR === undefined && NODE_DISABLE_COLORS === undefined && FORCE_COLOR !== "0"; + }, + 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 daf536b8da..a5ef9adc2e 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -1,1257 +1,995 @@ -// 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 - ); - } - 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_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; - }, -}); +const { + ArrayFrom, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + Error, + FunctionPrototypeCall, + NumberIsNaN, + ObjectAssign, + ObjectIs, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + ReflectHas, + ReflectOwnKeys, + RegExpPrototypeExec, + SafeMap, + SafeSet, + SafeWeakSet, + StringPrototypeIndexOf, + StringPrototypeSlice, + StringPrototypeSplit, + SymbolIterator, +} = require("internal/primordials"); -// 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]); - }); - } - 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); - } - } - 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); - } - 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) - ); - } - 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; - } - } - 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, - }); - } - 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], - ` +const { Buffer } = require("node:buffer"); +const { isKeyObject, isPromise, isRegExp, isMap, isSet, isDate, isWeakSet, isWeakMap } = require("node:util/types"); +const { innerOk } = require("internal/assert/utils"); -`, - ) + - "".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)); - } - } - } - 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)); +const { validateFunction } = require("internal/validators"); - ) - (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, - ` +type nodeAssert = typeof import("node:assert"); -`, - ) - .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) - ); - } - 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)), - )); - } - 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"); - } - - 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_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); - } - 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", - ); - } - argsLen === 2 && (operator = "!="); - } - if (message instanceof Error) throw message; - var errArgs = { - 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); - } - } - 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); - } - innerFail({ - actual, - expected, - message, - operator: fn.name, - stackStartFn: 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); - } - var keys = Object.keys(expected); - if (expected instanceof Error) keys.push("name", "message"); - else if (keys.length === 0) throw $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 - ); - } - 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; - } - return NO_EXCEPTION_SENTINEL; - } - 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 (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; - } - } - 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; - } - } - newErr.stack = "" - .concat( - tmp1.join(` -`), - ` -`, - ) - .concat( - tmp2.join(` -`), - ); - } - throw newErr; - } - }; - 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, - }); - assert.strict.strict = assert.strict; - }, -}); -var assert_module = require_assert(); - -function CallTracker() { - throw new Error("CallTracker is not supported yet"); +function isDeepEqual(a, b) { + return Bun.deepEquals(a, b, false); +} +function isDeepStrictEqual(a, b) { + return Bun.deepEquals(a, b, true); } -assert_module["CallTracker"] = CallTracker; +var _inspect; +function lazyInspect() { + if (_inspect === undefined) { + _inspect = require("internal/util/inspect").inspect; + } + return _inspect; +} -export default assert_module; +var AssertionError; +function loadAssertionError() { + if (AssertionError === undefined) { + AssertionError = require("internal/assert/assertion_error"); + } +} + +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", + ); + } + if (argsLen === 2) operator = "!="; + } + + if (message instanceof Error) throw message; + + const errArgs = { + actual, + expected, + operator: operator === undefined ? "fail" : operator, + stackStartFn: stackStartFn || fail, + message, + }; + if (AssertionError === undefined) loadAssertionError(); + 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; +Object.defineProperty(assert, "AssertionError", { + get() { + loadAssertionError(); + return AssertionError; + }, + set(value) { + AssertionError = value; + }, + configurable: true, + enumerable: true, +}); + +/** + * Pure assertion tests whether a value is truthy, as determined + * by !!value. + * @param {...any} args + * @returns {void} + */ + +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(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(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 (!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 (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" || Error.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)) { + return Bun.deepEquals(actual, expected, true); + } + + for (const type of typesToCallDeepStrictEqualWith) { + if (type(actual) || type(expected)) { + return isDeepStrictEqual(actual, expected); + } + } + + // 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 + } + + 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; + } + + return true; + } + + // Check if expected array is a subset of actual array + if ($isArray(actual) && $isArray(expected)) { + if (expected.length > actual.length) { + return false; + } + + // 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)) { + expectedCounts.$set(key, count + 1); + found = true; + break; + } + } + if (!found) { + expectedCounts.$set(expectedItem, 1); + } + } + + // 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) { + expectedCounts.$delete(key); + } else { + expectedCounts.$set(key, count - 1); + } + break; + } + } + } + + return !expectedCounts.size; + } + + // Comparison done when at least one of the values is not an object + if (isSpecial(actual) || isSpecial(expected)) { + 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); + + if (AssertionError === undefined) loadAssertionError(); + // 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; + } + } + + 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]; + } + } + } + } +} + +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); + + if (AssertionError === undefined) loadAssertionError(); + 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; + const inspect = lazyInspect(); + + 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) { + if (AssertionError === undefined) loadAssertionError(); + const err = new AssertionError({ + actual, + expected, + 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"); + } + for (const key of keys) { + if ( + typeof actual[key] === "string" && + isRegExp(expected[key]) && + RegExpPrototypeExec(expected[key], actual[key]) !== null + ) { + continue; + } + compareExceptionKey(actual, expected, key, message, keys, fn); + } + 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 (Error.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}"`; + } + if (actual.message) { + message += `\n\nError message:\n\n${actual.message}`; + } + } else { + message += `"${lazyInspect()(actual, { depth: -1 })}"`; + } + } + throwError = true; + } else { + // Check validation functions return value. + const res = expected.$apply({}, [actual]); + if (res !== true) { + if (!message) { + generatedMessage = true; + const name = expected.name ? `"${expected.name}" ` : ""; + const inspect = lazyInspect(); + message = `The ${name}validation function is expected to return` + ` "true". Received ${inspect(res)}`; + + if (Error.isError(actual)) { + message += `\n\nCaught error:\n\n${actual}`; + } + } + throwError = true; + } + } + + if (throwError) { + if (AssertionError === undefined) loadAssertionError(); + 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 (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 expected.$apply({}, [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, ...args: 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 { + const inspect = lazyInspect(); + message += inspect(err); + } + + if (AssertionError === undefined) loadAssertionError(); + 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; + } + } + const stackStart = ArrayPrototypeJoin(newFrames, "\n"); + const stackEnd = ArrayPrototypeJoin(originalFrames, "\n"); + newErr.stack = `${stackStart}\n${stackEnd}`; + } + } + + 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; + const inspect = lazyInspect(); + + // '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`; + if (AssertionError === undefined) loadAssertionError(); + const err = new AssertionError({ + actual: string, + expected: regexp, + message, + operator: fn.name, + stackStartFn: fn, + }); + 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); +}; + +/** + * 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); +}; + +var CallTracker; +Object.defineProperty(assert, "CallTracker", { + get() { + if (CallTracker === undefined) { + const { deprecate } = require("node:util"); + CallTracker = deprecate(require("internal/assert/calltracker"), "assert.CallTracker is deprecated.", "DEP0173"); + } + return CallTracker; + }, + set(value) { + CallTracker = value; + }, + configurable: true, + enumerable: true, +}); +// assert.CallTracker = CallTracker + +/** + * Expose a strict only variant of assert. + * @param {...any} args + * @returns {void} + */ +function strict(...args) { + innerOk(strict, args.length, ...args); +} + +assert.strict = ObjectAssign(strict, assert, { + equal: assert.strictEqual, + deepEqual: assert.deepStrictEqual, + notEqual: assert.notStrictEqual, + notDeepEqual: assert.notDeepStrictEqual, +}); + +assert.strict.strict = assert.strict; diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json index 5f4eb774ba..279f79762a 100644 --- a/src/js/tsconfig.json +++ b/src/js/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "esModuleInterop": true, // Path remapping "baseUrl": ".", "paths": { diff --git a/src/string.zig b/src/string.zig index 4f874a9864..d90f2bcf1b 100644 --- a/src/string.zig +++ b/src/string.zig @@ -994,6 +994,7 @@ pub const String = extern struct { return ZigString.Slice.empty; } + /// use `byteSlice` to get a `[]const u8`. pub fn toSlice(this: String, allocator: std.mem.Allocator) SliceWithUnderlyingString { return SliceWithUnderlyingString{ .utf8 = this.toUTF8(allocator), diff --git a/test/js/node/assert/assert-doesNotMatch.test.cjs b/test/js/node/assert/assert-doesNotMatch.test.cjs index 14ffd2eae2..136ea095db 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 4eb097e357..29f2d117eb 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", () => { diff --git a/test/js/node/bunfig.toml b/test/js/node/bunfig.toml index cac7f387d5..946890e448 100644 --- a/test/js/node/bunfig.toml +++ b/test/js/node/bunfig.toml @@ -1 +1,2 @@ -preload = ["./harness.ts"] +[test] +preload = ["./harness.ts", "../../preload.ts"] diff --git a/test/js/node/child_process/child_process-node.test.js b/test/js/node/child_process/child_process-node.test.js index 1d4a7a4305..ff4699e1e1 100644 --- a/test/js/node/child_process/child_process-node.test.js +++ b/test/js/node/child_process/child_process-node.test.js @@ -659,7 +659,7 @@ describe("fork", () => { code: "ERR_INVALID_ARG_TYPE", name: "TypeError", message: expect.stringContaining( - `The "modulePath" argument must be of type string, Buffer, or URL. Received `, + `The "modulePath" argument must be of type string, Buffer or URL. Received `, ), }), ); diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts index f723a749ac..c13b3a5afc 100644 --- a/test/js/node/harness.ts +++ b/test/js/node/harness.ts @@ -1,14 +1,15 @@ /** * @note this file patches `node:test` via the require cache. */ -import {AnyFunction} from "bun"; -import {hideFromStackTrace} from "harness"; +import { AnyFunction } from "bun"; +import os from "node:os"; +import { hideFromStackTrace } from "harness"; import assertNode from "node:assert"; type DoneCb = (err?: Error) => any; function noop() {} export function createTest(path: string) { - const {expect, test, it, describe, beforeAll, afterAll, beforeEach, afterEach, mock} = Bun.jest(path); + const { expect, test, it, describe, beforeAll, afterAll, beforeEach, afterEach, mock } = Bun.jest(path); hideFromStackTrace(expect); @@ -204,11 +205,11 @@ export function createTest(path: string) { let completed = 0; const globalTimer = globalTimeout ? (timers.push( - setTimeout(() => { - console.log("Global Timeout"); - done(new Error("Timed out!")); - }, globalTimeout), - ), + setTimeout(() => { + console.log("Global Timeout"); + done(new Error("Timed out!")); + }, globalTimeout), + ), timers[timers.length - 1]) : undefined; function createDoneCb(timeout?: number) { @@ -216,11 +217,11 @@ export function createTest(path: string) { const timer = timeout !== undefined ? (timers.push( - setTimeout(() => { - console.log("Timeout"); - done(new Error("Timed out!")); - }, timeout), - ), + setTimeout(() => { + console.log("Timeout"); + done(new Error("Timed out!")); + }, timeout), + ), timers[timers.length - 1]) : timeout; return (result?: Error) => { @@ -266,9 +267,9 @@ declare namespace Bun { function jest(path: string): typeof import("bun:test"); } -if (Bun.main.includes("node/test/parallel")) { +const normalized = os.platform() === "win32" ? Bun.main.replaceAll("\\", "/") : Bun.main; +if (normalized.includes("node/test/parallel")) { function createMockNodeTestModule() { - interface TestError extends Error { testStack: string[]; } @@ -279,8 +280,8 @@ if (Bun.main.includes("node/test/parallel")) { successes: number; addFailure(err: unknown): TestError; recordSuccess(): void; - } - const contexts: Record = {} + }; + const contexts: Record = {}; // @ts-ignore let activeSuite: Context = undefined; @@ -305,13 +306,13 @@ if (Bun.main.includes("node/test/parallel")) { 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; - return activeSuite = (contexts[key] ??= createContext(key)); + const key: string = Bun.main; // module.parent?.filename ?? require.main?.filename ?? __filename; + return (activeSuite = contexts[key] ??= createContext(key)); } async function test(label: string | Function, fn?: Function | undefined) { @@ -333,7 +334,7 @@ if (Bun.main.includes("node/test/parallel")) { } function describe(labelOrFn: string | Function, maybeFn?: Function) { - const [label, fn] = (typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn]); + 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); @@ -341,7 +342,7 @@ if (Bun.main.includes("node/test/parallel")) { fn(); } catch (e) { getContext().addFailure(e); - throw e + throw e; } finally { getContext().testStack.pop(); } @@ -352,14 +353,12 @@ if (Bun.main.includes("node/test/parallel")) { if (failures > 0) { throw new Error(`${failures} tests failed.`); } - } return { test, describe, - } - + }; } require.cache["node:test"] ??= { diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index 40d0639a00..9c283bb4d4 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -107,7 +107,7 @@ function parseTestFlags(filename = process.argv[1]) { // `worker_threads`) and child processes. // If the binary was built without-ssl then the crypto flags are // invalid (bad option). The test itself should handle this case. -if (process.argv.length === 2 && +if ((process.argv.length === 2 || process.argv.length === 3) && !process.env.NODE_SKIP_FLAG_CHECK && isMainThread && hasCrypto && diff --git a/test/js/node/test/parallel/needs-test/README.md b/test/js/node/test/parallel/needs-test/README.md new file mode 100644 index 0000000000..821ae16ee3 --- /dev/null +++ b/test/js/node/test/parallel/needs-test/README.md @@ -0,0 +1,8 @@ +A good deal of parallel test cases can be run directly via `bun `. +However, some newer cases use `node:test`. + +Files in this directory need to be run with `bun test `. The +`node:test` module is shimmed via a require cache hack in +`test/js/node/harness.js` to use `bun:test`. Note that our test runner +(`scripts/runner.node.mjs`) checks for `needs-test` in the names of test files, +so don't rename this folder without updating that code. diff --git a/test/js/node/test/parallel/needs-test/test-assert.js b/test/js/node/test/parallel/needs-test/test-assert.js new file mode 100644 index 0000000000..d16194b57f --- /dev/null +++ b/test/js/node/test/parallel/needs-test/test-assert.js @@ -0,0 +1,1601 @@ +// 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('node: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' + + '- [Error: foobar]\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, 21); + 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: '0 == true' + } + ); + 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: 'false == true' + } + ); + assert.throws( + () => eval('console.log("FOO");\nassert.ok(1 === 2);'), + { + code: 'ERR_ASSERTION', + message: 'false == true' + } + ); + + 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"', + message: 'The "error" argument must be of type Object, Error, Function or RegExp.' + invalidArgTypeHelper('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.' + invalidArgTypeHelper({foo: 'bar'}) + } + ); + + 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.' + + invalidArgTypeHelper('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. ' + + // NOTE: invalidArgTypeHelper just says "received instanceof Object", + // as does message formatters in ErrorCode.cpp. we may want to change that in the future. + // invalidArgTypeHelper({abc: 123}), + '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.' + + invalidArgTypeHelper('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 */ + + diff --git a/test/js/node/test/parallel/test-child-process-exec-timeout-not-expired.js b/test/js/node/test/parallel/test-child-process-exec-timeout-not-expired.js index fb0af5fa8f..7c8dd3661a 100644 --- a/test/js/node/test/parallel/test-child-process-exec-timeout-not-expired.js +++ b/test/js/node/test/parallel/test-child-process-exec-timeout-not-expired.js @@ -27,8 +27,8 @@ const cmd = `"${process.execPath}" "${__filename}" child`; cp.exec(cmd, { timeout: kTimeoutNotSupposedToExpire }, common.mustSucceed((stdout, stderr) => { - assert.strictEqual(stdout.trim(), 'child stdout'); - assert.strictEqual(stderr.trim(), 'child stderr'); + assert.strict(stdout.trim().includes('child stdout')); + assert.strict(stderr.trim().includes('child stderr')); })); cleanupStaleProcess(__filename); diff --git a/test/js/node/test/parallel/test-child-process-spawnsync-input.js b/test/js/node/test/parallel/test-child-process-spawnsync-input.js index 62ae476ae1..4b4549ff55 100644 --- a/test/js/node/test/parallel/test-child-process-spawnsync-input.js +++ b/test/js/node/test/parallel/test-child-process-spawnsync-input.js @@ -48,7 +48,9 @@ function checkSpawnSyncRet(ret) { function verifyBufOutput(ret) { checkSpawnSyncRet(ret); + assert.deepStrictEqual(ret.stdout.toString('utf8'), msgOutBuf.toString('utf8')); assert.deepStrictEqual(ret.stdout, msgOutBuf); + assert.deepStrictEqual(ret.stderr.toString('utf8'), msgErrBuf.toString('utf8')); assert.deepStrictEqual(ret.stderr, msgErrBuf); }