bun test: exit 1 when a process.on('exit') listener throws

RO
2mo ago
+48-0

Problem

  • When bun test runs a file's process.on('exit') listeners and one of them throws (or fails an expect or assert), the error is printed but the run still exits 0. bun run exits 1 for the same listener, and node's test runner reports the file as failed.
  • bun test runs exit listeners only for files that used a node:test API, or under BUN_TEST_DRAIN_EVENT_LOOP=1 (#38442). The cause is in TestCommand::exec (src/runtime/cli/test_command.rs:2674): it computes the exit code, calls vm.on_exit(), then calls global_exit() with the precomputed code. The listener's error goes through VirtualMachine::uncaught_exception, which only bumps unhandled_error_counter under bun test.

Fix

  • Read vm.unhandled_error_counter before on_exit() and again after. If it grew, set exit_handler.exit_code = 1.
  • This is the one place that owns the test run's exit code. The error is already printed by the existing handler. No summary line changes.
  • Verified: test/cli/test/bun-test.test.ts (two new tests in the process.on('exit') listeners block: a node:test file and a bun:test file under BUN_TEST_DRAIN_EVENT_LOOP=1). Both exit 0 on main. Also ran test/js/node/test_runner/node-test.test.ts and the rest of bun-test.test.ts.

Background

  • ExitHandler::dispatch_on_exit (src/jsc/VirtualMachine.rs:683) emits exit on the process object. The emitter reports a listener's throw through Bun__reportUnhandledError, which under bun test counts it and prints it, but does not touch the exit code.
  • For plain bun:test files the listeners are not dispatched at all (#38442 matched jest and vitest), so this change does not affect them.
  • --parallel workers exit through their own path in src/runtime/cli/test/parallel/runner.rs with a fixed code of 0 and report results over IPC. This PR does not change that path.
Notes

Repro on main:

// exit.test.ts
import { test } from "node:test";
import assert from "node:assert";
test("a", () => {
  process.on("exit", code => { assert.strictEqual(1, 2); });
});

bun test exit.test.ts prints the AssertionError and exits 0. node --test exit.test.ts reports the file as not ok with exit code 1.

A bun:test file with the same listener never runs it (since #38442), so there is nothing to fail there. Under BUN_TEST_DRAIN_EVENT_LOOP=1 the listener runs and the same fix applies.


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/test/bun-test.test.ts