Skip to content

test_runner: drain event loop before force-exit process.exit() - #64857

Open
NeverBetterEnough wants to merge 1 commit into
nodejs:mainfrom
NeverBetterEnough:fix/test-force-exit-concurrency
Open

test_runner: drain event loop before force-exit process.exit()#64857
NeverBetterEnough wants to merge 1 commit into
nodejs:mainfrom
NeverBetterEnough:fix/test-force-exit-concurrency

Conversation

@NeverBetterEnough

Copy link
Copy Markdown

When --test-force-exit is used with --test-concurrency > 1, the parent process can silently lose test verdicts from child processes. This happens because process.exit() fires immediately after reporter destinations close, before the event loop has a chance to drain pending write operations on child-process report streams.

The previous fix (#55099) ensured reporter destination files are flushed before exit, but did not extend the flush guarantee to the child-to-parent report stream layer. With process isolation and concurrency, V8-serialized test-event frames from children can be truncated or dropped before the parent's FileTest parser consumes them.

This change adds a setImmediate tick before process.exit() to allow the event loop one full iteration to drain any remaining write operations on reporter destinations (including child-process stdout pipes).

Changes

  • lib/internal/test_runner/test.js: Added await new Promise((resolve) => setImmediate(resolve)) before process.exit() in the force-exit handler
  • test/parallel/test-runner-force-exit-concurrency.mjs: New regression test (8 files × 10 tests, run 5 times to catch the race)

Reproduction

The issue requires --test-force-exit + --test-concurrency > 1 (process isolation). With many fast test files, the parent silently loses a fraction of test verdicts — both built-in and custom reporters receive fewer events than tests that actually ran, yet the run still exits 0.

Impact

Silent partial results defeat the purpose of a test run. On a production suite (Node v24.18.0, 1351 tests), the parent reported as few as 1168. CI stays green while a meaningful fraction of the suite is unaccounted for.

Fixes: #64833
Refs: #54327, #55099

When --test-force-exit is used with --test-concurrency > 1, the
parent process can silently lose test verdicts from child processes.
This happens because process.exit() fires immediately after reporter
destinations close, before the event loop has a chance to drain
pending write operations on child-process report streams.

The previous fix (nodejs#55099) ensured reporter destination files are
flushed before exit, but did not extend the flush guarantee to
the child-to-parent report stream layer. With process isolation
and concurrency, V8-serialized test-event frames from children
can be truncated or dropped.

Add a setImmediate tick before process.exit() to allow the event
loop to drain any remaining write operations on reporter
destinations (including child-process stdout pipes).

Fixes: nodejs#64833
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/test_runner

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. test_runner Issues and PRs related to the test runner subsystem. labels Jul 31, 2026
@avivkeller

Copy link
Copy Markdown
Member

@NeverBetterEnough Please sign your commit by adding a Signed-off-by: trailer

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.15%. Comparing base (c8e2a82) to head (db862f5).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64857      +/-   ##
==========================================
- Coverage   90.16%   90.15%   -0.01%     
==========================================
  Files         746      746              
  Lines      242760   242766       +6     
  Branches    45765    45757       -8     
==========================================
- Hits       218875   218863      -12     
- Misses      15375    15402      +27     
+ Partials     8510     8501       -9     
Files with missing lines Coverage Δ
lib/internal/test_runner/test.js 97.93% <100.00%> (+<0.01%) ⬆️

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. test_runner Issues and PRs related to the test runner subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_runner: --test-force-exit with concurrency silently loses test verdicts (parent reports fewer tests than ran, exit 0)

3 participants