From d505cdf375bb27c0eea4b60d9da290bb11339c6a Mon Sep 17 00:00:00 2001 From: Raynos Date: Thu, 31 Dec 2020 10:14:14 +0100 Subject: [PATCH] [Fix] preserve stack traces for returned Promises (async/await) --- lib/test.js | 7 +++++- test/async-await.js | 44 +++++++++++++++++++++++++++++++++-- test/async-await/async-bug.js | 32 +++++++++++++++++++++++++ test/promise_fail.js | 28 +++++++++++++++++----- 4 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 test/async-await/async-bug.js diff --git a/lib/test.js b/lib/test.js index 3d70bc5d..e1b56686 100644 --- a/lib/test.js +++ b/lib/test.js @@ -16,6 +16,7 @@ var isEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerabl var toLowerCase = bind.call(Function.call, String.prototype.toLowerCase); var isProto = bind.call(Function.call, Object.prototype.isPrototypeOf); var $test = bind.call(Function.call, RegExp.prototype.test); +var objectToString = bind.call(Function.call, Object.prototype.toString); module.exports = Test; @@ -111,7 +112,11 @@ Test.prototype.run = function () { self.end(); } })['catch'](function onError(err) { - self.fail(err); + if (err instanceof Error || objectToString(err) === '[object Error]') { + self.ifError(err); + } else { + self.fail(err); + } self.end(); }); return; diff --git a/test/async-await.js b/test/async-await.js index fb8da577..8fcbb642 100644 --- a/test/async-await.js +++ b/test/async-await.js @@ -217,9 +217,10 @@ tap.test('async-error', function (t) { 'ok 1 before throw', 'not ok 2 Error: oopsie', ' ---', - ' operator: fail', + ' operator: error', ' stack: |-', - ' Error: Error: oopsie', + ' Error: oopsie', + ' at Test.myTest ($TEST/async-await/async-error.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', '', @@ -245,3 +246,42 @@ tap.test('async-error', function (t) { t.end(); }); }); + +tap.test('async-bug', function (t) { + runProgram('async-await', 'async-bug.js', function (r) { + var stdout = r.stdout.toString('utf8'); + var lines = stdout.split('\n'); + lines = lines.filter(function (line) { + return !/^(\s+)at(\s+)$/.test(line); + }); + stdout = lines.join('\n'); + + t.same(stripFullStack(stdout), [ + 'TAP version 13', + '# async-error', + 'ok 1 before throw', + 'ok 2 should be strictly equal', + 'not ok 3 TypeError: Cannot read property \'length\' of null', + ' ---', + ' operator: error', + ' stack: |-', + ' TypeError: Cannot read property \'length\' of null', + ' at myCode ($TEST/async-await/async-bug.js:$LINE:$COL)', + ' at Test.myTest ($TEST/async-await/async-bug.js:$LINE:$COL)', + ' ...', + '', + '1..3', + '# tests 3', + '# pass 2', + '# fail 1', + '', + '', + ].join('\n')); + t.same(r.exitCode, 1); + + var stderr = r.stderr.toString('utf8'); + + t.same(stderr, ''); + t.end(); + }); +}); diff --git a/test/async-await/async-bug.js b/test/async-await/async-bug.js new file mode 100644 index 00000000..219db582 --- /dev/null +++ b/test/async-await/async-bug.js @@ -0,0 +1,32 @@ +'use strict'; + +var test = require('../../'); + +function myCode(arr) { + let sum = 0; + // oops forgot to handle null + for (let i = 0; i < arr.length; i++) { + sum += arr[i]; + } + return sum; +} + +test('async-error', async function myTest(t) { + await sleep(100); + t.ok(true, 'before throw'); + + const sum = myCode([1, 2, 3]); + t.equal(sum, 6); + + const sum2 = myCode(null); + t.equal(sum2, 0); + + t.end(); +}); + + +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} diff --git a/test/promise_fail.js b/test/promise_fail.js index ddc93412..1aeb95d5 100644 --- a/test/promise_fail.js +++ b/test/promise_fail.js @@ -20,21 +20,29 @@ tap.test('callback returning rejected promise should cause that test (and only t } var strippedString = stripFullStack(rowsString); + var lines = strippedString.split('\n'); + lines = lines.filter(function (line) { + return !/^(\s+)at(\s+)(?:Test\.)?(?:$|\s)/.test(line); + }); + strippedString = lines.join('\n'); // hack for consistency across all versions of node // some versions produce a longer stack trace for some reason // since this doesn't affect the validity of the test, the extra line is removed if present // the regex just removes the lines "at " and "[... stack stripped ...]" if they occur together - strippedString = strippedString.replace(/.+at \n.+\[\.\.\. stack stripped \.\.\.\]\n/, ''); + strippedString = strippedString + .replace(/.+at (?:Test\.)?\n.+\[\.\.\. stack stripped \.\.\.\]\n/g, '') + .replace(/(?:(.+)\[\.\.\. stack stripped \.\.\.\]\n)+/g, '$1[... stack stripped ...]\n'); tt.same(strippedString, [ 'TAP version 13', '# promise', 'not ok 1 Error: rejection message', ' ---', - ' operator: fail', + ' operator: error', ' stack: |-', - ' Error: Error: rejection message', + ' Error: rejection message', + ' at $TEST/promises/fail.js:$LINE:$COL', ' [... stack stripped ...]', ' ...', '# after', @@ -63,12 +71,19 @@ tap.test('subtest callback returning rejected promise should cause that subtest } var strippedString = stripFullStack(rowsString); + var lines = strippedString.split('\n'); + lines = lines.filter(function (line) { + return !/^(\s+)at(\s+)(?:Test\.)?(?:$|\s)/.test(line); + }); + strippedString = lines.join('\n'); // hack for consistency across all versions of node // some versions produce a longer stack trace for some reason // since this doesn't affect the validity of the test, the extra line is removed if present // the regex just removes the lines "at " and "[... stack stripped ...]" if they occur together - strippedString = strippedString.replace(/.+at \n.+\[\.\.\. stack stripped \.\.\.\]\n/, ''); + strippedString = strippedString + .replace(/.+at (?:Test\.)?\n.+\[\.\.\. stack stripped \.\.\.\]\n/, '') + .replace(/(?:(.+)\[\.\.\. stack stripped \.\.\.\]\n)+/g, '$1[... stack stripped ...]\n'); tt.same(strippedString, [ 'TAP version 13', @@ -76,9 +91,10 @@ tap.test('subtest callback returning rejected promise should cause that subtest '# sub test that should fail', 'not ok 1 Error: rejection message', ' ---', - ' operator: fail', + ' operator: error', ' stack: |-', - ' Error: Error: rejection message', + ' Error: rejection message', + ' at $TEST/promises/subTests.js:$LINE:$COL', ' [... stack stripped ...]', ' ...', '# sub test that should pass',