assert: allow printf-style messages as assertion error

Also add functions as allowed message input.
This allows to have leavy message computation to become cheaper.

PR-URL: https://github.com/nodejs/node/pull/58849
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
Ruben Bridgewater 2025-10-17 22:15:17 +02:00 committed by GitHub
parent 8096aeab81
commit d3f79aa65d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 434 additions and 78 deletions

View File

@ -39,6 +39,27 @@ strict methods. For example, [`assert.deepEqual()`][] will behave like
In strict assertion mode, error messages for objects display a diff. In legacy
assertion mode, error messages for objects display the objects, often truncated.
### Message parameter semantics
For assertion methods that accept an optional `message` parameter, the message
may be provided in one of the following forms:
* **string**: Used as-is. If additional arguments are supplied after the
`message` string, they are treated as printf-like substitutions (see
[`util.format()`][]).
* **Error**: If an `Error` instance is provided as `message`, that error is
thrown directly instead of an `AssertionError`.
* **function**: A function of the form `(actual, expected) => string`. It is
called only when the assertion fails and should return a string to be used as
the error message. Non-string return values are ignored and the default
message is used instead.
If additional arguments are passed along with an `Error` or a function as
`message`, the call is rejected with `ERR_AMBIGUOUS_ARGUMENT`.
If the first item is neither a string, `Error`, nor function, `ERR_INVALID_ARG_TYPE`
is thrown.
To use strict assertion mode:
```mjs
@ -305,10 +326,14 @@ destructuring and call methods directly on the instance.
<!-- YAML
added: v0.5.9
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
-->
* `value` {any} The input that is checked for being truthy.
* `message` {string|Error}
* `message` {string|Error|Function}
An alias of [`assert.ok()`][].
@ -324,6 +349,9 @@ changes:
- version: v25.0.0
pr-url: https://github.com/nodejs/node/pull/57627
description: Invalid dates are now considered equal.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v24.0.0
pr-url: https://github.com/nodejs/node/pull/57622
description: Recursion now stops when either side encounters a circular
@ -375,7 +403,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
**Strict assertion mode**
@ -524,6 +552,9 @@ changes:
- version: v25.0.0
pr-url: https://github.com/nodejs/node/pull/57627
description: Invalid dates are now considered equal.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v24.0.0
pr-url: https://github.com/nodejs/node/pull/57622
description: Recursion now stops when either side encounters a circular
@ -567,7 +598,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
Tests for deep equality between the `actual` and `expected` parameters.
"Deep" equality means that the enumerable "own" properties of child objects
@ -829,6 +860,9 @@ added:
- v13.6.0
- v12.16.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/38111
description: This API is no longer experimental.
@ -836,7 +870,7 @@ changes:
* `string` {string}
* `regexp` {RegExp}
* `message` {string|Error}
* `message` {string|Error|Function}
Expects the `string` input not to match the regular expression.
@ -1069,6 +1103,9 @@ assert.doesNotThrow(
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version:
- v16.0.0
- v14.18.0
@ -1083,7 +1120,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
**Strict assertion mode**
@ -1254,6 +1291,9 @@ added:
- v13.6.0
- v12.16.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/38111
description: This API is no longer experimental.
@ -1261,7 +1301,7 @@ changes:
* `string` {string}
* `regexp` {RegExp}
* `message` {string|Error}
* `message` {string|Error|Function}
Expects the `string` input to match the regular expression.
@ -1303,6 +1343,9 @@ instance of {Error} then it will be thrown instead of the
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version:
- v16.0.0
- v14.18.0
@ -1338,7 +1381,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
**Strict assertion mode**
@ -1427,6 +1470,9 @@ instead of the `AssertionError`.
<!-- YAML
added: v1.2.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v9.0.0
pr-url: https://github.com/nodejs/node/pull/15398
description: The `-0` and `+0` are not considered equal anymore.
@ -1458,7 +1504,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
Tests for deep strict inequality. Opposite of [`assert.deepStrictEqual()`][].
@ -1487,6 +1533,9 @@ instead of the [`AssertionError`][].
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version:
- v16.0.0
- v14.18.0
@ -1501,7 +1550,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
**Strict assertion mode**
@ -1551,6 +1600,9 @@ parameter is an instance of {Error} then it will be thrown instead of the
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v10.0.0
pr-url: https://github.com/nodejs/node/pull/17003
description: Used comparison changed from Strict Equality to `Object.is()`.
@ -1558,7 +1610,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
Tests strict inequality between the `actual` and `expected` parameters as
determined by [`Object.is()`][].
@ -1604,6 +1656,9 @@ instead of the `AssertionError`.
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v10.0.0
pr-url: https://github.com/nodejs/node/pull/18319
description: The `assert.ok()` (no arguments) will now use a predefined
@ -1611,7 +1666,7 @@ changes:
-->
* `value` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
Tests if `value` is truthy. It is equivalent to
`assert.equal(!!value, true, message)`.
@ -1844,6 +1899,9 @@ argument gets considered.
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58849
description: Message may now be a `printf`-like format string or function.
- version: v10.0.0
pr-url: https://github.com/nodejs/node/pull/17003
description: Used comparison changed from Strict Equality to `Object.is()`.
@ -1851,7 +1909,14 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function} Postfix `printf`-like arguments in case
it's used as format string.
If message is a function, it is called in case of a comparison failure. The
function receives the `actual` and `expected` arguments and has to return a
string that is going to be used as error message.
`printf`-like format strings and functions are beneficial for performance
reasons in case arguments are passed through. In addition, it allows nice
formatting with ease.
Tests strict equality between the `actual` and `expected` parameters as
determined by [`Object.is()`][].
@ -1880,8 +1945,17 @@ const oranges = 2;
assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`);
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
assert.strictEqual(apples, oranges, 'apples %s !== oranges %s', apples, oranges);
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
assert.strictEqual(1, '1', new TypeError('Inputs are not identical'));
// TypeError: Inputs are not identical
assert.strictEqual(apples, oranges, (actual, expected) => {
// Do 'heavy' computations
return `I expected ${expected} but I got ${actual}`;
});
// AssertionError [ERR_ASSERTION]: I expected oranges but I got apples
```
```cjs
@ -1908,8 +1982,17 @@ const oranges = 2;
assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`);
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
assert.strictEqual(apples, oranges, 'apples %s !== oranges %s', apples, oranges);
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
assert.strictEqual(1, '1', new TypeError('Inputs are not identical'));
// TypeError: Inputs are not identical
assert.strictEqual(apples, oranges, (actual, expected) => {
// Do 'heavy' computations
return `I expected ${expected} but I got ${actual}`;
});
// AssertionError [ERR_ASSERTION]: I expected oranges but I got apples
```
If the values are not strictly equal, an [`AssertionError`][] is thrown with a
@ -2295,7 +2378,7 @@ changes:
* `actual` {any}
* `expected` {any}
* `message` {string|Error}
* `message` {string|Error|Function}
Tests for partial deep equality between the `actual` and `expected` parameters.
"Deep" equality means that the enumerable "own" properties of child objects
@ -2460,5 +2543,6 @@ assert.partialDeepStrictEqual(
[`assert.strictEqual()`]: #assertstrictequalactual-expected-message
[`assert.throws()`]: #assertthrowsfn-error-message
[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv
[`util.format()`]: util.md#utilformatformat-args
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots

View File

@ -59,7 +59,7 @@ const {
isRegExp,
} = require('internal/util/types');
const { isError, setOwnProperty } = require('internal/util');
const { innerOk } = require('internal/assert/utils');
const { innerOk, innerFail } = require('internal/assert/utils');
const {
validateFunction,
@ -143,12 +143,6 @@ function Assert(options) {
// they lose their `this` context and will use default behavior instead of the
// instance's custom options.
function innerFail(obj) {
if (obj.message instanceof Error) throw obj.message;
throw new AssertionError(obj);
}
/**
* Throws an AssertionError with the given message.
* @param {any | Error} [message]
@ -189,7 +183,7 @@ assert.AssertionError = AssertionError;
* @returns {void}
*/
function assert(...args) {
innerOk(assert, args.length, ...args);
innerOk(assert, ...args);
}
/**
@ -200,17 +194,17 @@ function assert(...args) {
* @returns {void}
*/
Assert.prototype.ok = function ok(...args) {
innerOk(ok, args.length, ...args);
innerOk(ok, ...args);
};
/**
* The equality assertion tests shallow, coercive equality with ==.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.equal = function equal(actual, expected, message) {
Assert.prototype.equal = function equal(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -232,10 +226,10 @@ Assert.prototype.equal = function equal(actual, expected, message) {
* equal with !=.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.notEqual = function notEqual(actual, expected, message) {
Assert.prototype.notEqual = function notEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -256,10 +250,10 @@ Assert.prototype.notEqual = function notEqual(actual, expected, message) {
* The deep equivalence assertion tests a deep equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.deepEqual = function deepEqual(actual, expected, message) {
Assert.prototype.deepEqual = function deepEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -280,10 +274,10 @@ Assert.prototype.deepEqual = function deepEqual(actual, expected, message) {
* The deep non-equivalence assertion tests for any deep inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.notDeepEqual = function notDeepEqual(actual, expected, message) {
Assert.prototype.notDeepEqual = function notDeepEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -305,10 +299,10 @@ Assert.prototype.notDeepEqual = function notDeepEqual(actual, expected, message)
* relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
Assert.prototype.deepStrictEqual = function deepStrictEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -330,11 +324,11 @@ Assert.prototype.deepStrictEqual = function deepStrictEqual(actual, expected, me
* inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
function notDeepStrictEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -355,10 +349,10 @@ function notDeepStrictEqual(actual, expected, message) {
* The strict equivalence assertion tests a strict equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.strictEqual = function strictEqual(actual, expected, message) {
Assert.prototype.strictEqual = function strictEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -378,10 +372,10 @@ Assert.prototype.strictEqual = function strictEqual(actual, expected, message) {
* The strict non-equivalence assertion tests for any strict inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.notStrictEqual = function notStrictEqual(actual, expected, message) {
Assert.prototype.notStrictEqual = function notStrictEqual(actual, expected, ...message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
@ -401,13 +395,13 @@ Assert.prototype.notStrictEqual = function notStrictEqual(actual, expected, mess
* The strict equivalence assertion test between two objects
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.partialDeepStrictEqual = function partialDeepStrictEqual(
actual,
expected,
message,
...message
) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
@ -464,7 +458,7 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) {
innerFail({
actual,
expected,
message,
message: [message],
operator: fn.name,
stackStartFn: fn,
diff: this?.[kOptions]?.diff,
@ -666,7 +660,7 @@ function expectsError(stackStartFn, actual, error, message) {
actual: undefined,
expected: error,
operator: stackStartFn.name,
message: `Missing expected ${fnType}${details}`,
message: [`Missing expected ${fnType}${details}`],
stackStartFn,
diff: this?.[kOptions]?.diff,
});
@ -715,8 +709,8 @@ function expectsNoError(stackStartFn, actual, error, message) {
actual,
expected: error,
operator: stackStartFn.name,
message: `Got unwanted ${fnType}${details}\n` +
`Actual message: "${actual?.message}"`,
message: [`Got unwanted ${fnType}${details}\n` +
`Actual message: "${actual?.message}"`],
stackStartFn,
diff: this?.[kOptions]?.diff,
});
@ -834,30 +828,26 @@ function internalMatch(string, regexp, message, fn) {
const match = fn === Assert.prototype.match;
if (typeof string !== 'string' ||
RegExpPrototypeExec(regexp, string) !== null !== match) {
if (message instanceof Error) {
throw message;
}
const generatedMessage = !message;
const generatedMessage = message.length === 0;
// 'The input was expected to not match the regular expression ' +
message ||= (typeof string !== 'string' ?
message[0] ||= (typeof string !== 'string' ?
'The "string" argument must be of type string. Received type ' +
`${typeof string} (${inspect(string)})` :
(match ?
'The input did not match the regular expression ' :
'The input was expected to not match the regular expression ') +
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
const err = new AssertionError({
innerFail({
actual: string,
expected: regexp,
message,
operator: fn.name,
stackStartFn: fn,
diff: this?.[kOptions]?.diff,
generatedMessage: generatedMessage,
});
err.generatedMessage = generatedMessage;
throw err;
}
}
@ -865,10 +855,10 @@ function internalMatch(string, regexp, message, fn) {
* Expects the `string` input to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.match = function match(string, regexp, message) {
Assert.prototype.match = function match(string, regexp, ...message) {
internalMatch(string, regexp, message, match);
};
@ -876,10 +866,10 @@ Assert.prototype.match = function match(string, regexp, message) {
* Expects the `string` input not to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @param {string | Error | MessageFactory} [message]
* @returns {void}
*/
Assert.prototype.doesNotMatch = function doesNotMatch(string, regexp, message) {
Assert.prototype.doesNotMatch = function doesNotMatch(string, regexp, ...message) {
internalMatch(string, regexp, message, doesNotMatch);
};
@ -889,7 +879,7 @@ Assert.prototype.doesNotMatch = function doesNotMatch(string, regexp, message) {
* @returns {void}
*/
function strict(...args) {
innerOk(strict, args.length, ...args);
innerOk(strict, ...args);
}
ArrayPrototypeForEach([

View File

@ -3,15 +3,21 @@
const {
Error,
ErrorCaptureStackTrace,
ErrorPrototypeToString,
StringPrototypeCharCodeAt,
StringPrototypeReplace,
} = primordials;
const {
codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE,
},
isErrorStackTraceLimitWritable,
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { isError } = require('internal/util');
const { format } = require('internal/util/inspect');
const {
getErrorSourceExpression,
@ -33,6 +39,43 @@ const meta = [
const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
/**
* A function that derives the failure message from the actual and expected values.
* It is invoked only when the assertion fails.
*
* Other return values than a string are ignored.
* @callback MessageFactory
* @param {any} actual
* @param {any} expected
* @returns {string}
*/
/**
* Raw message input is always passed internally as a tuple array.
* Accepted shapes:
* - []
* - [string]
* - [string, ...any[]] (printf-like substitutions)
* - [Error]
* - [MessageFactory]
*
* Additional elements after [Error] or [MessageFactory] are rejected with ERR_AMBIGUOUS_ARGUMENT.
* A first element that is neither string, Error nor function is rejected with ERR_INVALID_ARG_TYPE.
* @typedef {[] | [string] | [string, ...any[]] | [Error] | [MessageFactory]} MessageTuple
*/
/**
* Options consumed by innerFail to construct and throw the AssertionError.
* @typedef {object} InnerFailOptions
* @property {any} actual Actual value
* @property {any} expected Expected value
* @property {MessageTuple} message Message
* @property {string} operator Operator
* @property {Function} stackStartFn Stack start function
* @property {'simple' | 'full'} [diff] Diff mode
* @property {boolean} [generatedMessage] Generated message
*/
function getErrMessage(fn) {
const tmpLimit = Error.stackTraceLimit;
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
@ -52,32 +95,89 @@ function getErrMessage(fn) {
}
}
function innerOk(fn, argLen, value, message) {
if (!value) {
let generatedMessage = false;
/**
* @param {InnerFailOptions} obj
*/
function innerFail(obj) {
if (obj.message.length === 0) {
obj.message = undefined;
} else if (typeof obj.message[0] === 'string') {
if (obj.message.length > 1) {
obj.message = format(...obj.message);
} else {
obj.message = obj.message[0];
}
} else if (isError(obj.message[0])) {
if (obj.message.length > 1) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'message',
`The error message was passed as error object "${ErrorPrototypeToString(obj.message[0])}" has trailing arguments that would be ignored.`,
);
}
throw obj.message[0];
} else if (typeof obj.message[0] === 'function') {
if (obj.message.length > 1) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'message',
`The error message with function "${obj.message[0].name || 'anonymous'}" has trailing arguments that would be ignored.`,
);
}
try {
obj.message = obj.message[0](obj.actual, obj.expected);
if (typeof obj.message !== 'string') {
obj.message = undefined;
}
} catch {
// Ignore and use default message instead
obj.message = undefined;
}
} else {
throw new ERR_INVALID_ARG_TYPE(
'message',
['string', 'function'],
obj.message[0],
);
}
if (argLen === 0) {
const error = new AssertionError(obj);
if (obj.generatedMessage !== undefined) {
error.generatedMessage = obj.generatedMessage;
}
throw error;
}
/**
* Internal ok handler delegating to innerFail for message handling.
* @param {Function} fn
* @param {...any} args
*/
function innerOk(fn, ...args) {
if (!args[0]) {
let generatedMessage = false;
let messageArgs;
if (args.length === 0) {
generatedMessage = true;
message = 'No value argument passed to `assert.ok()`';
} else if (message == null) {
messageArgs = ['No value argument passed to `assert.ok()`'];
} else if (args.length === 1 || args[1] == null) {
generatedMessage = true;
message = getErrMessage(fn);
} else if (isError(message)) {
throw message;
messageArgs = [getErrMessage(fn)];
} else {
messageArgs = args.slice(1);
}
const err = new AssertionError({
actual: value,
innerFail({
actual: args[0],
expected: true,
message,
message: messageArgs,
operator: '==',
stackStartFn: fn,
generatedMessage,
});
err.generatedMessage = generatedMessage;
throw err;
}
}
module.exports = {
innerOk,
innerFail,
};

View File

@ -333,7 +333,7 @@ class TestContext {
if (plan !== null) {
plan.count();
}
innerOk(ok, args.length, ...args);
innerOk(ok, ...args);
}
assert.ok = ok;

View File

@ -1,6 +1,6 @@
Exiting with code=1
node:assert:*
throw new AssertionError(obj);
node:internal*assert*utils:*
throw error;
^
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

View File

@ -904,10 +904,9 @@ test('Additional asserts', () => {
assert.throws(
() => assert(false, Symbol('foo')),
{
code: 'ERR_ASSERTION',
constructor: assert.AssertionError,
generatedMessage: false,
message: 'Symbol(foo)'
code: 'ERR_INVALID_ARG_TYPE',
constructor: TypeError,
message: /"message" argument.+Symbol\(foo\)/
}
);
@ -1595,5 +1594,188 @@ test('assert/strict exists', () => {
assert.strictEqual(require('assert/strict'), assert.strict);
});
test('Printf-like format strings as error message', () => {
assert.throws(
() => assert.equal(1, 2, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.strictEqual(1, 2, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.notEqual(1, 1, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.notStrictEqual(1, 1, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.deepEqual(1, 2, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.notDeepEqual(1, 1, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.deepStrictEqual(1, 2, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.notDeepStrictEqual(1, 1, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.partialDeepStrictEqual(1, 2, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.match('foo', /bar/, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
assert.throws(
() => assert.doesNotMatch('foo', /foo/, 'The answer to all questions is %i', 42),
/The answer to all questions is 42/
);
});
test('Functions as error message', () => {
function errorMessage(actual, expected) {
return `Nice message including ${actual} and ${expected}`;
}
assert.throws(
() => assert.equal(1, 2, errorMessage),
{ message: 'Nice message including 1 and 2' }
);
assert.throws(
() => assert.strictEqual(1, 2, errorMessage),
// TODO(BridgeAR): Align input arguments with generated message for all
// methods.
// TODO(BridgeAR): Check how to handle this for custom messages. Do
// we need this? Should it be skipped for methods like these?
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
);
assert.throws(
() => assert.notEqual(1, 1, errorMessage),
{ message: 'Nice message including 1 and 1' }
);
assert.throws(
() => assert.notStrictEqual(1, 1, errorMessage),
{ message: 'Nice message including 1 and 1' }
);
assert.throws(
() => assert.deepEqual(1, 2, errorMessage),
{ message: 'Nice message including 1 and 2' }
);
assert.throws(
() => assert.notDeepEqual(1, 1, errorMessage),
{ message: 'Nice message including 1 and 1' }
);
assert.throws(
() => assert.deepStrictEqual(1, 2, errorMessage),
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
);
assert.throws(
() => assert.notDeepStrictEqual(1, 1, errorMessage),
{ message: 'Nice message including 1 and 1' }
);
assert.throws(
() => assert.partialDeepStrictEqual(1, 2, errorMessage),
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
);
assert.throws(
() => assert.match('foo', /bar/, errorMessage),
{ message: 'Nice message including foo and /bar/' }
);
assert.throws(
() => assert.doesNotMatch('foo', /foo/, errorMessage),
{ message: 'Nice message including foo and /foo/' }
);
});
test('Ambiguous error messages fail', () => {
function errorMessage(actual, expected) {
return `Nice message including ${actual} and ${expected}`;
}
assert.throws(
() => assert.doesNotMatch('foo', /foo/, errorMessage, 'foobar'),
{
code: 'ERR_AMBIGUOUS_ARGUMENT',
message: /errorMessage/
}
);
assert.throws(
() => assert.doesNotMatch('foo', /foo/, new Error('baz'), 'foobar'),
{
code: 'ERR_AMBIGUOUS_ARGUMENT',
message: /baz/
}
);
});
test('Faulty message functions', () => {
assert.throws(
() => assert.doesNotMatch('foo', /foo/, (a, b) => 123),
{
code: 'ERR_ASSERTION',
message: "'foo' doesNotMatch /foo/"
}
);
assert.throws(
() => assert.match('foo', /123/, (a, b) => { new Error('baz'); }),
{
code: 'ERR_ASSERTION',
message: "'foo' match /123/"
}
);
});
test('Functions as error message', () => {
function errorMessage(actual, expected) {
return `Nice message including ${actual} and ${expected}`;
}
assert.throws(
() => assert.equal('foo', 'bar', errorMessage),
{
code: 'ERR_ASSERTION',
message: /Nice message including foo and bar/
}
);
assert.throws(
() => assert.doesNotMatch('foo', /foo/, errorMessage),
{
code: 'ERR_ASSERTION',
message: /Nice message including foo and \/foo\//
}
);
});
/* eslint-enable no-restricted-syntax */
/* eslint-enable no-restricted-properties */