Skip to content

Commit 560925f

Browse files
committed
assert: make sure throws is able to handle primitives
This fixes some possible issues with `assert.throws` and `assert.rejects` in combination with an validation object. It will now properly handle primitive values being thrown as error. It also makes sure the `generatedMessage` property is properly set if `assert.throws` or `assert.rejects` is used in combination with an validation object and improves the error performance in such cases by only creating the error once. In addition it will fix detecting regular expressions from a different context such as n-api that are passed through as validator for `assert.throws` or `assert.rejects`. Until now those were not tested. PR-URL: nodejs#20482 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 5e6ca89 commit 560925f

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

lib/assert.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,16 +382,16 @@ function compareExceptionKey(actual, expected, key, message, keys) {
382382
const a = new Comparison(actual, keys);
383383
const b = new Comparison(expected, keys, actual);
384384

385-
const tmpLimit = Error.stackTraceLimit;
386-
Error.stackTraceLimit = 0;
387385
const err = new AssertionError({
388386
actual: a,
389387
expected: b,
390388
operator: 'deepStrictEqual',
391389
stackStartFn: assert.throws
392390
});
393-
Error.stackTraceLimit = tmpLimit;
394-
message = err.message;
391+
err.actual = actual;
392+
err.expected = expected;
393+
err.operator = 'throws';
394+
throw err;
395395
}
396396
innerFail({
397397
actual,
@@ -405,14 +405,34 @@ function compareExceptionKey(actual, expected, key, message, keys) {
405405

406406
function expectedException(actual, expected, msg) {
407407
if (typeof expected !== 'function') {
408-
if (expected instanceof RegExp)
408+
if (isRegExp(expected))
409409
return expected.test(actual);
410410
// assert.doesNotThrow does not accept objects.
411411
if (arguments.length === 2) {
412412
throw new ERR_INVALID_ARG_TYPE(
413413
'expected', ['Function', 'RegExp'], expected
414414
);
415415
}
416+
417+
// TODO: Disallow primitives as error argument.
418+
// This is here to prevent a breaking change.
419+
if (typeof expected !== 'object') {
420+
return true;
421+
}
422+
423+
// Handle primitives properly.
424+
if (typeof actual !== 'object' || actual === null) {
425+
const err = new AssertionError({
426+
actual,
427+
expected,
428+
message: msg,
429+
operator: 'deepStrictEqual',
430+
stackStartFn: assert.throws
431+
});
432+
err.operator = 'throws';
433+
throw err;
434+
}
435+
416436
const keys = Object.keys(expected);
417437
// Special handle errors to make sure the name and the message are compared
418438
// as well.

test/message/assert_throws_stack.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
assert.js:*
2-
throw new AssertionError(obj);
3-
^
2+
throw err;
3+
^
44

55
AssertionError [ERR_ASSERTION]: Input A expected to strictly deep-equal input B:
66
+ expected - actual

test/parallel/test-assert.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,9 @@ common.expectsError(
740740
const frames = err.stack.split('\n');
741741
const [, filename, line, column] = frames[1].match(/\((.+):(\d+):(\d+)\)/);
742742
// Reset the cache to check again
743+
const size = errorCache.size;
743744
errorCache.delete(`${filename}${line - 1}${column - 1}`);
745+
assert.strictEqual(errorCache.size, size - 1);
744746
const data = `${'\n'.repeat(line - 1)}${' '.repeat(column - 1)}` +
745747
'ok(failed(badly));';
746748
try {
@@ -849,6 +851,7 @@ common.expectsError(
849851
{
850852
name: 'AssertionError [ERR_ASSERTION]',
851853
code: 'ERR_ASSERTION',
854+
generatedMessage: true,
852855
message: `${start}\n${actExp}\n\n` +
853856
" Comparison {\n name: 'Error',\n- message: 'foo'" +
854857
"\n+ message: ''\n }"
@@ -940,3 +943,45 @@ assert.throws(
940943
' }'
941944
}
942945
);
946+
947+
{
948+
let actual = null;
949+
const expected = { message: 'foo' };
950+
assert.throws(
951+
() => assert.throws(
952+
() => { throw actual; },
953+
expected
954+
),
955+
{
956+
operator: 'throws',
957+
actual,
958+
expected,
959+
generatedMessage: true,
960+
message: `${start}\n${actExp}\n\n` +
961+
'- null\n' +
962+
'+ {\n' +
963+
"+ message: 'foo'\n" +
964+
'+ }'
965+
}
966+
);
967+
968+
actual = 'foobar';
969+
const message = 'message';
970+
assert.throws(
971+
() => assert.throws(
972+
() => { throw actual; },
973+
{ message: 'foobar' },
974+
message
975+
),
976+
{
977+
actual,
978+
message,
979+
operator: 'throws',
980+
generatedMessage: false
981+
}
982+
);
983+
}
984+
985+
// TODO: This case is only there to make sure there is no breaking change.
986+
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
987+
assert.throws(() => { throw 4; }, 4);

0 commit comments

Comments
 (0)