diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index 7e842a348ebe..14ee14294eb0 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -79,8 +79,6 @@ function loadAssertionError() { } } -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. @@ -98,57 +96,24 @@ const NO_EXCEPTION_SENTINEL = {}; function innerFail(obj) { const objMessage = obj.message; - if (objMessage instanceof Error) throw objMessage; + if (Error.isError(objMessage)) throw objMessage; 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; +function fail(message?: string | Error): never { + if (Error.isError(message)) throw message; + + const internalMessage = message === undefined; + if (internalMessage) { 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, + actual: undefined, + expected: undefined, + operator: "fail", + stackStartFn: fail, message, }; if (AssertionError === undefined) loadAssertionError(); diff --git a/test/js/node/assert/assert-fail.test.ts b/test/js/node/assert/assert-fail.test.ts new file mode 100644 index 000000000000..80a41849f43a --- /dev/null +++ b/test/js/node/assert/assert-fail.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, it } from "bun:test"; +import { bunEnv, bunExe } from "harness"; +import assert from "node:assert"; +import vm from "node:vm"; + +// node v26 removed the end-of-life DEP0094 multi-argument behaviour of +// assert.fail: only the first argument is used (as the message, or thrown if +// it is an Error), operator is always "fail", actual/expected are undefined. +describe("assert.fail", () => { + const capture = (fn: () => void) => { + try { + fn(); + } catch (e: any) { + return { + message: e.message, + actual: e.actual, + expected: e.expected, + operator: e.operator, + generatedMessage: e.generatedMessage, + }; + } + throw new Error("assert.fail did not throw"); + }; + + it("with no arguments uses the default generated message", () => { + expect(capture(() => assert.fail())).toEqual({ + message: "Failed", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: true, + }); + }); + + it("with an explicit undefined message uses the default generated message", () => { + expect(capture(() => assert.fail(undefined))).toEqual({ + message: "Failed", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: true, + }); + }); + + it("treats a falsy non-Error message as generated (matches AssertionError)", () => { + expect(capture(() => assert.fail(""))).toEqual({ + message: "", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: true, + }); + }); + + it("uses the first argument as the message", () => { + expect(capture(() => assert.fail("boom"))).toEqual({ + message: "boom", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: false, + }); + }); + + it("ignores extra arguments (no legacy actual/expected synthesis)", () => { + expect(capture(() => (assert.fail as any)(1, 2))).toEqual({ + message: "1", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: false, + }); + expect(capture(() => (assert.fail as any)(1, 2, undefined, "=="))).toEqual({ + message: "1", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: false, + }); + expect(capture(() => (assert.fail as any)("a", "b", "m"))).toEqual({ + message: "a", + actual: undefined, + expected: undefined, + operator: "fail", + generatedMessage: false, + }); + }); + + it("throws the first argument when it is an Error", () => { + const err = new Error("custom"); + try { + assert.fail(err); + } catch (e) { + expect(e).toBe(err); + return; + } + throw new Error("assert.fail did not throw"); + }); + + it("throws a cross-realm Error instance directly", () => { + const err = vm.runInNewContext("new Error('cross-realm')"); + try { + assert.fail(err); + } catch (e) { + expect(e).toBe(err); + return; + } + throw new Error("assert.fail did not throw"); + }); + + it("does not emit a DEP0094 deprecation warning for multi-argument calls", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `const assert = require("node:assert"); + process.on("warning", w => { console.error("ROBOBUN_PROCESS_WARNING", w.name, w.code); process.exit(2); }); + try { assert.fail(1, 2); } catch {} + setImmediate(() => process.exit(0));`, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stderr).not.toContain("DEP0094"); + expect(stderr).not.toContain("ROBOBUN_PROCESS_WARNING"); + expect(stdout).toBe(""); + expect(exitCode).toBe(0); + }); +}); diff --git a/test/js/node/test/parallel/test-assert-fail-deprecation.js b/test/js/node/test/parallel/test-assert-fail-deprecation.js deleted file mode 100644 index ab31b08f953f..000000000000 --- a/test/js/node/test/parallel/test-assert-fail-deprecation.js +++ /dev/null @@ -1,70 +0,0 @@ -// Flags: --no-warnings -'use strict'; - -const { expectWarning } = require('../common'); -const assert = require('assert'); -const { test } = require('node:test'); - -expectWarning( - 'DeprecationWarning', - 'assert.fail() with more than one argument is deprecated. ' + - 'Please use assert.strictEqual() instead or only pass a message.', - 'DEP0094' -); - -test('Two args only, operator defaults to "!="', () => { - assert.throws(() => { - assert.fail('first', 'second'); - }, { - code: 'ERR_ASSERTION', - name: 'AssertionError', - message: '\'first\' != \'second\'', - operator: '!=', - actual: 'first', - expected: 'second', - generatedMessage: true - }); -}); - -test('Three args', () => { - assert.throws(() => { - assert.fail('ignored', 'ignored', 'another custom message'); - }, { - code: 'ERR_ASSERTION', - name: 'AssertionError', - message: 'another custom message', - operator: 'fail', - actual: 'ignored', - expected: 'ignored', - generatedMessage: false - }); -}); - -test('Three args with custom Error', () => { - assert.throws(() => { - assert.fail(typeof 1, 'object', new TypeError('another custom message')); - }, { - name: 'TypeError', - message: 'another custom message' - }); -}); - -test('No third arg (but a fourth arg)', () => { - assert.throws(() => { - assert.fail('first', 'second', undefined, 'operator'); - }, { - code: 'ERR_ASSERTION', - name: 'AssertionError', - message: '\'first\' operator \'second\'', - operator: 'operator', - actual: 'first', - expected: 'second' - }); -}); - -test('The stackFrameFunction should exclude the foo frame', () => { - assert.throws( - function foo() { assert.fail('first', 'second', 'message', '!==', foo); }, - (err) => !/^\s*at\sfoo\b/m.test(err.stack) - ); -});