Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {
} = primordials;

const { spawn } = require('child_process');
const { existsSync } = require('fs');
const { finished } = require('internal/streams/end-of-stream');
const { availableParallelism } = require('os');
const { resolve, sep, isAbsolute } = require('path');
Expand Down Expand Up @@ -161,9 +162,20 @@ function createTestFileList(patterns, cwd) {
});
const results = glob.globSync();

if (hasUserSuppliedPattern && results.length === 0 && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) {
console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`);
process.exit(kGenericUserError);
if (hasUserSuppliedPattern) {
if (results.length === 0 && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) {
console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`);
process.exit(kGenericUserError);
}

const missing = ArrayPrototypeFilter(patterns, (pattern, i) => {
return !glob.matchers[i].hasMagic() && !existsSync(resolve(cwd, pattern));
});

if (missing.length > 0) {
console.error(`Could not find '${ArrayPrototypeJoin(missing, ', ')}'`);
process.exit(kGenericUserError);
}
Comment on lines +165 to +178

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (hasUserSuppliedPattern) {
if (results.length === 0 && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) {
console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`);
process.exit(kGenericUserError);
}
const missing = ArrayPrototypeFilter(patterns, (pattern, i) => {
return !glob.matchers[i].hasMagic() && !existsSync(resolve(cwd, pattern));
});
if (missing.length > 0) {
console.error(`Could not find '${ArrayPrototypeJoin(missing, ', ')}'`);
process.exit(kGenericUserError);
}
if (hasUserSuppliedPattern && results.length === 0) {
console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`);
process.exit(kGenericUserError);
}

Wouldn't this suffice?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not — that check only fires when all patterns collectively match nothing, which is the pre-existing behavior (minus the hasMagic() guard). In the repro from #64109, spec/*.test.js does match files, so results.length > 0 and no-tests-here is still silently dropped. The per-pattern check is what catches a nonexistent literal path even when other patterns match.

I also deliberately kept the hasMagic() guard on both checks so a glob that matches zero files keeps its current no-error behavior — removing it would newly fail runs that pass optional globs. Happy to make globs error too if that's preferred, but it seemed like a separate (and riskier) behavior change.

}

return ArrayPrototypeSort(results);
Expand Down
36 changes: 36 additions & 0 deletions test/parallel/test-runner-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,42 @@ for (const isolation of ['none', 'process']) {
assert.match(child.stderr.toString(), /^Could not find/);
}

{
// A file that is not found should error even when other patterns match.
const args = [
'--test',
`--test-isolation=${isolation}`,
'a-random-file-that-does-not-exist.js',
join(testFixtures, 'default-behavior/test/random.cjs'),
];
const child = spawnSync(process.execPath, args);

assert.strictEqual(child.status, 1);
assert.strictEqual(child.signal, null);
assert.strictEqual(child.stdout.toString(), '');
assert.match(child.stderr.toString(),
/^Could not find 'a-random-file-that-does-not-exist\.js'/);
}

{
// Options after positional arguments are treated as patterns and should
// error instead of being silently dropped.
const args = [
'--test',
`--test-isolation=${isolation}`,
join(testFixtures, 'default-behavior/test/random.cjs'),
'--test-reporter',
'tap',
];
const child = spawnSync(process.execPath, args);

assert.strictEqual(child.status, 1);
assert.strictEqual(child.signal, null);
assert.strictEqual(child.stdout.toString(), '');
assert.match(child.stderr.toString(),
/^Could not find '--test-reporter, tap'/);
}

{
// Default behavior. node_modules is ignored. Files that don't match the
// pattern are ignored except in test/ directories.
Expand Down
Loading