From d326348af94df274645894fe932b24e0d915ad65 Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sun, 19 Feb 2023 21:15:03 +0200 Subject: [PATCH] test_runner: bootstrap reporters before running tests --- doc/api/test.md | 3 +++ lib/internal/main/test_runner.js | 5 ++--- lib/internal/test_runner/harness.js | 12 +++++++++--- lib/internal/test_runner/runner.js | 21 +++++++++++++++------ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/doc/api/test.md b/doc/api/test.md index be5b9baeeb9fbe..4f8caffe12487a 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -684,6 +684,9 @@ added: **Default:** `false`. * `files`: {Array} An array containing the list of files to run. **Default** matching files from [test runner execution model][]. + * `setup` {Function} A function that accepts the `TestsStream` instance + and can be used to setup listeners before any tests are run. + **Default:** `undefined`. * `signal` {AbortSignal} Allows aborting an in-progress test execution. * `timeout` {number} A number of milliseconds the test execution will fail after. diff --git a/lib/internal/main/test_runner.js b/lib/internal/main/test_runner.js index 658aab03323a24..9a209eb18961b5 100644 --- a/lib/internal/main/test_runner.js +++ b/lib/internal/main/test_runner.js @@ -22,8 +22,7 @@ if (isUsingInspector()) { inspectPort = process.debugPort; } -const testsStream = run({ concurrency, inspectPort, watch: getOptionValue('--watch') }); -testsStream.once('test:fail', () => { +run({ concurrency, inspectPort, watch: getOptionValue('--watch'), setup: setupTestReporters }) +.once('test:fail', () => { process.exitCode = kGenericUserError; }); -setupTestReporters(testsStream); diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 6f196645ff473d..078ae4c41fb6d1 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -161,21 +161,27 @@ function setup(root) { } let globalRoot; +let reportersSetup; function getGlobalRoot() { if (!globalRoot) { globalRoot = createTestTree(); globalRoot.reporter.once('test:fail', () => { process.exitCode = kGenericUserError; }); - setupTestReporters(globalRoot.reporter); + reportersSetup = setupTestReporters(globalRoot.reporter); } return globalRoot; } +async function startSubtest(subtest) { + await reportersSetup; + await subtest.start(); +} + function test(name, options, fn) { const parent = testResources.get(executionAsyncId()) || getGlobalRoot(); const subtest = parent.createSubtest(Test, name, options, fn); - return subtest.start(); + return startSubtest(subtest); } function runInParentContext(Factory) { @@ -183,7 +189,7 @@ function runInParentContext(Factory) { const parent = testResources.get(executionAsyncId()) || getGlobalRoot(); const subtest = parent.createSubtest(Factory, name, options, fn, overrides); if (parent === getGlobalRoot()) { - subtest.start(); + startSubtest(subtest); } } diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index d73b14868be32a..2e0c62505f5004 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -13,8 +13,10 @@ const { ObjectAssign, ObjectKeys, PromisePrototypeThen, + SafePromiseAll, SafePromiseAllReturnVoid, SafePromiseAllSettledReturnVoid, + PromiseResolve, SafeMap, SafeSet, StringPrototypeIndexOf, @@ -24,6 +26,7 @@ const { const { spawn } = require('child_process'); const { readdirSync, statSync } = require('fs'); +const { finished } = require('internal/streams/end-of-stream'); // TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern. const { createInterface } = require('readline'); const { FilesWatcher } = require('internal/watch_mode/files_watcher'); @@ -33,7 +36,7 @@ const { ERR_TEST_FAILURE, }, } = require('internal/errors'); -const { validateArray, validateBoolean } = require('internal/validators'); +const { validateArray, validateBoolean, validateFunction } = require('internal/validators'); const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector'); const { kEmptyObject } = require('internal/util'); const { createTestTree } = require('internal/test_runner/harness'); @@ -299,7 +302,10 @@ function runTestFile(path, root, inspectPort, filesWatcher) { subtest.addToReport(ast); }); - const { 0: code, 1: signal } = await once(child, 'exit', { signal: t.signal }); + const { 0: { 0: code, 1: signal } } = await SafePromiseAll([ + once(child, 'exit', { signal: t.signal }), + finished(parser, { signal: t.signal }), + ]); runningProcesses.delete(path); runningSubtests.delete(path); @@ -348,7 +354,7 @@ function run(options) { if (options === null || typeof options !== 'object') { options = kEmptyObject; } - const { concurrency, timeout, signal, files, inspectPort, watch } = options; + const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options; if (files != null) { validateArray(files, 'options.files'); @@ -356,6 +362,9 @@ function run(options) { if (watch != null) { validateBoolean(watch, 'options.watch'); } + if (setup != null) { + validateFunction(setup, 'options.setup'); + } const root = createTestTree({ concurrency, timeout, signal }); const testFiles = files ?? createTestFileList(); @@ -366,13 +375,13 @@ function run(options) { filesWatcher = watchFiles(testFiles, root, inspectPort); postRun = undefined; } - - PromisePrototypeThen(SafePromiseAllSettledReturnVoid(testFiles, (path) => { + const runFiles = () => SafePromiseAllSettledReturnVoid(testFiles, (path) => { const subtest = runTestFile(path, root, inspectPort, filesWatcher); runningSubtests.set(path, subtest); return subtest; - }), postRun); + }); + PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root.reporter)), runFiles), postRun); return root.reporter; }