Skip to content

Commit af5684d

Browse files
authored
Don't force-exit after tests have completed
Fixes #1718. With worker threads, this seems to stop AVA from detecting that the thread has exited, causing a hang. Also remove flush logic implemented in #1722. Let's hope that current Node.js versions are better at flushing IPC before exiting.
1 parent 88e4333 commit af5684d

11 files changed

+47
-55
lines changed

lib/fork.js

-5
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ export default function loadFork(file, options, execArgv = process.execArgv) {
122122
break;
123123
}
124124

125-
case 'ping': {
126-
send({type: 'pong'});
127-
break;
128-
}
129-
130125
default: {
131126
emitStateChange(message.ava);
132127
}

lib/reporters/default.js

+1
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ export default class Reporter {
363363
writePendingTests(evt) {
364364
for (const [file, testsInFile] of evt.pendingTests) {
365365
if (testsInFile.size === 0) {
366+
this.lineWriter.writeLine(`Failed to exit when running ${this.relativeFile(file)}\n`);
366367
continue;
367368
}
368369

lib/run-status.js

+4
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ export default class RunStatus extends Emittery {
193193

194194
case 'worker-finished': {
195195
stats.finishedWorkers++;
196+
if (this.pendingTests.get(event.testFile)?.size === 0) {
197+
this.pendingTests.delete(event.testFile);
198+
}
199+
196200
break;
197201
}
198202

lib/worker/base.js

+27-31
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,26 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
2323
const currentlyUnhandled = setUpCurrentlyUnhandled();
2424
let runner;
2525

26+
let forcingExit = false;
27+
28+
const forceExit = () => {
29+
forcingExit = true;
30+
process.exit(1);
31+
};
32+
2633
// Override process.exit with an undetectable replacement
2734
// to report when it is called from a test (which it should never be).
28-
const {apply} = Reflect;
29-
const realExit = process.exit;
30-
31-
async function exit(code, forceSync = false) {
32-
const flushing = channel.flush();
33-
if (!forceSync) {
34-
await flushing;
35+
const handleProcessExit = (target, thisArg, args) => {
36+
if (!forcingExit) {
37+
const error = new Error('Unexpected process.exit()');
38+
Error.captureStackTrace(error, handleProcessExit);
39+
channel.send({type: 'process-exit', stack: error.stack});
3540
}
3641

37-
apply(realExit, process, [code]);
38-
}
39-
40-
const handleProcessExit = (fn, receiver, args) => {
41-
const error = new Error('Unexpected process.exit()');
42-
Error.captureStackTrace(error, handleProcessExit);
43-
channel.send({type: 'process-exit', stack: error.stack});
44-
45-
// Make sure to extract the code only from `args` rather than e.g. `Array.prototype`.
46-
// This level of paranoia is usually unwarranted, but we're dealing with test code
47-
// that has already colored outside the lines.
48-
const code = args.length > 0 ? args[0] : undefined;
49-
50-
// Force a synchronous exit as guaranteed by the real process.exit().
51-
exit(code, true);
42+
target.apply(thisArg, args);
5243
};
5344

54-
process.exit = new Proxy(realExit, {
45+
process.exit = new Proxy(process.exit, {
5546
apply: handleProcessExit,
5647
});
5748

@@ -101,7 +92,7 @@ const run = async options => {
10192

10293
runner.on('error', error => {
10394
channel.send({type: 'internal-error', err: serializeError(error)});
104-
exit(1);
95+
forceExit();
10596
});
10697

10798
runner.on('finish', async () => {
@@ -112,30 +103,35 @@ const run = async options => {
112103
}
113104
} catch (error) {
114105
channel.send({type: 'internal-error', err: serializeError(error)});
115-
exit(1);
106+
forceExit();
116107
return;
117108
}
118109

119110
try {
120111
await Promise.all(sharedWorkerTeardowns.map(fn => fn()));
121112
} catch (error) {
122113
channel.send({type: 'uncaught-exception', err: serializeError(error)});
123-
exit(1);
114+
forceExit();
124115
return;
125116
}
126117

127118
nowAndTimers.setImmediate(() => {
128-
for (const rejection of currentlyUnhandled()) {
119+
const unhandled = currentlyUnhandled();
120+
if (unhandled.length === 0) {
121+
return;
122+
}
123+
124+
for (const rejection of unhandled) {
129125
channel.send({type: 'unhandled-rejection', err: serializeError(rejection.reason, {testFile: options.file})});
130126
}
131127

132-
exit(0);
128+
forceExit();
133129
});
134130
});
135131

136132
process.on('uncaughtException', error => {
137133
channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
138-
exit(1);
134+
forceExit();
139135
});
140136

141137
// Store value to prevent required modules from modifying it.
@@ -248,11 +244,11 @@ const run = async options => {
248244
channel.unref();
249245
} else {
250246
channel.send({type: 'missing-ava-import'});
251-
exit(1);
247+
forceExit();
252248
}
253249
} catch (error) {
254250
channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
255-
exit(1);
251+
forceExit();
256252
}
257253
};
258254

lib/worker/channel.cjs

-16
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,6 @@ exports.peerFailed = selectAvaMessage(handle.channel, 'peer-failed');
110110
exports.send = handle.send.bind(handle);
111111
exports.unref = handle.unref.bind(handle);
112112

113-
let pendingPings = Promise.resolve();
114-
async function flush() {
115-
handle.ref();
116-
const promise = pendingPings.then(async () => {
117-
handle.send({type: 'ping'});
118-
await selectAvaMessage(handle.channel, 'pong');
119-
if (promise === pendingPings) {
120-
handle.unref();
121-
}
122-
});
123-
pendingPings = promise;
124-
await promise;
125-
}
126-
127-
exports.flush = flush;
128-
129113
let channelCounter = 0;
130114
let messageCounter = 0;
131115

test-tap/reporters/default.regular.v18.log

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114

115115
null
116116

117+
---tty-stream-chunk-separator
118+
✘ unhandled-rejection.cjs exited with a non-zero exit code: 1
117119
---tty-stream-chunk-separator
118120
─
119121

test-tap/reporters/default.regular.v20.log

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114

115115
null
116116

117+
---tty-stream-chunk-separator
118+
✘ unhandled-rejection.cjs exited with a non-zero exit code: 1
117119
---tty-stream-chunk-separator
118120
─
119121

test-tap/reporters/default.regular.v21.log

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114

115115
null
116116

117+
---tty-stream-chunk-separator
118+
✘ unhandled-rejection.cjs exited with a non-zero exit code: 1
117119
---tty-stream-chunk-separator
118120
─
119121

test-tap/reporters/tap.regular.v18.log

+3-1
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection
246246
formatted: 'null'
247247
...
248248
---tty-stream-chunk-separator
249+
not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1
250+
---tty-stream-chunk-separator
249251

250252
1..21
251253
# tests 20
252254
# pass 6
253255
# skip 1
254-
# fail 20
256+
# fail 21
255257

256258
---tty-stream-chunk-separator

test-tap/reporters/tap.regular.v20.log

+3-1
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection
246246
formatted: 'null'
247247
...
248248
---tty-stream-chunk-separator
249+
not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1
250+
---tty-stream-chunk-separator
249251

250252
1..21
251253
# tests 20
252254
# pass 6
253255
# skip 1
254-
# fail 20
256+
# fail 21
255257

256258
---tty-stream-chunk-separator

test-tap/reporters/tap.regular.v21.log

+3-1
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@ not ok 27 - unhandled-rejection
246246
formatted: 'null'
247247
...
248248
---tty-stream-chunk-separator
249+
not ok 28 - unhandled-rejection.cjs exited with a non-zero exit code: 1
250+
---tty-stream-chunk-separator
249251

250252
1..21
251253
# tests 20
252254
# pass 6
253255
# skip 1
254-
# fail 20
256+
# fail 21
255257

256258
---tty-stream-chunk-separator

0 commit comments

Comments
 (0)