test(ci): log unhandledRejection / uncaughtException in backend test bootstrap (#7663)

Backend tests on develop have a ~22% silent failure rate (mostly Windows,
sometimes Linux) where mocha exits with code 1 mid-suite, producing no
test failure marker, no error, and no Mocha summary. Different exit
points each run.

Root cause discovery is blocked by src/tests/backend/common.ts:33, which
rethrows unhandled Promise rejections as uncaught exceptions but never
logs the reason first. When the rethrow happens between specs, mocha
exits with code 1 and the original rejection is lost - especially on
Windows, where stderr is not always flushed before abrupt exit.

This patch is purely diagnostic: it writes the reason (or stack) to
stderr before rethrowing, and adds a matching uncaughtException handler
for the same purpose. Behavior on success is unchanged. The next CI
failure will surface what is actually rejecting (DirtyDB write?
plugin lifecycle? socket cleanup?), so we can fix the real cause.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
John McLear 2026-05-03 13:42:59 +08:00 committed by GitHub
parent ba28241179
commit a0b85dd8b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -30,7 +30,27 @@ const logLevel = logger.level;
// Mocha doesn't monitor unhandled Promise rejections, so convert them to uncaught exceptions.
// https://github.com/mochajs/mocha/issues/2640
process.on('unhandledRejection', (reason: string) => { throw reason; });
//
// Log via process.stderr.write before throwing: when the rethrown rejection
// kills mocha between specs, the runner exits with code 1 and no summary.
// Without this, the rejection reason is lost (worst on Windows runners,
// where stderr is not always flushed before abrupt exit) and CI shows a
// silent ELIFECYCLE with no clue what rejected.
process.on('unhandledRejection', (reason: any) => {
process.stderr.write(`[backend tests] unhandledRejection: ${
reason && reason.stack ? reason.stack : String(reason)}\n`);
throw reason;
});
// Surface uncaught exceptions for the same reason. Node's default behavior
// (exit non-zero) is preserved by the explicit process.exit below — without
// the handler, Node would write to stderr and exit; with the handler we have
// to do it ourselves.
process.on('uncaughtException', (err: any) => {
process.stderr.write(`[backend tests] uncaughtException: ${
err && err.stack ? err.stack : String(err)}\n`);
process.exit(1);
});
before(async function () {
this.timeout(60000);