Skip to content

Commit 033d0bb

Browse files
committed
test_runner: emit test:watch:drained event
PR-URL: #48259 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 618a9e1 commit 033d0bb

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

doc/api/test.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,10 @@ This event is only emitted if `--test` flag is passed.
14731473
Emitted when a running test writes to `stdout`.
14741474
This event is only emitted if `--test` flag is passed.
14751475

1476+
### Event: `'test:watch:drained'`
1477+
1478+
Emitted when no more tests are queued for execution in watch mode.
1479+
14761480
## Class: `TestContext`
14771481

14781482
<!-- YAML

lib/internal/test_runner/runner.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -341,25 +341,25 @@ class FileTest extends Test {
341341
}
342342
}
343343

344-
const runningProcesses = new SafeMap();
345-
const runningSubtests = new SafeMap();
346-
347344
function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
345+
const watchMode = filesWatcher != null;
348346
const subtest = root.createSubtest(FileTest, path, async (t) => {
349347
const args = getRunArgs({ path, inspectPort, testNamePatterns });
350348
const stdio = ['pipe', 'pipe', 'pipe'];
351349
const env = { ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
352-
if (filesWatcher) {
350+
if (watchMode) {
353351
stdio.push('ipc');
354352
env.WATCH_REPORT_DEPENDENCIES = '1';
355353
}
356354

357355
const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8', env, stdio });
358-
runningProcesses.set(path, child);
356+
if (watchMode) {
357+
filesWatcher.runningProcesses.set(path, child);
358+
filesWatcher.watcher.watchChildProcessModules(child, path);
359+
}
359360

360361
let err;
361362

362-
filesWatcher?.watchChildProcessModules(child, path);
363363

364364
child.on('error', (error) => {
365365
err = error;
@@ -391,8 +391,14 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
391391
finished(child.stdout, { signal: t.signal }),
392392
]);
393393

394-
runningProcesses.delete(path);
395-
runningSubtests.delete(path);
394+
if (watchMode) {
395+
filesWatcher.runningProcesses.delete(path);
396+
filesWatcher.runningSubtests.delete(path);
397+
if (filesWatcher.runningSubtests.size === 0) {
398+
root.reporter[kEmitMessage]('test:watch:drained');
399+
}
400+
}
401+
396402
if (code !== 0 || signal !== null) {
397403
if (!err) {
398404
const failureType = subtest.failedSubtests ? kSubtestsFailed : kTestCodeFailure;
@@ -413,9 +419,13 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
413419
}
414420

415421
function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
416-
const filesWatcher = new FilesWatcher({ throttle: 500, mode: 'filter', signal });
417-
filesWatcher.on('changed', ({ owners }) => {
418-
filesWatcher.unfilterFilesOwnedBy(owners);
422+
const runningProcesses = new SafeMap();
423+
const runningSubtests = new SafeMap();
424+
const watcher = new FilesWatcher({ throttle: 500, mode: 'filter', signal });
425+
const filesWatcher = { __proto__: null, watcher, runningProcesses, runningSubtests };
426+
427+
watcher.on('changed', ({ owners }) => {
428+
watcher.unfilterFilesOwnedBy(owners);
419429
PromisePrototypeThen(SafePromiseAllReturnVoid(testFiles, async (file) => {
420430
if (!owners.has(file)) {
421431
return;
@@ -436,6 +446,7 @@ function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
436446
}));
437447
});
438448
signal?.addEventListener('abort', () => root.postRun(), { __proto__: null, once: true });
449+
439450
return filesWatcher;
440451
}
441452

@@ -485,7 +496,7 @@ function run(options) {
485496
root.harness.bootstrapComplete = true;
486497
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
487498
const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
488-
runningSubtests.set(path, subtest);
499+
filesWatcher?.runningSubtests.set(path, subtest);
489500
return subtest;
490501
});
491502
};

test/parallel/test-runner-run.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,14 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
132132
.toArray();
133133
assert.deepStrictEqual(result, ['this should pass']);
134134
});
135+
136+
it('should emit "test:watch:drained" event on watch mode', async () => {
137+
const controller = new AbortController();
138+
await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal })
139+
.on('data', function({ type }) {
140+
if (type === 'test:watch:drained') {
141+
controller.abort();
142+
}
143+
});
144+
});
135145
});

0 commit comments

Comments
 (0)