From b48ff5b96c4916a93b0b1800015e97c22fafcfc1 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Mon, 20 Jul 2026 23:10:59 -0400 Subject: [PATCH] test_runner: restore directory search for --test Passing a directory to `node --test` (e.g. `node --test tests`) matched the directory itself as a glob pattern and then tried to run it as a test file, failing with MODULE_NOT_FOUND. Before glob patterns were supported, a directory argument was searched for test files within it. Expand a pattern that resolves to a directory into a search for the default test files inside it. Despite the report framing this as Windows-only, it reproduces on every platform: it is a plain regression from when directory arguments stopped being searched. Fixes: https://github.com/nodejs/node/issues/64555 Signed-off-by: Paul Bouchon --- lib/internal/test_runner/runner.js | 19 +++++++++++++++++++ test/parallel/test-runner-cli.js | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index dc6529c220539a..ee587e041a9d7f 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -18,6 +18,7 @@ const { ObjectAssign, PromisePrototypeThen, PromiseWithResolvers, + RegExpPrototypeSymbolReplace, SafeMap, SafePromiseAll, SafePromiseAllReturnVoid, @@ -34,6 +35,7 @@ const { } = primordials; const { spawn } = require('child_process'); +const { statSync } = require('fs'); const { finished } = require('internal/streams/end-of-stream'); const { availableParallelism } = require('os'); const { resolve, sep, isAbsolute } = require('path'); @@ -153,6 +155,23 @@ function createTestFileList(patterns, cwd) { const hasUserSuppliedPattern = patterns != null; if (!patterns || patterns.length === 0) { patterns = [kDefaultPattern]; + } else { + patterns = ArrayPrototypeMap(patterns, (pattern) => { + // A directory argument is expanded to search for the default test files + // within it, matching the behavior from before glob patterns were + // supported. Glob patterns always use forward slashes, so a trailing + // path separator (of either kind) is stripped before appending. + const resolved = isAbsolute(pattern) ? pattern : resolve(cwd, pattern); + try { + if (statSync(resolved).isDirectory()) { + return `${RegExpPrototypeSymbolReplace(/[\\/]+$/, pattern, '')}/${kDefaultPattern}`; + } + } catch { + // Not a directory or not accessible; leave the pattern as-is so the + // existing "Could not find" handling still applies. + } + return pattern; + }); } const glob = new Glob(patterns, { __proto__: null, diff --git a/test/parallel/test-runner-cli.js b/test/parallel/test-runner-cli.js index f563d67d1a9a41..0702c5c1500c29 100644 --- a/test/parallel/test-runner-cli.js +++ b/test/parallel/test-runner-cli.js @@ -62,6 +62,27 @@ for (const isolation of ['none', 'process']) { assert.doesNotMatch(stdout, /ok 4 - this should pass/); } + { + // A directory argument is searched for the default test files within it, + // both bare and with a trailing separator. + // Refs: https://github.com/nodejs/node/issues/64555 + for (const dir of ['matching-patterns', 'matching-patterns/']) { + const args = ['--test', '--test-reporter=tap', + '--no-experimental-strip-types', + `--test-isolation=${isolation}`, dir]; + const child = spawnSync(process.execPath, args, { cwd: testFixtures }); + + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); + assert.strictEqual(child.stderr.toString(), ''); + const stdout = child.stdout.toString(); + + assert.match(stdout, /ok 1 - this should pass/); + assert.match(stdout, /ok 2 - this should pass/); + assert.match(stdout, /ok 3 - this should pass/); + } + } + { // Should match files with "-test.(c|m)(t|j)s" suffix when typescript support is enabled const args = ['--test', '--test-reporter=tap', '--no-warnings',