1
1
import { subscribeSpyTo } from '@hirez_io/observer-spy' ;
2
2
import { spawn } from 'child_process' ;
3
+ import { sendCtrlC , spawnWithWrapper } from 'ctrlc-wrapper' ;
3
4
import { build } from 'esbuild' ;
4
5
import fs from 'fs' ;
5
6
import { escapeRegExp } from 'lodash' ;
@@ -11,8 +12,14 @@ import { map } from 'rxjs/operators';
11
12
import stringArgv from 'string-argv' ;
12
13
13
14
const isWindows = process . platform === 'win32' ;
14
- const createKillMessage = ( prefix : string ) =>
15
- new RegExp ( escapeRegExp ( prefix ) + ' exited with code ' + ( isWindows ? 1 : '(SIGTERM|143)' ) ) ;
15
+ const createKillMessage = ( prefix : string , signal : 'SIGTERM' | 'SIGINT' ) => {
16
+ const map : Record < string , string | number > = {
17
+ SIGTERM : isWindows ? 1 : '(SIGTERM|143)' ,
18
+ // Could theoretically be anything (e.g. 0) if process has SIGINT handler
19
+ SIGINT : isWindows ? '(3221225786|0)' : '(SIGINT|130|0)' ,
20
+ } ;
21
+ return new RegExp ( escapeRegExp ( prefix ) + ' exited with code ' + map [ signal ] ) ;
22
+ } ;
16
23
17
24
let tmpDir : string ;
18
25
@@ -38,8 +45,9 @@ afterAll(() => {
38
45
* Creates a child process running 'concurrently' with the given args.
39
46
* Returns observables for its combined stdout + stderr output, close events, pid, and stdin stream.
40
47
*/
41
- const run = ( args : string ) => {
42
- const child = spawn ( 'node' , [ path . join ( tmpDir , 'concurrently.js' ) , ...stringArgv ( args ) ] , {
48
+ const run = ( args : string , ctrlcWrapper ?: boolean ) => {
49
+ const spawnFn = ctrlcWrapper ? spawnWithWrapper : spawn ;
50
+ const child = spawnFn ( 'node' , [ path . join ( tmpDir , 'concurrently.js' ) , ...stringArgv ( args ) ] , {
43
51
cwd : __dirname ,
44
52
env : {
45
53
...process . env ,
@@ -94,6 +102,7 @@ const run = (args: string) => {
94
102
} ;
95
103
96
104
return {
105
+ process : child ,
97
106
stdin : child . stdin ,
98
107
pid : child . pid ,
99
108
log,
@@ -160,23 +169,36 @@ describe('exiting conditions', () => {
160
169
} ) ;
161
170
162
171
it ( 'is of success when a SIGINT is sent' , async ( ) => {
163
- const child = run ( '"node fixtures/read-echo.js"' ) ;
172
+ // Windows doesn't support sending signals like on POSIX platforms.
173
+ // However, in a console, processes can be interrupted with CTRL+C (like a SIGINT).
174
+ // This is what we simulate here with the help of a wrapper application.
175
+ const child = run ( '"node fixtures/read-echo.js"' , isWindows ? true : false ) ;
164
176
// Wait for command to have started before sending SIGINT
165
177
child . log . subscribe ( ( line ) => {
166
178
if ( / R E A D I N G / . test ( line ) ) {
167
- process . kill ( child . pid , 'SIGINT' ) ;
179
+ if ( isWindows ) {
180
+ // Instruct the wrapper to send CTRL+C to its child
181
+ sendCtrlC ( child . process ) ;
182
+ } else {
183
+ process . kill ( child . pid , 'SIGINT' ) ;
184
+ }
168
185
}
169
186
} ) ;
187
+ const lines = await child . getLogLines ( ) ;
170
188
const exit = await child . exit ;
171
189
172
- // TODO
173
- // Windows doesn't support sending signals like on POSIX platforms.
174
- // In a console, processes can be interrupted with CTRL+C (SIGINT).
175
- // However, there is no easy way to simulate this event.
176
- // Calling 'process.kill' on a process in Windows means it
177
- // is getting killed forcefully and abruptly (similar to SIGKILL),
178
- // which then results in the exit code of '1'.
179
- expect ( exit . code ) . toBe ( isWindows ? 1 : 0 ) ;
190
+ expect ( exit . code ) . toBe ( 0 ) ;
191
+ expect ( lines ) . toContainEqual (
192
+ expect . stringMatching (
193
+ createKillMessage (
194
+ isWindows
195
+ ? // '^C' is echoed by read-echo.js (also happens without the wrapper)
196
+ '[0] ^Cnode fixtures/read-echo.js'
197
+ : '[0] node fixtures/read-echo.js' ,
198
+ 'SIGINT'
199
+ )
200
+ )
201
+ ) ;
180
202
} ) ;
181
203
} ) ;
182
204
@@ -281,7 +303,9 @@ describe('--kill-others', () => {
281
303
expect . stringContaining ( 'Sending SIGTERM to other processes' )
282
304
) ;
283
305
expect ( lines ) . toContainEqual (
284
- expect . stringMatching ( createKillMessage ( '[0] node fixtures/sleep.mjs 10' ) )
306
+ expect . stringMatching (
307
+ createKillMessage ( '[0] node fixtures/sleep.mjs 10' , 'SIGTERM' )
308
+ )
285
309
) ;
286
310
} ) ;
287
311
} ) ;
@@ -294,7 +318,7 @@ describe('--kill-others', () => {
294
318
expect ( lines ) . toContainEqual ( expect . stringContaining ( '[1] exit 1 exited with code 1' ) ) ;
295
319
expect ( lines ) . toContainEqual ( expect . stringContaining ( 'Sending SIGTERM to other processes' ) ) ;
296
320
expect ( lines ) . toContainEqual (
297
- expect . stringMatching ( createKillMessage ( '[0] node fixtures/sleep.mjs 10' ) )
321
+ expect . stringMatching ( createKillMessage ( '[0] node fixtures/sleep.mjs 10' , 'SIGTERM' ) )
298
322
) ;
299
323
} ) ;
300
324
} ) ;
@@ -319,7 +343,7 @@ describe('--kill-others-on-fail', () => {
319
343
expect ( lines ) . toContainEqual ( expect . stringContaining ( '[1] exit 1 exited with code 1' ) ) ;
320
344
expect ( lines ) . toContainEqual ( expect . stringContaining ( 'Sending SIGTERM to other processes' ) ) ;
321
345
expect ( lines ) . toContainEqual (
322
- expect . stringMatching ( createKillMessage ( '[0] node fixtures/sleep.mjs 10' ) )
346
+ expect . stringMatching ( createKillMessage ( '[0] node fixtures/sleep.mjs 10' , 'SIGTERM' ) )
323
347
) ;
324
348
} ) ;
325
349
} ) ;
@@ -359,7 +383,7 @@ describe('--handle-input', () => {
359
383
expect ( exit . code ) . toBeGreaterThan ( 0 ) ;
360
384
expect ( lines ) . toContainEqual ( expect . stringContaining ( '[1] stop' ) ) ;
361
385
expect ( lines ) . toContainEqual (
362
- expect . stringMatching ( createKillMessage ( '[0] node fixtures/read-echo.js' ) )
386
+ expect . stringMatching ( createKillMessage ( '[0] node fixtures/read-echo.js' , 'SIGTERM' ) )
363
387
) ;
364
388
} ) ;
365
389
@@ -376,7 +400,7 @@ describe('--handle-input', () => {
376
400
expect ( exit . code ) . toBeGreaterThan ( 0 ) ;
377
401
expect ( lines ) . toContainEqual ( expect . stringContaining ( '[1] stop' ) ) ;
378
402
expect ( lines ) . toContainEqual (
379
- expect . stringMatching ( createKillMessage ( '[0] node fixtures/read-echo.js' ) )
403
+ expect . stringMatching ( createKillMessage ( '[0] node fixtures/read-echo.js' , 'SIGTERM' ) )
380
404
) ;
381
405
} ) ;
382
406
} ) ;
0 commit comments