From 0289a023a5c94a1426948139649b5f87ab712b0e Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Tue, 14 May 2024 11:31:02 +0300 Subject: [PATCH] test_runner: fix watch mode race condition PR-URL: https://github.com/nodejs/node/pull/52954 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Marco Ippolito Reviewed-By: Chemi Atlow --- lib/internal/test_runner/runner.js | 18 ++++++++++++------ test/parallel/test-runner-watch-mode.mjs | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 463729d67d1b75..45c15aaf310378 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -35,7 +35,6 @@ 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: { @@ -420,10 +419,16 @@ function runTestFile(path, filesWatcher, opts) { if (watchMode) { filesWatcher.runningProcesses.delete(path); filesWatcher.runningSubtests.delete(path); - if (filesWatcher.runningSubtests.size === 0) { - opts.root.reporter[kEmitMessage]('test:watch:drained'); - queueMicrotask(() => opts.root.postRun()); - } + (async () => { + try { + await subTestEnded; + } finally { + if (filesWatcher.runningSubtests.size === 0) { + opts.root.reporter[kEmitMessage]('test:watch:drained'); + opts.root.postRun(); + } + } + })(); } if (code !== 0 || signal !== null) { @@ -442,7 +447,8 @@ function runTestFile(path, filesWatcher, opts) { throw err; } }); - return subtest.start(); + const subTestEnded = subtest.start(); + return subTestEnded; } function watchFiles(testFiles, opts) { diff --git a/test/parallel/test-runner-watch-mode.mjs b/test/parallel/test-runner-watch-mode.mjs index 5ec847330c2c2c..b210dab1e97142 100644 --- a/test/parallel/test-runner-watch-mode.mjs +++ b/test/parallel/test-runner-watch-mode.mjs @@ -1,6 +1,7 @@ // Flags: --expose-internals import * as common from '../common/index.mjs'; import { describe, it } from 'node:test'; +import assert from 'node:assert'; import { spawn } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import util from 'internal/util'; @@ -36,21 +37,33 @@ async function testWatch({ fileToUpdate, file }) { ['--watch', '--test', file ? fixturePaths[file] : undefined].filter(Boolean), { encoding: 'utf8', stdio: 'pipe', cwd: tmpdir.path }); let stdout = ''; + let currentRun = ''; + const runs = []; child.stdout.on('data', (data) => { stdout += data.toString(); - const testRuns = stdout.match(/ - test has ran/g); + currentRun += data.toString(); + const testRuns = stdout.match(/# duration_ms\s\d+/g); if (testRuns?.length >= 1) ran1.resolve(); if (testRuns?.length >= 2) ran2.resolve(); }); await ran1.promise; + runs.push(currentRun); + currentRun = ''; const content = fixtureContent[fileToUpdate]; const path = fixturePaths[fileToUpdate]; const interval = setInterval(() => writeFileSync(path, content), common.platformTimeout(1000)); await ran2.promise; + runs.push(currentRun); clearInterval(interval); child.kill(); + for (const run of runs) { + assert.match(run, /# tests 1/); + assert.match(run, /# pass 1/); + assert.match(run, /# fail 0/); + assert.match(run, /# cancelled 0/); + } } describe('test runner watch mode', () => {