Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: accept only in run #49753

Merged
merged 1 commit into from
Sep 24, 2023
Merged
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
2 changes: 2 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,8 @@ changes:
number. If a nullish value is provided, each process gets its own port,
incremented from the primary's `process.debugPort`.
**Default:** `undefined`.
* `only`: {boolean} If truthy, the test context will only run tests that
have the `only` option set
* `setup` {Function} A function that accepts the `TestsStream` instance
and can be used to setup listeners before any tests are run.
**Default:** `undefined`.
Expand Down
41 changes: 24 additions & 17 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,17 @@ function filterExecArgv(arg, i, arr) {
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
}

function getRunArgs({ path, inspectPort, testNamePatterns }) {
function getRunArgs(path, { inspectPort, testNamePatterns, only }) {
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
if (isUsingInspector()) {
ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`);
}
if (testNamePatterns) {
if (testNamePatterns != null) {
ArrayPrototypeForEach(testNamePatterns, (pattern) => ArrayPrototypePush(argv, `--test-name-pattern=${pattern}`));
}
if (only === true) {
ArrayPrototypePush(argv, '--test-only');
}
ArrayPrototypePush(argv, path);

return argv;
Expand Down Expand Up @@ -301,17 +304,17 @@ class FileTest extends Test {
}
}

function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
function runTestFile(path, filesWatcher, opts) {
const watchMode = filesWatcher != null;
const subtest = root.createSubtest(FileTest, path, async (t) => {
const args = getRunArgs({ __proto__: null, path, inspectPort, testNamePatterns });
const subtest = opts.root.createSubtest(FileTest, path, async (t) => {
const args = getRunArgs(path, opts);
const stdio = ['pipe', 'pipe', 'pipe'];
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
if (watchMode) {
stdio.push('ipc');
env.WATCH_REPORT_DEPENDENCIES = '1';
}
if (root.harness.shouldColorizeTestFiles) {
if (opts.root.harness.shouldColorizeTestFiles) {
env.FORCE_COLOR = '1';
}

Expand Down Expand Up @@ -358,7 +361,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
filesWatcher.runningProcesses.delete(path);
filesWatcher.runningSubtests.delete(path);
if (filesWatcher.runningSubtests.size === 0) {
root.reporter[kEmitMessage]('test:watch:drained');
opts.root.reporter[kEmitMessage]('test:watch:drained');
}
}

Expand All @@ -381,10 +384,10 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
return subtest.start();
}

function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
function watchFiles(testFiles, opts) {
const runningProcesses = new SafeMap();
const runningSubtests = new SafeMap();
const watcher = new FilesWatcher({ __proto__: null, debounce: 200, mode: 'filter', signal });
const watcher = new FilesWatcher({ __proto__: null, debounce: 200, mode: 'filter', signal: opts.signal });
const filesWatcher = { __proto__: null, watcher, runningProcesses, runningSubtests };

watcher.on('changed', ({ owners }) => {
Expand All @@ -400,19 +403,19 @@ function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
}
if (!runningSubtests.size) {
// Reset the topLevel counter
root.harness.counters.topLevel = 0;
opts.root.harness.counters.topLevel = 0;
}
await runningSubtests.get(file);
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher, testNamePatterns));
runningSubtests.set(file, runTestFile(file, filesWatcher, opts));
}, undefined, (error) => {
triggerUncaughtException(error, true /* fromPromise */);
}));
});
if (signal) {
if (opts.signal) {
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
signal.addEventListener(
opts.signal.addEventListener(
'abort',
() => root.postRun(),
() => opts.root.postRun(),
{ __proto__: null, once: true, [kResistStopPropagation]: true },
);
}
Expand All @@ -425,14 +428,17 @@ function run(options) {
options = kEmptyObject;
}
let { testNamePatterns, shard } = options;
const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options;
const { concurrency, timeout, signal, files, inspectPort, watch, setup, only } = options;

if (files != null) {
validateArray(files, 'options.files');
}
if (watch != null) {
validateBoolean(watch, 'options.watch');
}
if (only != null) {
validateBoolean(only, 'options.only');
}
if (shard != null) {
validateObject(shard, 'options.shard');
// Avoid re-evaluating the shard object in case it's a getter
Expand Down Expand Up @@ -478,14 +484,15 @@ function run(options) {

let postRun = () => root.postRun();
let filesWatcher;
const opts = { __proto__: null, root, signal, inspectPort, testNamePatterns, only };
if (watch) {
filesWatcher = watchFiles(testFiles, root, inspectPort, signal, testNamePatterns);
filesWatcher = watchFiles(testFiles, opts);
postRun = undefined;
}
const runFiles = () => {
root.harness.bootstrapComplete = true;
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
const subtest = runTestFile(path, filesWatcher, opts);
filesWatcher?.runningSubtests.set(path, subtest);
return subtest;
});
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/test-runner/test_only.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';
const test = require('node:test');

test('this should be skipped');
test.only('this should be executed');
12 changes: 12 additions & 0 deletions test/parallel/test-runner-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
});

it('should pass only to children', async () => {
const result = await run({
files: [join(testFixtures, 'test_only.js')],
only: true
})
.compose(tap)
.toArray();

assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP \'only\' option not set\n');
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
});

it('should emit "test:watch:drained" event on watch mode', async () => {
const controller = new AbortController();
await run({
Expand Down