@@ -6,16 +6,44 @@ jest.mock('cross-spawn')
6
6
7
7
const crossEnv = require ( '../' )
8
8
9
- const getSpawned = ( call = 0 ) => crossSpawnMock . spawn . mock . results [ call ] . value
9
+ /*
10
+ Each test should spawn at most once.
11
+ */
12
+ const getSpawned = ( ) => {
13
+ if ( crossSpawnMock . spawn . mock . results . length !== 0 ) {
14
+ return crossSpawnMock . spawn . mock . results [ 0 ] . value
15
+ }
16
+
17
+ return undefined
18
+ }
19
+
20
+ const getSpawnedOnExitCallBack = ( ) => getSpawned ( ) . on . mock . calls [ 0 ] [ 1 ]
10
21
11
- process . setMaxListeners ( 20 )
22
+ // Enforce checks for leaking process.on() listeners in cross-env
23
+ process . setMaxListeners ( 1 )
12
24
13
25
beforeEach ( ( ) => {
14
26
jest . spyOn ( process , 'exit' ) . mockImplementation ( ( ) => { } )
15
- crossSpawnMock . spawn . mockReturnValue ( { on : jest . fn ( ) , kill : jest . fn ( ) } )
27
+ jest . spyOn ( process , 'kill' ) . mockImplementation ( ( ) => { } )
28
+ crossSpawnMock . spawn . mockReturnValue ( {
29
+ pid : 42 ,
30
+ on : jest . fn ( ) ,
31
+ kill : jest . fn ( ) ,
32
+ } )
16
33
} )
17
34
18
35
afterEach ( ( ) => {
36
+ // stop tests from leaking process.on() listeners in cross-env
37
+ const spawned = getSpawned ( )
38
+ if ( spawned ) {
39
+ const spawnExitCallback = getSpawnedOnExitCallBack ( )
40
+ const signal = 'SIGTEST'
41
+ const exitCode = null
42
+ spawnExitCallback ( exitCode , signal )
43
+
44
+ process . removeAllListeners ( 'exit' )
45
+ }
46
+
19
47
jest . clearAllMocks ( )
20
48
process . exit . mockRestore ( )
21
49
} )
@@ -130,20 +158,6 @@ test(`does not normalize command arguments on windows`, () => {
130
158
)
131
159
} )
132
160
133
- test ( `propagates kill signals` , ( ) => {
134
- testEnvSetting ( { FOO_ENV : 'foo=bar' } , 'FOO_ENV="foo=bar"' )
135
-
136
- process . emit ( 'SIGTERM' )
137
- process . emit ( 'SIGINT' )
138
- process . emit ( 'SIGHUP' )
139
- process . emit ( 'SIGBREAK' )
140
- const spawned = getSpawned ( )
141
- expect ( spawned . kill ) . toHaveBeenCalledWith ( 'SIGTERM' )
142
- expect ( spawned . kill ) . toHaveBeenCalledWith ( 'SIGINT' )
143
- expect ( spawned . kill ) . toHaveBeenCalledWith ( 'SIGHUP' )
144
- expect ( spawned . kill ) . toHaveBeenCalledWith ( 'SIGBREAK' )
145
- } )
146
-
147
161
test ( `keeps backslashes` , ( ) => {
148
162
isWindowsMock . mockReturnValue ( true )
149
163
crossEnv ( [ 'echo' , '\\\\\\\\someshare\\\\somefolder' ] )
@@ -157,29 +171,73 @@ test(`keeps backslashes`, () => {
157
171
)
158
172
} )
159
173
160
- test ( `propagates unhandled exit signal` , ( ) => {
161
- const { spawned} = testEnvSetting ( { FOO_ENV : 'foo=bar' } , 'FOO_ENV="foo=bar"' )
162
- const spawnExitCallback = spawned . on . mock . calls [ 0 ] [ 1 ]
163
- const spawnExitCode = null
164
- spawnExitCallback ( spawnExitCode )
165
- expect ( process . exit ) . toHaveBeenCalledWith ( 1 )
174
+ describe ( `cross-env delegates signals to spawn` , ( ) => {
175
+ test ( `SIGINT is not delegated` , ( ) => {
176
+ const signal = 'SIGINT'
177
+
178
+ crossEnv ( [ 'echo' , 'hello world' ] )
179
+ const spawnExitCallback = getSpawnedOnExitCallBack ( )
180
+ const exitCode = null
181
+ const parentProcessId = expect . any ( Number )
182
+
183
+ // Parent receives signal
184
+ // SIGINT is sent to all processes in group, no need to delegated.
185
+ process . emit ( signal )
186
+ expect ( process . kill ) . not . toHaveBeenCalled ( )
187
+ // child handles signal and 'exits'
188
+ spawnExitCallback ( exitCode , signal )
189
+ expect ( process . kill ) . toHaveBeenCalledTimes ( 1 )
190
+ expect ( process . kill ) . toHaveBeenCalledWith ( parentProcessId , signal )
191
+ } )
192
+
193
+ test . each ( [ 'SIGTERM' , 'SIGBREAK' , 'SIGHUP' , 'SIGQUIT' ] ) (
194
+ `delegates %s` ,
195
+ signal => {
196
+ crossEnv ( [ 'echo' , 'hello world' ] )
197
+ const spawnExitCallback = getSpawnedOnExitCallBack ( )
198
+ const exitCode = null
199
+ const parentProcessId = expect . any ( Number )
200
+
201
+ // Parent receives signal
202
+ process . emit ( signal )
203
+ expect ( process . kill ) . toHaveBeenCalledTimes ( 1 )
204
+ expect ( process . kill ) . toHaveBeenCalledWith ( 42 , signal )
205
+ // Parent delegates signal to child, child handles signal and 'exits'
206
+ spawnExitCallback ( exitCode , signal )
207
+ expect ( process . kill ) . toHaveBeenCalledTimes ( 2 )
208
+ expect ( process . kill ) . toHaveBeenCalledWith ( parentProcessId , signal )
209
+ } ,
210
+ )
166
211
} )
167
212
168
- test ( `exits cleanly with SIGINT with a null exit code` , ( ) => {
169
- const { spawned} = testEnvSetting ( { FOO_ENV : 'foo=bar' } , 'FOO_ENV="foo=bar"' )
170
- const spawnExitCallback = spawned . on . mock . calls [ 0 ] [ 1 ]
171
- const spawnExitCode = null
172
- const spawnExitSignal = 'SIGINT'
173
- spawnExitCallback ( spawnExitCode , spawnExitSignal )
174
- expect ( process . exit ) . toHaveBeenCalledWith ( 0 )
213
+ describe ( `spawn received signal and exits` , ( ) => {
214
+ test . each ( [ 'SIGTERM' , 'SIGINT' , 'SIGBREAK' , 'SIGHUP' , 'SIGQUIT' ] ) (
215
+ `delegates %s` ,
216
+ signal => {
217
+ crossEnv ( [ 'echo' , 'hello world' ] )
218
+
219
+ const spawnExitCallback = getSpawnedOnExitCallBack ( )
220
+ const exitCode = null
221
+ const parentProcessId = expect . any ( Number )
222
+
223
+ // cross-env child.on('exit') re-raises signal, now with no signal handlers
224
+ spawnExitCallback ( exitCode , signal )
225
+ process . emit ( 'exit' , exitCode , signal )
226
+ expect ( process . kill ) . toHaveBeenCalledTimes ( 1 )
227
+ expect ( process . kill ) . toHaveBeenCalledWith ( parentProcessId , signal )
228
+ } ,
229
+ )
175
230
} )
176
231
177
- test ( `propagates regular exit code` , ( ) => {
178
- const { spawned} = testEnvSetting ( { FOO_ENV : 'foo=bar' } , 'FOO_ENV="foo=bar"' )
179
- const spawnExitCallback = spawned . on . mock . calls [ 0 ] [ 1 ]
232
+ test ( `spawn regular exit code` , ( ) => {
233
+ crossEnv ( [ 'echo' , 'hello world' ] )
234
+
235
+ const spawnExitCallback = getSpawnedOnExitCallBack ( )
180
236
const spawnExitCode = 0
181
- spawnExitCallback ( spawnExitCode )
182
- expect ( process . exit ) . toHaveBeenCalledWith ( 0 )
237
+ const spawnExitSignal = null
238
+ spawnExitCallback ( spawnExitCode , spawnExitSignal )
239
+ expect ( process . exit ) . not . toHaveBeenCalled ( )
240
+ expect ( process . exitCode ) . toBe ( 0 )
183
241
} )
184
242
185
243
function testEnvSetting ( expected , ...envSettings ) {
0 commit comments