diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index fc540599cf9bc5..cce8e07fab53a2 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -519,6 +519,8 @@ function run(options = kEmptyObject) { root.harness.shouldColorizeTestFiles ||= shouldColorizeTestFiles(root); if (process.env.NODE_TEST_CONTEXT !== undefined) { + process.emitWarning('node:test run() is being called recursively within a test file. skipping running files.'); + root.postRun(); return root.reporter; } let testFiles = files ?? createTestFileList(); diff --git a/test/fixtures/test-runner/recursive_run.js b/test/fixtures/test-runner/recursive_run.js new file mode 100644 index 00000000000000..e9583664499cd4 --- /dev/null +++ b/test/fixtures/test-runner/recursive_run.js @@ -0,0 +1,7 @@ +'use strict'; + +const { test, run } = require('node:test'); + +test('recursive run() calls', async () => { + for await (const event of run({ files: [__filename] })); +}); diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index 1dc5cf99da8d62..4c974ac8641992 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -522,6 +522,18 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { // eslint-disable-next-line no-unused-vars for await (const _ of stream); }); + + it('should avoid running recursively', async () => { + const stream = run({ files: [join(testFixtures, 'recursive_run.js')] }); + let stderr = ''; + stream.on('test:fail', common.mustNotCall()); + stream.on('test:pass', common.mustCall(1)); + stream.on('test:stderr', (c) => { stderr += c.message; }); + + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + assert.match(stderr, /Warning: node:test run\(\) is being called recursively/); + }); }); describe('forceExit', () => {