diff --git a/lib/internal/test_runner/tap_stream.js b/lib/internal/test_runner/tap_stream.js index 769d275..003b9f2 100644 --- a/lib/internal/test_runner/tap_stream.js +++ b/lib/internal/test_runner/tap_stream.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/59527de13d39327eb3dfa8dedc92241eb40066d5/lib/internal/test_runner/tap_stream.js +// https://github.com/nodejs/node/blob/cb7e0c59df10a42cd6930ca7f99d3acee1ce7627/lib/internal/test_runner/tap_stream.js 'use strict' @@ -177,7 +177,7 @@ function jsToYaml (indent, name, value) { } if (isErrorObj) { - const { kTestCodeFailure, kHookFailure } = lazyLoadTest() + const { kTestCodeFailure, kUnwrapErrors } = lazyLoadTest() const { cause, code, @@ -191,7 +191,7 @@ function jsToYaml (indent, name, value) { // If the ERR_TEST_FAILURE came from an error provided by user code, // then try to unwrap the original error message and stack. - if (code === 'ERR_TEST_FAILURE' && (failureType === kTestCodeFailure || failureType === kHookFailure)) { + if (code === 'ERR_TEST_FAILURE' && kUnwrapErrors.has(failureType)) { errStack = cause?.stack ?? errStack errCode = cause?.code ?? errCode if (failureType === kTestCodeFailure) { diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 4049abc..e8549ca 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/a165193c5c8e4bcfbd12b2c3f6e55a81a251c258/lib/internal/test_runner/test.js +// https://github.com/nodejs/node/blob/cb7e0c59df10a42cd6930ca7f99d3acee1ce7627/lib/internal/test_runner/test.js 'use strict' @@ -16,6 +16,7 @@ const { PromiseResolve, ReflectApply, SafeMap, + SafeSet, SafePromiseAll, SafePromiseRace, Symbol @@ -63,6 +64,9 @@ const testOnlyFlag = !isTestRunner && getOptionValue('--test-only') const kShouldAbort = Symbol('kShouldAbort') const kRunHook = Symbol('kRunHook') const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']) +const kUnwrapErrors = new SafeSet() + .add(kTestCodeFailure).add(kHookFailure) + .add('uncaughtException').add('unhandledRejection') function stopTest (timeout, signal) { if (timeout === kDefaultTimeout) { @@ -725,4 +729,13 @@ class Suite extends Test { } } -module.exports = { kCancelledByParent, kDefaultIndent, kSubtestsFailed, kTestCodeFailure, Test, Suite, ItTest } +module.exports = { + ItTest, + kCancelledByParent, + kDefaultIndent, + kSubtestsFailed, + kTestCodeFailure, + kUnwrapErrors, + Suite, + Test +} diff --git a/test/message.js b/test/message.js index 4520448..2a35fc4 100755 --- a/test/message.js +++ b/test/message.js @@ -27,6 +27,7 @@ const stackTraceLine = /^\s+\*$/ const stackTraceEndLine = /^\s+\.\.\.$/ const nodejs14NotEmittedWarn = /^# Warning:.*\breject/ +const nodejs14NotEmittedUnhandledRejection = /'unhandledRejection'/ // https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/test/message/testcfg.py#L53 async function IsFailureOutput (self, output) { @@ -38,6 +39,10 @@ async function IsFailureOutput (self, output) { // Node.js 14 doesn't emit some warnings if (process.version.startsWith('v14.') && nodejs14NotEmittedWarn.test(line)) continue + if (process.version.startsWith('v14.') && nodejs14NotEmittedUnhandledRejection.test(line)) { + patterns.push(WAIT_FOR_ELLIPSIS) + continue + } // Sometimes Node.js won't have any stack trace, but we would if (stackTraceEndLine.test(line) && patterns[patterns.length - 1].toString().endsWith("code: 'ERR_TEST_FAILURE'$")) { diff --git a/test/message/test_runner_desctibe_it.out b/test/message/test_runner_desctibe_it.out index 2d58215..812a373 100644 --- a/test/message/test_runner_desctibe_it.out +++ b/test/message/test_runner_desctibe_it.out @@ -421,6 +421,7 @@ not ok 49 - callback async throw code: 'ERR_TEST_FAILURE' stack: |- * + * ... # Subtest: callback async throw after done ok 50 - callback async throw after done diff --git a/test/message/test_runner_output.js b/test/message/test_runner_output.js index e4edf0a..9a1dc66 100644 --- a/test/message/test_runner_output.js +++ b/test/message/test_runner_output.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/a3e110820ff98702e1761831e7beaf0f5f1f75e7/test/message/test_runner_output.js +// https://github.com/nodejs/node/blob/cb7e0c59df10a42cd6930ca7f99d3acee1ce7627/test/message/test_runner_output.js // Flags: --no-warnings 'use strict' require('../common') @@ -371,3 +371,15 @@ test('rejected thenable', () => { } } }) + +test('unfinished test with uncaughtException', async () => { + await new Promise(() => { + setTimeout(() => { throw new Error('foo') }) + }) +}) + +test('unfinished test with unhandledRejection', async () => { + await new Promise(() => { + setTimeout(() => Promise.reject(new Error('bar'))) + }) +}) diff --git a/test/message/test_runner_output.out b/test/message/test_runner_output.out index 49a19fe..fe5698e 100644 --- a/test/message/test_runner_output.out +++ b/test/message/test_runner_output.out @@ -463,6 +463,7 @@ not ok 51 - callback async throw code: 'ERR_TEST_FAILURE' stack: |- * + * ... # Subtest: callback async throw after done ok 52 - callback async throw after done @@ -601,8 +602,32 @@ not ok 62 - rejected thenable error: 'custom error' code: 'ERR_TEST_FAILURE' ... +# Subtest: unfinished test with uncaughtException +not ok 63 - unfinished test with uncaughtException + --- + duration_ms: * + failureType: 'uncaughtException' + error: 'foo' + code: 'ERR_TEST_FAILURE' + stack: |- + * + * + * + ... +# Subtest: unfinished test with unhandledRejection +not ok 64 - unfinished test with unhandledRejection + --- + duration_ms: * + failureType: 'unhandledRejection' + error: 'bar' + code: 'ERR_TEST_FAILURE' + stack: |- + * + * + * + ... # Subtest: invalid subtest fail -not ok 63 - invalid subtest fail +not ok 65 - invalid subtest fail --- duration_ms: * failureType: 'parentAlreadyFinished' @@ -611,16 +636,16 @@ not ok 63 - invalid subtest fail stack: |- * ... -1..63 +1..65 # Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event. # Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event. # Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event. -# tests 63 +# tests 65 # pass 27 -# fail 19 +# fail 21 # cancelled 2 # skipped 10 # todo 5