diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 4f833741cf999fa..bd68c9bd5545829 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1604,8 +1604,8 @@ E('ERR_TAP_VALIDATION_ERROR', function(errorMsg) { }, Error); E('ERR_TEST_FAILURE', function(error, failureType) { hideInternalStackFrames(this); - assert(typeof failureType === 'string', - "The 'failureType' argument must be of type string."); + assert(typeof failureType === 'string' || typeof failureType === 'symbol', + "The 'failureType' argument must be of type string or symbol."); let msg = error?.message ?? error; diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 3dec42d0c4aa9eb..c85b26f3b8c2502 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -18,7 +18,10 @@ const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const { kEmptyObject } = require('internal/util'); const { getOptionValue } = require('internal/options'); const { kCancelledByParent, Test, ItTest, Suite } = require('internal/test_runner/test'); -const { setupTestReporters } = require('internal/test_runner/utils'); +const { + kAsyncBootstrapFailure, + setupTestReporters, +} = require('internal/test_runner/utils'); const { bigint: hrtime } = process.hrtime; const isTestRunnerCli = getOptionValue('--test'); @@ -31,6 +34,13 @@ function createTestTree(options = kEmptyObject) { function createProcessEventHandler(eventName, rootTest) { return (err) => { + if (err?.failureType === kAsyncBootstrapFailure) { + // Something went wrong during the asynchronous portion of bootstrapping + // the test runner. Since the test runner is not setup properly, we can't + // do anything but throw the error. + throw err.cause; + } + // Check if this error is coming from a test. If it is, fail the test. const test = testResources.get(executionAsyncId()); diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index d981c0f6d4381bc..8e8769f1b40771a 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -22,6 +22,7 @@ const { } = require('internal/errors'); const { compose } = require('stream'); +const kAsyncBootstrapFailure = Symbol('asyncBootstrapFailure'); const kMultipleCallbackInvocations = 'multipleCallbackInvocations'; const kRegExpPattern = /^\/(.*)\/([a-z]*)$/; const kSupportedFileExtensions = /\.[cm]?js$/; @@ -164,10 +165,14 @@ async function setupTestReporters(testsStream) { 'must match the number of specified \'--test-reporter-destination\''); } - const reportersMap = await getReportersMap(reporters, destinations); - for (let i = 0; i < reportersMap.length; i++) { - const { reporter, destination } = reportersMap[i]; - compose(testsStream, reporter).pipe(destination); + try { + const reportersMap = await getReportersMap(reporters, destinations); + for (let i = 0; i < reportersMap.length; i++) { + const { reporter, destination } = reportersMap[i]; + compose(testsStream, reporter).pipe(destination); + } + } catch (err) { + throw new ERR_TEST_FAILURE(err, kAsyncBootstrapFailure); } } @@ -177,5 +182,6 @@ module.exports = { doesPathMatchFilter, isSupportedFileType, isTestFailureError, + kAsyncBootstrapFailure, setupTestReporters, }; diff --git a/test/parallel/test-runner-reporters.js b/test/parallel/test-runner-reporters.js index 5961fd8752d1769..1ea6de77727242e 100644 --- a/test/parallel/test-runner-reporters.js +++ b/test/parallel/test-runner-reporters.js @@ -116,4 +116,16 @@ describe('node:test reporters', { concurrency: true }, () => { /^package: reporter-esm{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/, ); }); + + it('should throw when reporter setup throws asynchronously', async () => { + const child = spawnSync( + process.execPath, + ['--test', '--test-reporter', fixtures.path('empty.js'), 'reporters.js'], + { cwd: fixtures.path('test-runner') } + ); + assert.strictEqual(child.status, 7); + assert.strictEqual(child.signal, null); + assert.strictEqual(child.stdout.toString(), ''); + assert.match(child.stderr.toString(), /ERR_INVALID_ARG_TYPE/); + }); });