Skip to content

Commit a84cc9d

Browse files
mydeaclaude
andauthored
test(node-integration-tests): Surface early scenario exit instead of hanging (#22136)
When a scenario process exits before delivering the envelopes a test expects — e.g. it throws before sending its transaction, and the unhandled rejection exits the process with no envelope — the runner kept waiting until the vitest per-test timeout fired, reporting an opaque `Test timed out in Nms` with **no child output**. The runner's own 120s diagnostic dump never ran because the vitest timeout always beat it. This is why so many Docker/DB integration flakes surface as an uninformative timeout that only passes on retry. Now the runner completes the moment the child closes early, with an error carrying the exit code/signal and how many envelopes arrived vs. were expected (plus a tail of the child's output in node-core, which has no separate log dump). Opaque multi-second timeouts become fast, diagnosable failures across every child-process integration suite. It only fires for envelope-expecting tests that haven't completed: the success path has already called `complete()` (so this is a no-op), server-style (`makeRequest`) tests are killed by `complete()` first, and `ensureNoErrorOutput` / no-envelope tests are unaffected. _Verified locally_: a scenario that throws before sending an envelope now fails in ~0.5s with `Scenario exited (code 1) after 0/1 expected envelope(s), before the test completed.` instead of hanging; normal envelope-then-exit suites (cron), server-style suites (httpIntegration-streamed), and the ANR suite (incl. `should exit`, which expects no envelopes) all still pass. This does not by itself fix a specific flaky test — it makes the next occurrence diagnosable. In particular it unblocks root-causing the cron timeouts (#22112, #21891), whose real cause is currently hidden behind the opaque timeout. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 88e3b32 commit a84cc9d

1 file changed

Lines changed: 20 additions & 1 deletion

File tree

dev-packages/node-integration-tests/utils/runner/createRunner.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,30 @@ export function createRunner(...paths: string[]) {
480480
}
481481
});
482482

483-
child.on('close', () => {
483+
child.on('close', (code, signal) => {
484484
hasExited = true;
485485

486486
if (ensureNoErrorOutput) {
487487
complete();
488+
return;
489+
}
490+
491+
// A scenario that still owes envelopes but has already exited will never deliver them.
492+
// Without this, `completed()` blocks until the vitest test timeout and reports an opaque
493+
// "Test timed out", hiding the real failure (e.g. the scenario threw before sending its
494+
// transaction — an unhandled rejection exits the process with no envelope). Complete with
495+
// the exit status and how far we got so the failure is fast and diagnosable; the captured
496+
// child output is dumped by `completed()`. In the success path `complete()` has already
497+
// run (so `isComplete` short-circuits this), server-style tests are killed by `complete()`
498+
// first, and tests that expect no envelopes drive completion some other way.
499+
if (!isComplete && expectedEnvelopeCount > 0) {
500+
const how = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`;
501+
complete(
502+
new Error(
503+
`Scenario exited (${how}) after ${envelopeCount}/${expectedEnvelopeCount} expected ` +
504+
'envelope(s), before the test completed.',
505+
),
506+
);
488507
}
489508
});
490509

0 commit comments

Comments
 (0)