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')],