diff --git a/lib/fork.js b/lib/fork.js index 5ddf294e8..8317774ff 100644 --- a/lib/fork.js +++ b/lib/fork.js @@ -122,11 +122,6 @@ export default function loadFork(file, options, execArgv = process.execArgv) { break; } - case 'ping': { - send({type: 'pong'}); - break; - } - default: { emitStateChange(message.ava); } diff --git a/lib/reporters/default.js b/lib/reporters/default.js index df9dc68e1..a70f24e4e 100644 --- a/lib/reporters/default.js +++ b/lib/reporters/default.js @@ -363,6 +363,7 @@ export default class Reporter { writePendingTests(evt) { for (const [file, testsInFile] of evt.pendingTests) { if (testsInFile.size === 0) { + this.lineWriter.writeLine(`Failed to exit when running ${this.relativeFile(file)}\n`); continue; } diff --git a/lib/run-status.js b/lib/run-status.js index f58c9bcbc..cd80fc847 100644 --- a/lib/run-status.js +++ b/lib/run-status.js @@ -193,6 +193,10 @@ export default class RunStatus extends Emittery { case 'worker-finished': { stats.finishedWorkers++; + if (this.pendingTests.get(event.testFile)?.size === 0) { + this.pendingTests.delete(event.testFile); + } + break; } diff --git a/lib/worker/base.js b/lib/worker/base.js index fd1fee372..d5a483af5 100644 --- a/lib/worker/base.js +++ b/lib/worker/base.js @@ -23,35 +23,26 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs'; const currentlyUnhandled = setUpCurrentlyUnhandled(); let runner; +let forcingExit = false; + +const forceExit = () => { + forcingExit = true; + process.exit(1); +}; + // Override process.exit with an undetectable replacement // to report when it is called from a test (which it should never be). -const {apply} = Reflect; -const realExit = process.exit; - -async function exit(code, forceSync = false) { - const flushing = channel.flush(); - if (!forceSync) { - await flushing; +const handleProcessExit = (target, thisArg, args) => { + if (!forcingExit) { + const error = new Error('Unexpected process.exit()'); + Error.captureStackTrace(error, handleProcessExit); + channel.send({type: 'process-exit', stack: error.stack}); } - apply(realExit, process, [code]); -} - -const handleProcessExit = (fn, receiver, args) => { - const error = new Error('Unexpected process.exit()'); - Error.captureStackTrace(error, handleProcessExit); - channel.send({type: 'process-exit', stack: error.stack}); - - // 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 - // that has already colored outside the lines. - const code = args.length > 0 ? args[0] : undefined; - - // Force a synchronous exit as guaranteed by the real process.exit(). - exit(code, true); + target.apply(thisArg, args); }; -process.exit = new Proxy(realExit, { +process.exit = new Proxy(process.exit, { apply: handleProcessExit, }); @@ -101,7 +92,7 @@ const run = async options => { runner.on('error', error => { channel.send({type: 'internal-error', err: serializeError(error)}); - exit(1); + forceExit(); }); runner.on('finish', async () => { @@ -112,7 +103,7 @@ const run = async options => { } } catch (error) { channel.send({type: 'internal-error', err: serializeError(error)}); - exit(1); + forceExit(); return; } @@ -120,22 +111,27 @@ const run = async options => { await Promise.all(sharedWorkerTeardowns.map(fn => fn())); } catch (error) { channel.send({type: 'uncaught-exception', err: serializeError(error)}); - exit(1); + forceExit(); return; } nowAndTimers.setImmediate(() => { - for (const rejection of currentlyUnhandled()) { + const unhandled = currentlyUnhandled(); + if (unhandled.length === 0) { + return; + } + + for (const rejection of unhandled) { channel.send({type: 'unhandled-rejection', err: serializeError(rejection.reason, {testFile: options.file})}); } - exit(0); + forceExit(); }); }); process.on('uncaughtException', error => { channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})}); - exit(1); + forceExit(); }); // Store value to prevent required modules from modifying it. @@ -248,11 +244,11 @@ const run = async options => { channel.unref(); } else { channel.send({type: 'missing-ava-import'}); - exit(1); + forceExit(); } } catch (error) { channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})}); - exit(1); + forceExit(); } }; diff --git a/lib/worker/channel.cjs b/lib/worker/channel.cjs index 2479f4bcb..3f11a973a 100644 --- a/lib/worker/channel.cjs +++ b/lib/worker/channel.cjs @@ -110,22 +110,6 @@ exports.peerFailed = selectAvaMessage(handle.channel, 'peer-failed'); exports.send = handle.send.bind(handle); exports.unref = handle.unref.bind(handle); -let pendingPings = Promise.resolve(); -async function flush() { - handle.ref(); - const promise = pendingPings.then(async () => { - handle.send({type: 'ping'}); - await selectAvaMessage(handle.channel, 'pong'); - if (promise === pendingPings) { - handle.unref(); - } - }); - pendingPings = promise; - await promise; -} - -exports.flush = flush; - let channelCounter = 0; let messageCounter = 0; diff --git a/test-tap/reporters/default.regular.v18.log b/test-tap/reporters/default.regular.v18.log index 49148cd43..c65476560 100644 --- a/test-tap/reporters/default.regular.v18.log +++ b/test-tap/reporters/default.regular.v18.log @@ -114,6 +114,8 @@ null +---tty-stream-chunk-separator + ✘ unhandled-rejection.cjs exited with a non-zero exit code: 1 ---tty-stream-chunk-separator ─ diff --git a/test-tap/reporters/default.regular.v20.log b/test-tap/reporters/default.regular.v20.log index 49148cd43..c65476560 100644 --- a/test-tap/reporters/default.regular.v20.log +++ b/test-tap/reporters/default.regular.v20.log @@ -114,6 +114,8 @@ null +---tty-stream-chunk-separator + ✘ unhandled-rejection.cjs exited with a non-zero exit code: 1 ---tty-stream-chunk-separator ─ diff --git a/test-tap/reporters/default.regular.v21.log b/test-tap/reporters/default.regular.v21.log index 49148cd43..c65476560 100644 --- a/test-tap/reporters/default.regular.v21.log +++ b/test-tap/reporters/default.regular.v21.log @@ -114,6 +114,8 @@ null +---tty-stream-chunk-separator + ✘ unhandled-rejection.cjs exited with a non-zero exit code: 1 ---tty-stream-chunk-separator ─ diff --git a/test-tap/reporters/tap.regular.v18.log b/test-tap/reporters/tap.regular.v18.log index bf22b1c7f..7686951e4 100644 --- a/test-tap/reporters/tap.regular.v18.log +++ b/test-tap/reporters/tap.regular.v18.log @@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection formatted: 'null' ... ---tty-stream-chunk-separator +not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1 +---tty-stream-chunk-separator 1..21 # tests 20 # pass 6 # skip 1 -# fail 20 +# fail 21 ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.regular.v20.log b/test-tap/reporters/tap.regular.v20.log index bf22b1c7f..7686951e4 100644 --- a/test-tap/reporters/tap.regular.v20.log +++ b/test-tap/reporters/tap.regular.v20.log @@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection formatted: 'null' ... ---tty-stream-chunk-separator +not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1 +---tty-stream-chunk-separator 1..21 # tests 20 # pass 6 # skip 1 -# fail 20 +# fail 21 ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.regular.v21.log b/test-tap/reporters/tap.regular.v21.log index bf22b1c7f..7686951e4 100644 --- a/test-tap/reporters/tap.regular.v21.log +++ b/test-tap/reporters/tap.regular.v21.log @@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection formatted: 'null' ... ---tty-stream-chunk-separator +not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1 +---tty-stream-chunk-separator 1..21 # tests 20 # pass 6 # skip 1 -# fail 20 +# fail 21 ---tty-stream-chunk-separator