@@ -23,35 +23,42 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
23
23
const currentlyUnhandled = setUpCurrentlyUnhandled ( ) ;
24
24
let runner ;
25
25
26
+ let failedToExit = false ;
27
+
28
+ const expectExit = code => {
29
+ // Set the exit code, which is just handy.
30
+ process . exitCode = code ;
31
+
32
+ // Note that this timer is unreferenced. It should not keep the event loop
33
+ // open. However if after a second the process is still running, assume
34
+ // something is keeping the event loop busy. Exit forcibly but try to convey
35
+ // the reason to the main process.
36
+ nowAndTimers . setTimeout ( ( ) => {
37
+ failedToExit = true ;
38
+ channel . send ( { type : 'exit-failed' , code} ) ;
39
+ process . exit ( 1 ) ;
40
+ } , 1000 ) . unref ( ) ;
41
+ } ;
42
+
26
43
// Override process.exit with an undetectable replacement
27
44
// 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 ;
45
+ const handleProcessExit = ( target , thisArg , args ) => {
46
+ if ( ! failedToExit ) {
47
+ const error = new Error ( 'Unexpected process.exit()' ) ;
48
+ Error . captureStackTrace ( error , handleProcessExit ) ;
49
+ channel . send ( { type : 'process-exit' , stack : error . stack } ) ;
35
50
}
36
51
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
52
// Make sure to extract the code only from `args` rather than e.g. `Array.prototype`.
46
53
// This level of paranoia is usually unwarranted, but we're dealing with test code
47
54
// that has already colored outside the lines.
48
55
const code = args . length > 0 ? args [ 0 ] : undefined ;
49
56
50
57
// Force a synchronous exit as guaranteed by the real process.exit().
51
- exit ( code , true ) ;
58
+ target . call ( thisArg , code ) ;
52
59
} ;
53
60
54
- process . exit = new Proxy ( realExit , {
61
+ process . exit = new Proxy ( process . exit , {
55
62
apply : handleProcessExit ,
56
63
} ) ;
57
64
@@ -101,7 +108,7 @@ const run = async options => {
101
108
102
109
runner . on ( 'error' , error => {
103
110
channel . send ( { type : 'internal-error' , err : serializeError ( error ) } ) ;
104
- exit ( 1 ) ;
111
+ expectExit ( 1 ) ;
105
112
} ) ;
106
113
107
114
runner . on ( 'finish' , async ( ) => {
@@ -112,30 +119,32 @@ const run = async options => {
112
119
}
113
120
} catch ( error ) {
114
121
channel . send ( { type : 'internal-error' , err : serializeError ( error ) } ) ;
115
- exit ( 1 ) ;
122
+ expectExit ( 1 ) ;
116
123
return ;
117
124
}
118
125
119
126
try {
120
127
await Promise . all ( sharedWorkerTeardowns . map ( fn => fn ( ) ) ) ;
121
128
} catch ( error ) {
122
129
channel . send ( { type : 'uncaught-exception' , err : serializeError ( error ) } ) ;
123
- exit ( 1 ) ;
130
+ expectExit ( 1 ) ;
124
131
return ;
125
132
}
126
133
127
134
nowAndTimers . setImmediate ( ( ) => {
135
+ let exitCode = 0 ;
128
136
for ( const rejection of currentlyUnhandled ( ) ) {
129
137
channel . send ( { type : 'unhandled-rejection' , err : serializeError ( rejection . reason , { testFile : options . file } ) } ) ;
138
+ exitCode = 1 ;
130
139
}
131
140
132
- exit ( 0 ) ;
141
+ expectExit ( exitCode ) ;
133
142
} ) ;
134
143
} ) ;
135
144
136
145
process . on ( 'uncaughtException' , error => {
137
146
channel . send ( { type : 'uncaught-exception' , err : serializeError ( error , { testFile : options . file } ) } ) ;
138
- exit ( 1 ) ;
147
+ expectExit ( 1 ) ;
139
148
} ) ;
140
149
141
150
// Store value to prevent required modules from modifying it.
@@ -248,11 +257,11 @@ const run = async options => {
248
257
channel . unref ( ) ;
249
258
} else {
250
259
channel . send ( { type : 'missing-ava-import' } ) ;
251
- exit ( 1 ) ;
260
+ expectExit ( 1 ) ;
252
261
}
253
262
} catch ( error ) {
254
263
channel . send ( { type : 'uncaught-exception' , err : serializeError ( error , { testFile : options . file } ) } ) ;
255
- exit ( 1 ) ;
264
+ expectExit ( 1 ) ;
256
265
}
257
266
} ;
258
267
0 commit comments