diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index d715fa7879eb16..202759bdbfe7ae 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -184,21 +184,26 @@ function setup(root) { __proto__: null, allowTestsToRun: false, bootstrapComplete: false, + watching: false, coverage: FunctionPrototypeBind(collectCoverage, null, root, coverage), - counters: { - __proto__: null, - all: 0, - failed: 0, - passed: 0, - cancelled: 0, - skipped: 0, - todo: 0, - topLevel: 0, - suites: 0, + resetCounters() { + root.harness.counters = { + __proto__: null, + all: 0, + failed: 0, + passed: 0, + cancelled: 0, + skipped: 0, + todo: 0, + topLevel: 0, + suites: 0, + }; }, + counters: null, shouldColorizeTestFiles: false, teardown: exitHandler, }; + root.harness.resetCounters(); root.startTime = hrtime(); return root; } diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 41fbfdab931392..fc540599cf9bc5 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -36,6 +36,7 @@ const { createInterface } = require('readline'); const { deserializeError } = require('internal/error_serdes'); const { Buffer } = require('buffer'); const { FilesWatcher } = require('internal/watch_mode/files_watcher'); +const { queueMicrotask } = require('internal/process/task_queues'); const console = require('internal/console/global'); const { codes: { @@ -378,6 +379,7 @@ function runTestFile(path, filesWatcher, opts) { filesWatcher.runningSubtests.delete(path); if (filesWatcher.runningSubtests.size === 0) { opts.root.reporter[kEmitMessage]('test:watch:drained'); + queueMicrotask(() => opts.root.postRun()); } } @@ -405,6 +407,7 @@ function watchFiles(testFiles, opts) { const runningSubtests = new SafeMap(); const watcher = new FilesWatcher({ __proto__: null, debounce: 200, mode: 'filter', signal: opts.signal }); const filesWatcher = { __proto__: null, watcher, runningProcesses, runningSubtests }; + opts.root.harness.watching = true; watcher.on('changed', ({ owners }) => { watcher.unfilterFilesOwnedBy(owners); @@ -431,7 +434,10 @@ function watchFiles(testFiles, opts) { kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation; opts.signal.addEventListener( 'abort', - () => opts.root.postRun(), + () => { + opts.root.harness.watching = false; + opts.root.postRun(); + }, { __proto__: null, once: true, [kResistStopPropagation]: true }, ); } diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index e2576f30b19fab..b12febe824d2d7 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -847,7 +847,12 @@ class Test extends AsyncResource { reporter.coverage(nesting, loc, coverage); } - reporter.end(); + if (harness.watching) { + this.reported = false; + harness.resetCounters(); + } else { + reporter.end(); + } } } diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index 2e8a022ea3dd78..8b050274a3f4b7 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -195,9 +195,16 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { signal: controller.signal, }) .compose(async function* (source) { + let waitForCancel = 2; for await (const chunk of source) { - if (chunk.type === 'test:pass') { + if (chunk.type === 'test:watch:drained' || + (chunk.type === 'test:diagnostic' && chunk.data.message.startsWith('duration_ms'))) { + waitForCancel--; + } + if (waitForCancel === 0) { controller.abort(); + } + if (chunk.type === 'test:pass') { yield chunk.data.name; } }