From 19bed4240d590ebd551565fc56d4e0236bbf6377 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 16 Aug 2022 01:51:45 -0400 Subject: [PATCH] Create a process-exit message for process.exit() --- lib/reporters/default.js | 19 +++++++++++++++++++ lib/reporters/tap.js | 3 +++ lib/run-status.js | 6 ++++++ lib/watcher.js | 1 + lib/worker/base.js | 2 +- test/helpers/exec.js | 9 +++++++++ test/test-process-exit/test.js | 2 +- 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/reporters/default.js b/lib/reporters/default.js index 8a612deef..d4faaf887 100644 --- a/lib/reporters/default.js +++ b/lib/reporters/default.js @@ -111,6 +111,7 @@ export default class Reporter { this.failures = []; this.internalErrors = []; + this.interrupts = []; this.knownFailures = []; this.lineNumberErrors = []; this.sharedWorkerErrors = []; @@ -193,6 +194,7 @@ export default class Reporter { } case 'interrupt': { + this.interrupts.push(event); this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`)); this.lineWriter.writeLine(''); this.writePendingTests(event); @@ -233,6 +235,23 @@ export default class Reporter { break; } + case 'process-exit': { + this.interrupts.push(event); + + if (event.testFile) { + this.write(colors.error(`${figures.cross} Exiting due to process.exit() when running ${this.relativeFile(event.testFile)}`)); + } else { + this.write(colors.error(`${figures.cross} Exiting due to process.exit()`)); + } + + this.lineWriter.writeLine(colors.stack(event.err.summary)); + this.lineWriter.writeLine(colors.errorStack(event.err.stack)); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + + break; + } + case 'hook-finished': { if (event.logs.length > 0) { this.lineWriter.writeLine(` ${this.prefixTitle(event.testFile, event.title)}`); diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index 51a55cdb6..e2dd9a2b5 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -158,6 +158,9 @@ export default class TapReporter { this.filesWithMissingAvaImports.add(evt.testFile); this.writeCrash(evt, `No tests found in ${this.relativeFile(evt.testFile)}, make sure to import "ava" at the top of your test file`); break; + case 'process-exit': + this.writeCrash(evt); + break; case 'selected-test': if (evt.skip) { this.writeTest(evt, {passed: true, todo: false, skip: true}); diff --git a/lib/run-status.js b/lib/run-status.js index 7d6b26c01..6867cb03f 100644 --- a/lib/run-status.js +++ b/lib/run-status.js @@ -62,6 +62,7 @@ export default class RunStatus extends Emittery { worker.onStateChange(data => this.emitStateChange(data)); } + // eslint-disable-next-line complexity emitStateChange(event) { const {stats} = this; const fileStats = stats.byFile.get(event.testFile); @@ -134,6 +135,10 @@ export default class RunStatus extends Emittery { event.pendingTests = this.pendingTests; this.pendingTests = new Map(); break; + case 'process-exit': + event.pendingTests = this.pendingTests; + this.pendingTests = new Map(); + break; case 'uncaught-exception': stats.uncaughtExceptions++; fileStats.uncaughtExceptions++; @@ -175,6 +180,7 @@ export default class RunStatus extends Emittery { || this.stats.failedHooks > 0 || this.stats.failedTests > 0 || this.stats.failedWorkers > 0 + || this.stats.remainingTests > 0 || this.stats.sharedWorkerErrors > 0 || this.stats.timeouts > 0 || this.stats.uncaughtExceptions > 0 diff --git a/lib/watcher.js b/lib/watcher.js index 58d59e245..00784e315 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -294,6 +294,7 @@ export default class Watcher { switch (evt.type) { case 'hook-failed': case 'internal-error': + case 'process-exit': case 'test-failed': case 'uncaught-exception': case 'unhandled-rejection': diff --git a/lib/worker/base.js b/lib/worker/base.js index be8b79ace..df9de7f7d 100644 --- a/lib/worker/base.js +++ b/lib/worker/base.js @@ -37,7 +37,7 @@ async function exit(code, forceSync = false) { process.exit = new Proxy(realExit, { apply(fn, receiver, args) { - channel.send({type: 'internal-error', err: serializeError('Forced exit error', false, new Error('Unexpected process.exit()'), runner?.file)}); + channel.send({type: 'process-exit', err: serializeError('Forced exit error', false, new Error('Unexpected process.exit()'), runner?.file)}); // Make sure to extract the code only from `args` rather than e.g. `Array.prototype`. // This level of paranoia is usually unwarranted, but we're dealing with test code diff --git a/test/helpers/exec.js b/test/helpers/exec.js index 03bc800ca..68e9605ac 100644 --- a/test/helpers/exec.js +++ b/test/helpers/exec.js @@ -70,6 +70,7 @@ export const fixture = async (args, options = {}) => { failed: [], failedHooks: [], internalErrors: [], + interrupts: [], passed: [], selectedTestCount: 0, sharedWorkerErrors: [], @@ -102,6 +103,14 @@ export const fixture = async (args, options = {}) => { break; } + case 'process-exit': { + const {testFile} = statusEvent; + const statObject = {file: normalizePath(workingDir, testFile)}; + errors.set(statObject, statusEvent.err); + stats.interrupts.push(statObject); + break; + } + case 'selected-test': { stats.selectedTestCount++; if (statusEvent.skip) { diff --git a/test/test-process-exit/test.js b/test/test-process-exit/test.js index 3dfdff1cd..8c3307c8a 100644 --- a/test/test-process-exit/test.js +++ b/test/test-process-exit/test.js @@ -8,6 +8,6 @@ test('process.exit is intercepted', async t => { t.like(result, {timedOut: false, isCanceled: false, killed: false}); t.is(result.stats.selectedTestCount, 3); t.is(result.stats.passed.length, 2); - const error = result.stats.getError(result.stats.internalErrors[0]); + const error = result.stats.getError(result.stats.interrupts[0]); t.is(error.message, 'Unexpected process.exit()'); });