Skip to content

Commit f6aeff1

Browse files
committed
fixup!
1 parent 6150aa4 commit f6aeff1

File tree

3 files changed

+185
-19
lines changed

3 files changed

+185
-19
lines changed

doc/api/assert.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,8 +1759,10 @@ changes:
17591759
* `actual` {any}
17601760
* `expected` {any}
17611761
* `message` {string|Error|Function} Postfix `printf`-like arguments in case
1762-
it's used as format string. If a function is used, the function is called. If
1763-
that function call fails, the default message is generated instead.
1762+
it's used as format string.
1763+
If message is a function, it is called in case of a comparison failure. The
1764+
function receives the `actual` and `expected` arguments and has to return a
1765+
string that is going to be used as error message.
17641766
`printf`-like format strings and functions are beneficial for performance
17651767
reasons in case arguments are passed through. In addition, it allows nice
17661768
formatting with ease.
@@ -1800,7 +1802,7 @@ assert.strictEqual(1, '1', new TypeError('Inputs are not identical'));
18001802

18011803
assert.strictEqual(apples, oranges, () => {
18021804
// Do 'heavy' computations
1803-
return 'My error string'
1805+
return 'My error string';
18041806
});
18051807
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
18061808
```

lib/assert.js

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
ArrayPrototypePush,
2727
ArrayPrototypeSlice,
2828
Error,
29+
ErrorPrototypeToString,
2930
NumberIsNaN,
3031
ObjectAssign,
3132
ObjectIs,
@@ -88,17 +89,37 @@ const NO_EXCEPTION_SENTINEL = {};
8889

8990
function innerFail(obj) {
9091
if (obj.message.length === 0) {
91-
obj.message = undefined
92+
obj.message = undefined;
9293
} else if (typeof obj.message[0] === 'string') {
9394
if (obj.message.length > 1) {
9495
obj.message = format(...obj.message);
9596
} else {
9697
obj.message = obj.message[0];
9798
}
9899
} else if (isError(obj.message[0])) {
100+
if (obj.message.length > 1) {
101+
throw new ERR_AMBIGUOUS_ARGUMENT(
102+
'message',
103+
`The error message was passed as error object "${ErrorPrototypeToString(obj.message[0])}" has trailing arguments that would be ignored.`,
104+
);
105+
}
99106
throw obj.message[0];
100107
} else if (typeof obj.message[0] === 'function') {
101-
obj.message = obj.message[0]();
108+
if (obj.message.length > 1) {
109+
throw new ERR_AMBIGUOUS_ARGUMENT(
110+
'message',
111+
`The error message with function "${obj.message[0].name || 'anonymous'}" has trailing arguments that would be ignored.`,
112+
);
113+
}
114+
try {
115+
obj.message = obj.message[0](obj.actual, obj.expected);
116+
if (typeof obj.message !== 'string') {
117+
obj.message = undefined;
118+
}
119+
} catch {
120+
// Ignore and use default message instead
121+
obj.message = undefined;
122+
}
102123
} else {
103124
throw new ERR_INVALID_ARG_TYPE(
104125
'message',
@@ -107,7 +128,11 @@ function innerFail(obj) {
107128
);
108129
}
109130

110-
throw new AssertionError(obj);
131+
const error = new AssertionError(obj);
132+
if (obj.generatedMessage !== undefined) {
133+
error.generatedMessage = obj.generatedMessage;
134+
}
135+
throw error;
111136
}
112137

113138
/**
@@ -349,7 +374,7 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, ...message) {
349374
assert.partialDeepStrictEqual = function partialDeepStrictEqual(
350375
actual,
351376
expected,
352-
...message,
377+
...message
353378
) {
354379
if (arguments.length < 2) {
355380
throw new ERR_MISSING_ARGS('actual', 'expected');
@@ -404,7 +429,7 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) {
404429
innerFail({
405430
actual,
406431
expected,
407-
message,
432+
message: [message],
408433
operator: fn.name,
409434
stackStartFn: fn,
410435
});
@@ -603,7 +628,7 @@ function expectsError(stackStartFn, actual, error, message) {
603628
actual: undefined,
604629
expected: error,
605630
operator: stackStartFn.name,
606-
message: `Missing expected ${fnType}${details}`,
631+
message: [`Missing expected ${fnType}${details}`],
607632
stackStartFn,
608633
});
609634
}
@@ -651,8 +676,8 @@ function expectsNoError(stackStartFn, actual, error, message) {
651676
actual,
652677
expected: error,
653678
operator: stackStartFn.name,
654-
message: `Got unwanted ${fnType}${details}\n` +
655-
`Actual message: "${actual?.message}"`,
679+
message: [`Got unwanted ${fnType}${details}\n` +
680+
`Actual message: "${actual?.message}"`],
656681
stackStartFn,
657682
});
658683
}
@@ -768,29 +793,25 @@ function internalMatch(string, regexp, message, fn) {
768793
const match = fn === assert.match;
769794
if (typeof string !== 'string' ||
770795
RegExpPrototypeExec(regexp, string) !== null !== match) {
771-
if (message instanceof Error) {
772-
throw message;
773-
}
774796

775-
const generatedMessage = !message;
797+
const generatedMessage = message.length === 0;
776798

777799
// 'The input was expected to not match the regular expression ' +
778-
message ||= (typeof string !== 'string' ?
800+
message[0] ||= (typeof string !== 'string' ?
779801
'The "string" argument must be of type string. Received type ' +
780802
`${typeof string} (${inspect(string)})` :
781803
(match ?
782804
'The input did not match the regular expression ' :
783805
'The input was expected to not match the regular expression ') +
784806
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
785-
const err = new AssertionError({
807+
innerFail({
786808
actual: string,
787809
expected: regexp,
788810
message,
789811
operator: fn.name,
790812
stackStartFn: fn,
813+
generatedMessage: generatedMessage,
791814
});
792-
err.generatedMessage = generatedMessage;
793-
throw err;
794815
}
795816
}
796817

test/parallel/test-assert.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,5 +1595,148 @@ test('assert/strict exists', () => {
15951595
assert.strictEqual(require('assert/strict'), assert.strict);
15961596
});
15971597

1598+
test('Printf-like format strings as error message', () => {
1599+
assert.throws(
1600+
() => assert.equal(1, 2, 'The answer to all questions is %i', 42),
1601+
/The answer to all questions is 42/
1602+
);
1603+
1604+
assert.throws(
1605+
() => assert.strictEqual(1, 2, 'The answer to all questions is %i', 42),
1606+
/The answer to all questions is 42/
1607+
);
1608+
1609+
assert.throws(
1610+
() => assert.notEqual(1, 1, 'The answer to all questions is %i', 42),
1611+
/The answer to all questions is 42/
1612+
);
1613+
1614+
assert.throws(
1615+
() => assert.notStrictEqual(1, 1, 'The answer to all questions is %i', 42),
1616+
/The answer to all questions is 42/
1617+
);
1618+
1619+
assert.throws(
1620+
() => assert.deepEqual(1, 2, 'The answer to all questions is %i', 42),
1621+
/The answer to all questions is 42/
1622+
);
1623+
1624+
assert.throws(
1625+
() => assert.notDeepEqual(1, 1, 'The answer to all questions is %i', 42),
1626+
/The answer to all questions is 42/
1627+
);
1628+
1629+
assert.throws(
1630+
() => assert.deepStrictEqual(1, 2, 'The answer to all questions is %i', 42),
1631+
/The answer to all questions is 42/
1632+
);
1633+
1634+
assert.throws(
1635+
() => assert.notDeepStrictEqual(1, 1, 'The answer to all questions is %i', 42),
1636+
/The answer to all questions is 42/
1637+
);
1638+
1639+
assert.throws(
1640+
() => assert.partialDeepStrictEqual(1, 2, 'The answer to all questions is %i', 42),
1641+
/The answer to all questions is 42/
1642+
);
1643+
1644+
assert.throws(
1645+
() => assert.match('foo', /bar/, 'The answer to all questions is %i', 42),
1646+
/The answer to all questions is 42/
1647+
);
1648+
1649+
assert.throws(
1650+
() => assert.doesNotMatch('foo', /foo/, 'The answer to all questions is %i', 42),
1651+
/The answer to all questions is 42/
1652+
);
1653+
});
1654+
1655+
test('Functions as error message', () => {
1656+
function errorMessage(actual, expected) {
1657+
return `Nice message including ${actual} and ${expected}`;
1658+
}
1659+
assert.throws(
1660+
() => assert.equal(1, 2, errorMessage),
1661+
{ message: 'Nice message including 1 and 2' }
1662+
);
1663+
1664+
assert.throws(
1665+
() => assert.strictEqual(1, 2, errorMessage),
1666+
// TODO(BridgeAR): Align input arguments with generated message for all
1667+
// methods.
1668+
// TODO(BridgeAR): Check how to handle this for custom messages. Do
1669+
// we need this? Should it be skipped for methods like these?
1670+
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
1671+
);
1672+
1673+
assert.throws(
1674+
() => assert.notEqual(1, 1, errorMessage),
1675+
{ message: 'Nice message including 1 and 1' }
1676+
);
1677+
1678+
assert.throws(
1679+
() => assert.notStrictEqual(1, 1, errorMessage),
1680+
{ message: 'Nice message including 1 and 1' }
1681+
);
1682+
1683+
assert.throws(
1684+
() => assert.deepEqual(1, 2, errorMessage),
1685+
{ message: 'Nice message including 1 and 2' }
1686+
);
1687+
1688+
assert.throws(
1689+
() => assert.notDeepEqual(1, 1, errorMessage),
1690+
{ message: 'Nice message including 1 and 1' }
1691+
);
1692+
1693+
assert.throws(
1694+
() => assert.deepStrictEqual(1, 2, errorMessage),
1695+
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
1696+
);
1697+
1698+
assert.throws(
1699+
() => assert.notDeepStrictEqual(1, 1, errorMessage),
1700+
{ message: 'Nice message including 1 and 1' }
1701+
);
1702+
1703+
assert.throws(
1704+
() => assert.partialDeepStrictEqual(1, 2, errorMessage),
1705+
{ message: 'Nice message including 1 and 2\n\n1 !== 2\n' }
1706+
);
1707+
1708+
assert.throws(
1709+
() => assert.match('foo', /bar/, errorMessage),
1710+
{ message: 'Nice message including foo and /bar/' }
1711+
);
1712+
1713+
assert.throws(
1714+
() => assert.doesNotMatch('foo', /foo/, errorMessage),
1715+
{ message: 'Nice message including foo and /foo/' }
1716+
);
1717+
});
1718+
1719+
test('Ambiguous error messages fail', () => {
1720+
function errorMessage(actual, expected) {
1721+
return `Nice message including ${actual} and ${expected}`;
1722+
}
1723+
1724+
assert.throws(
1725+
() => assert.doesNotMatch('foo', /foo/, errorMessage, 'foobar'),
1726+
{
1727+
code: 'ERR_AMBIGUOUS_ARGUMENT',
1728+
message: /errorMessage/
1729+
}
1730+
);
1731+
1732+
assert.throws(
1733+
() => assert.doesNotMatch('foo', /foo/, new Error('baz'), 'foobar'),
1734+
{
1735+
code: 'ERR_AMBIGUOUS_ARGUMENT',
1736+
message: /baz/
1737+
}
1738+
);
1739+
});
1740+
15981741
/* eslint-enable no-restricted-syntax */
15991742
/* eslint-enable no-restricted-properties */

0 commit comments

Comments
 (0)