@@ -6,16 +6,44 @@ jest.mock('cross-spawn')
66
77const crossEnv = require ( '../' )
88
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 ]
1021
11- process . setMaxListeners ( 20 )
22+ // Enforce checks for leaking process.on() listeners in cross-env
23+ process . setMaxListeners ( 1 )
1224
1325beforeEach ( ( ) => {
1426 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+ } )
1633} )
1734
1835afterEach ( ( ) => {
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+
1947 jest . clearAllMocks ( )
2048 process . exit . mockRestore ( )
2149} )
@@ -130,20 +158,6 @@ test(`does not normalize command arguments on windows`, () => {
130158 )
131159} )
132160
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-
147161test ( `keeps backslashes` , ( ) => {
148162 isWindowsMock . mockReturnValue ( true )
149163 crossEnv ( [ 'echo' , '\\\\\\\\someshare\\\\somefolder' ] )
@@ -157,29 +171,73 @@ test(`keeps backslashes`, () => {
157171 )
158172} )
159173
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+ )
166211} )
167212
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+ )
175230} )
176231
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 ( )
180236 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 )
183241} )
184242
185243function testEnvSetting ( expected , ...envSettings ) {
0 commit comments