From 560925fe22bfc23860b04704ff4cae21e4dd19ff Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 2 May 2018 16:59:49 +0200 Subject: [PATCH] assert: make sure throws is able to handle primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: https://github.com/nodejs/node/pull/20482 Reviewed-By: James M Snell Reviewed-By: Michaƫl Zasso --- lib/assert.js | 30 +++++++++++++++---- test/message/assert_throws_stack.out | 4 +-- test/parallel/test-assert.js | 45 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 27e71c2751ed74..8bcdc8cdacb3ee 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -382,16 +382,16 @@ function compareExceptionKey(actual, expected, key, message, keys) { const a = new Comparison(actual, keys); const b = new Comparison(expected, keys, actual); - const tmpLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 0; const err = new AssertionError({ actual: a, expected: b, operator: 'deepStrictEqual', stackStartFn: assert.throws }); - Error.stackTraceLimit = tmpLimit; - message = err.message; + err.actual = actual; + err.expected = expected; + err.operator = 'throws'; + throw err; } innerFail({ actual, @@ -405,7 +405,7 @@ function compareExceptionKey(actual, expected, key, message, keys) { function expectedException(actual, expected, msg) { if (typeof expected !== 'function') { - if (expected instanceof RegExp) + if (isRegExp(expected)) return expected.test(actual); // assert.doesNotThrow does not accept objects. if (arguments.length === 2) { @@ -413,6 +413,26 @@ function expectedException(actual, expected, msg) { 'expected', ['Function', 'RegExp'], expected ); } + + // TODO: Disallow primitives as error argument. + // This is here to prevent a breaking change. + if (typeof expected !== 'object') { + return true; + } + + // Handle primitives properly. + if (typeof actual !== 'object' || actual === null) { + const err = new AssertionError({ + actual, + expected, + message: msg, + operator: 'deepStrictEqual', + stackStartFn: assert.throws + }); + err.operator = 'throws'; + throw err; + } + const keys = Object.keys(expected); // Special handle errors to make sure the name and the message are compared // as well. diff --git a/test/message/assert_throws_stack.out b/test/message/assert_throws_stack.out index 62a25a63673dcf..3d5f4de4cf26a6 100644 --- a/test/message/assert_throws_stack.out +++ b/test/message/assert_throws_stack.out @@ -1,6 +1,6 @@ assert.js:* - throw new AssertionError(obj); - ^ + throw err; + ^ AssertionError [ERR_ASSERTION]: Input A expected to strictly deep-equal input B: + expected - actual diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index cab6fb33e1887a..dba40c0a0e3c42 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -740,7 +740,9 @@ common.expectsError( const frames = err.stack.split('\n'); const [, filename, line, column] = frames[1].match(/\((.+):(\d+):(\d+)\)/); // Reset the cache to check again + const size = errorCache.size; errorCache.delete(`${filename}${line - 1}${column - 1}`); + assert.strictEqual(errorCache.size, size - 1); const data = `${'\n'.repeat(line - 1)}${' '.repeat(column - 1)}` + 'ok(failed(badly));'; try { @@ -849,6 +851,7 @@ common.expectsError( { name: 'AssertionError [ERR_ASSERTION]', code: 'ERR_ASSERTION', + generatedMessage: true, message: `${start}\n${actExp}\n\n` + " Comparison {\n name: 'Error',\n- message: 'foo'" + "\n+ message: ''\n }" @@ -940,3 +943,45 @@ assert.throws( ' }' } ); + +{ + let actual = null; + const expected = { message: 'foo' }; + assert.throws( + () => assert.throws( + () => { throw actual; }, + expected + ), + { + operator: 'throws', + actual, + expected, + generatedMessage: true, + message: `${start}\n${actExp}\n\n` + + '- null\n' + + '+ {\n' + + "+ message: 'foo'\n" + + '+ }' + } + ); + + actual = 'foobar'; + const message = 'message'; + assert.throws( + () => assert.throws( + () => { throw actual; }, + { message: 'foobar' }, + message + ), + { + actual, + message, + operator: 'throws', + generatedMessage: false + } + ); +} + +// TODO: This case is only there to make sure there is no breaking change. +// eslint-disable-next-line no-restricted-syntax, no-throw-literal +assert.throws(() => { throw 4; }, 4);