GeezerVison is showing github.com — test_runner: catch reporter errors · nodejs/node@103c4a0
File treeExpand file treeCollapse file tree Open diff view settings fixtures/test-runner/custom_reporters
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/harness.js +9-1Lines changed: 9 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -19,12 +19,15 @@ const { kEmptyObject } = require('internal/util'); | 19 | 19 | const { kCancelledByParent, Test, Suite } = require('internal/test_runner/test'); | 20 | 20 | const { | 21 | 21 | parseCommandLine, | | 22 | + reporterScope, | 22 | 23 | setupTestReporters, | 23 | 24 | } = require('internal/test_runner/utils'); | 24 | 25 | const { bigint: hrtime } = process.hrtime; | 25 | 26 |
| 26 | 27 | const testResources = new SafeMap(); | 27 | 28 |
| | 29 | +testResources.set(reporterScope.asyncId(), reporterScope); | | 30 | + | 28 | 31 | function createTestTree(options = kEmptyObject) { | 29 | 32 | return setup(new Test({ __proto__: null, ...options, name: '<root>' })); | 30 | 33 | } | @@ -38,9 +41,14 @@ function createProcessEventHandler(eventName, rootTest) { | 38 | 41 | throw err; | 39 | 42 | } | 40 | 43 |
| 41 | | - // Check if this error is coming from a test. If it is, fail the test. | 42 | 44 | const test = testResources.get(executionAsyncId()); | 43 | 45 |
| | 46 | + // Check if this error is coming from a reporter. If it is, throw it. | | 47 | + if (test === reporterScope) { | | 48 | + throw err; | | 49 | + } | | 50 | + | | 51 | + // Check if this error is coming from a test. If it is, fail the test. | 44 | 52 | if (!test || test.finished) { | 45 | 53 | // If the test is already finished or the resource that created the error | 46 | 54 | // is not mapped to a Test, report this as a top level diagnostic. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/utils.js +5-3Lines changed: 5 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -20,6 +20,7 @@ const { | 20 | 20 | StringPrototypeSlice, | 21 | 21 | } = primordials; | 22 | 22 |
| | 23 | +const { AsyncResource } = require('async_hooks'); | 23 | 24 | const { basename, relative } = require('path'); | 24 | 25 | const { createWriteStream } = require('fs'); | 25 | 26 | const { pathToFileURL } = require('internal/url'); | @@ -169,15 +170,15 @@ async function getReportersMap(reporters, destinations, rootTest) { | 169 | 170 | }); | 170 | 171 | } | 171 | 172 |
| 172 | | - | 173 | | -async function setupTestReporters(rootTest) { | | 173 | +const reporterScope = new AsyncResource('TestReporterScope'); | | 174 | +const setupTestReporters = reporterScope.bind(async (rootTest) => { | 174 | 175 | const { reporters, destinations } = parseCommandLine(); | 175 | 176 | const reportersMap = await getReportersMap(reporters, destinations, rootTest); | 176 | 177 | for (let i = 0; i < reportersMap.length; i++) { | 177 | 178 | const { reporter, destination } = reportersMap[i]; | 178 | 179 | compose(rootTest.reporter, reporter).pipe(destination); | 179 | 180 | } | 180 | | -} | | 181 | +}); | 181 | 182 |
| 182 | 183 | let globalTestOptions; | 183 | 184 |
| @@ -420,6 +421,7 @@ module.exports = { | 420 | 421 | isSupportedFileType, | 421 | 422 | isTestFailureError, | 422 | 423 | parseCommandLine, | | 424 | + reporterScope, | 423 | 425 | setupTestReporters, | 424 | 426 | getCoverageReport, | 425 | 427 | }; |
Collapse file Copy file name to clipboard +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +module.exports = async function * customReporter() { | | 4 | + yield 'Going to throw an error\n'; | | 5 | + setImmediate(() => { | | 6 | + throw new Error('Reporting error'); | | 7 | + }); | | 8 | +}; |
Collapse file Copy file name to clipboard +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +module.exports = async function * customReporter() { | | 4 | + yield 'Going to throw an error\n'; | | 5 | + throw new Error('Reporting error'); | | 6 | +}; |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-runner-reporters.js +21Lines changed: 21 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -135,4 +135,25 @@ describe('node:test reporters', { concurrency: true }, () => { | 135 | 135 | assert.strictEqual(child.stdout.toString(), ''); | 136 | 136 | assert.match(child.stderr.toString(), /ERR_INVALID_ARG_TYPE/); | 137 | 137 | }); | | 138 | + | | 139 | + it('should throw when reporter errors', async () => { | | 140 | + const child = spawnSync(process.execPath, | | 141 | + ['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/throwing.js'), | | 142 | + fixtures.path('test-runner/default-behavior/index.test.js')]); | | 143 | + assert.strictEqual(child.status, 7); | | 144 | + assert.strictEqual(child.signal, null); | | 145 | + assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n'); | | 146 | + assert.match(child.stderr.toString(), /Error: Reporting error\r?\n\s+at customReporter/); | | 147 | + }); | | 148 | + | | 149 | + it('should throw when reporter errors asynchronously', async () => { | | 150 | + const child = spawnSync(process.execPath, | | 151 | + ['--test', '--test-reporter', | | 152 | + fixtures.fileURL('test-runner/custom_reporters/throwing-async.js'), | | 153 | + fixtures.path('test-runner/default-behavior/index.test.js')]); | | 154 | + assert.strictEqual(child.status, 7); | | 155 | + assert.strictEqual(child.signal, null); | | 156 | + assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n'); | | 157 | + assert.match(child.stderr.toString(), /Emitted 'error' event on Duplex instance/); | | 158 | + }); | 138 | 159 | }); |
|