From 60aba6b5869e7f8946719788a3a47072a06369cb Mon Sep 17 00:00:00 2001 From: uditDewan Date: Wed, 8 Jul 2026 22:41:16 -0400 Subject: [PATCH] test_runner: apply run() name filters with isolation none The testNamePatterns and testSkipPatterns options of run() were only forwarded to spawned processes as CLI flags, so they were silently ignored when isolation was 'none' and tests ran in the current process. Apply the validated patterns to the root test configuration in that case. Exempt file level tests from name filtering: they represent test files rather than named tests, and filtering them prevented any test from running in watch mode when the root configuration contained name patterns. Fixes: https://github.com/nodejs/node/issues/64359 --- lib/internal/test_runner/runner.js | 15 ++++++++++++ test/parallel/test-runner-run.mjs | 37 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 7f50cc8521bcda..893861eb58ad97 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -284,6 +284,12 @@ class FileTest extends Test { this.timeout = null; } + willBeFilteredByName() { + // A FileTest represents a test file, not a named test. Name filters are + // applied to the tests inside of the file by the child process. + return false; + } + #skipReporting() { return this.#reportedChildren > 0 && (!this.error || this.error.failureType === kSubtestsFailed); } @@ -936,6 +942,15 @@ function run(options = kEmptyObject) { testTagFilters, }; + if (isolation === 'none') { + // Tests run in this process, so the name filters must be applied to the + // root test's configuration. Under isolation 'process' the filters are + // forwarded to each child process via --test-name-pattern and + // --test-skip-pattern instead. + globalOptions.testNamePatterns = testNamePatterns ?? globalOptions.testNamePatterns; + globalOptions.testSkipPatterns = testSkipPatterns ?? globalOptions.testSkipPatterns; + } + const root = createTestTree(rootTestOptions, globalOptions); let testFiles = files ?? createTestFileList(globPatterns, cwd); const { isTestRunner } = globalOptions; diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index b6eb6b6af51877..cb93ce03930eb3 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -213,6 +213,43 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { assert.strictEqual(result[5], '# tests 1\n'); }); + it('should apply testNamePatterns and testSkipPatterns with isolation \'none\'', async () => { + const stream = run({ + files: [join(testFixtures, 'default-behavior/test/skip_by_name.cjs')], + isolation: 'none', + testNamePatterns: [/should/], + testSkipPatterns: [/skipped/], + }); + stream.on('test:fail', common.mustNotCall()); + stream.on('test:pass', common.mustCall((event) => { + assert.strictEqual(event.name, 'this should be executed'); + }, 1)); + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + }); + + it('should run tests with testNamePatterns in watch mode with isolation \'none\'', async () => { + // The name filters must not be applied to the file level test that wraps + // the spawned process. Without this, no test ever runs. + const controller = new AbortController(); + const passes = []; + const stream = run({ + files: [join(testFixtures, 'default-behavior/test/skip_by_name.cjs')], + watch: true, + isolation: 'none', + signal: controller.signal, + testNamePatterns: [/executed/], + }); + stream.on('test:pass', (event) => { + passes.push(event.name); + controller.abort(); + }); + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + assert.ok(passes.length > 0); + assert.ok(!passes.includes('this should be skipped')); + }); + it('should pass only to children', async () => { const result = await run({ files: [join(testFixtures, 'test_only.js')],