mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
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:
parent
8096aeab81
commit
d3f79aa65d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
if (argLen === 0) {
|
||||
generatedMessage = true;
|
||||
message = 'No value argument passed to `assert.ok()`';
|
||||
} else if (message == null) {
|
||||
generatedMessage = true;
|
||||
message = getErrMessage(fn);
|
||||
} else if (isError(message)) {
|
||||
throw message;
|
||||
/**
|
||||
* @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],
|
||||
);
|
||||
}
|
||||
|
||||
const err = new AssertionError({
|
||||
actual: value,
|
||||
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;
|
||||
messageArgs = ['No value argument passed to `assert.ok()`'];
|
||||
} else if (args.length === 1 || args[1] == null) {
|
||||
generatedMessage = true;
|
||||
messageArgs = [getErrMessage(fn)];
|
||||
} else {
|
||||
messageArgs = args.slice(1);
|
||||
}
|
||||
|
||||
innerFail({
|
||||
actual: args[0],
|
||||
expected: true,
|
||||
message,
|
||||
message: messageArgs,
|
||||
operator: '==',
|
||||
stackStartFn: fn,
|
||||
generatedMessage,
|
||||
});
|
||||
err.generatedMessage = generatedMessage;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
innerOk,
|
||||
innerFail,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ class TestContext {
|
|||
if (plan !== null) {
|
||||
plan.count();
|
||||
}
|
||||
innerOk(ok, args.length, ...args);
|
||||
innerOk(ok, ...args);
|
||||
}
|
||||
|
||||
assert.ok = ok;
|
||||
|
|
|
|||
4
test/fixtures/errors/error_exit.snapshot
vendored
4
test/fixtures/errors/error_exit.snapshot
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user