1- import { fork } from 'child_process ' ;
1+ import * as assert from 'assert ' ;
22import * as CircularJSON from 'circular-json' ;
33import * as debug from 'debug' ;
44import * as Mocha from 'mocha' ;
55import { resolve as pathResolve } from 'path' ;
66
7+ import ProcessPool from './process-pool' ;
78import RunnerMain from './runner' ;
89import TaskManager from './task-manager' ;
910import {
10- removeDebugArgs ,
1111 subprocessParseReviver ,
1212} from './util' ;
1313
14- import { DEBUG_SUBPROCESS , SUITE_OWN_OPTIONS } from '../config' ;
14+ import { SUITE_OWN_OPTIONS } from '../config' ;
1515import {
1616 IRetriedTest ,
1717 ISubprocessOutputMessage ,
@@ -24,6 +24,7 @@ import {
2424const debugLog = debug ( 'mocha-parallel-tests' ) ;
2525
2626export default class MochaWrapper extends Mocha {
27+ private pool = new ProcessPool ( ) ;
2728 private isTypescriptRunMode = false ;
2829 private maxParallel : number | undefined ;
2930 private requires : string [ ] = [ ] ;
@@ -50,6 +51,7 @@ export default class MochaWrapper extends Mocha {
5051
5152 setMaxParallel ( maxParallel : number ) {
5253 this . maxParallel = maxParallel ;
54+ this . pool . setMaxParallel ( maxParallel ) ;
5355 }
5456
5557 enableExitMode ( ) {
@@ -98,6 +100,9 @@ export default class MochaWrapper extends Mocha {
98100
99101 taskManager
100102 . on ( 'taskFinished' , ( testResults : ISubprocessResult ) => {
103+ if ( ! testResults ) {
104+ throw new Error ( 'No output from test' ) ;
105+ }
101106 const {
102107 code,
103108 execTime,
@@ -136,6 +141,7 @@ export default class MochaWrapper extends Mocha {
136141 } ;
137142
138143 runner . emitFinishEvents ( done ) ;
144+ this . pool . destroyAll ( ) ;
139145 } ) ;
140146
141147 return runner ;
@@ -162,89 +168,90 @@ export default class MochaWrapper extends Mocha {
162168 }
163169
164170 private spawnTestProcess ( file : string ) : Promise < ISubprocessResult > {
165- return new Promise ( ( resolve ) => {
166- const nodeFlags : string [ ] = [ ] ;
167- const extension = this . isTypescriptRunMode ? 'ts' : 'js' ;
168- const runnerPath = pathResolve ( __dirname , `../subprocess/runner.${ extension } ` ) ;
171+ return new Promise < ISubprocessResult > ( async ( resolve ) => {
169172 const resolvedFilePath = pathResolve ( file ) ;
170173
171- const forkArgs : string [ ] = [ '--test' , resolvedFilePath ] ;
174+ const testOptions : { [ key : string ] : any } = { test : resolvedFilePath } ;
175+
172176 for ( const option of SUITE_OWN_OPTIONS ) {
173177 const propValue = this . suite [ option ] ( ) ;
174178 // bail is undefined by default, we need to somehow pass its value to the subprocess
175- forkArgs . push ( `-- ${ option } ` , propValue === undefined ? false : propValue ) ;
179+ testOptions [ option ] = propValue === undefined ? false : propValue ;
176180 }
177181
178182 for ( const requirePath of this . requires ) {
179- forkArgs . push ( '-- require' , requirePath ) ;
183+ testOptions . require = requirePath ;
180184 }
181185
182- for ( const compilerPath of this . compilers ) {
183- forkArgs . push ( '--compilers' , compilerPath ) ;
184- }
186+ testOptions . compilers = this . compilers || [ ] ;
185187
186188 if ( this . options . delay ) {
187- forkArgs . push ( '-- delay' ) ;
189+ testOptions . delay = true ;
188190 }
189191
190192 if ( this . options . grep ) {
191- forkArgs . push ( '-- grep' , this . options . grep . toString ( ) ) ;
193+ testOptions . grep = this . options . grep . toString ( ) ;
192194 }
193195
194196 if ( this . exitImmediately ) {
195- forkArgs . push ( '-- exit' ) ;
197+ testOptions . exit = true ;
196198 }
197199
198200 if ( this . options . fullStackTrace ) {
199- forkArgs . push ( '--full-trace' ) ;
201+ testOptions . fullStackTrace = true ;
200202 }
201203
202- const test = fork ( runnerPath , forkArgs , {
203- // otherwise `--inspect-brk` and other params will be passed to subprocess
204- execArgv : process . execArgv . filter ( removeDebugArgs ) ,
205- stdio : [ 'ipc' ] ,
206- } ) ;
207-
208- if ( this . isTypescriptRunMode ) {
209- nodeFlags . push ( '--require' , 'ts-node/register' ) ;
204+ let test ;
205+ try {
206+ test = await this . pool . getOrCreate ( this . isTypescriptRunMode ) ;
207+ } catch ( e ) {
208+ throw e ;
210209 }
211210
212- debugLog ( 'Process spawned. You can run it manually with this command:' ) ;
213- debugLog ( `node ${ nodeFlags . join ( ' ' ) } ${ runnerPath } ${ forkArgs . concat ( [ DEBUG_SUBPROCESS . argument ] ) . join ( ' ' ) } ` ) ;
211+ test . send ( JSON . stringify ( { type : 'start' , testOptions } ) ) ;
214212
215213 const events : Array < ISubprocessOutputMessage | ISubprocessRunnerMessage > = [ ] ;
216214 let syncedSubprocessData : ISubprocessSyncedData | undefined ;
217215 const startedAt = Date . now ( ) ;
218216
219- test . on ( 'message' , function onMessageHandler ( { event, data } ) {
217+ function onMessageHandler ( { event, data } ) {
220218 if ( event === 'sync' ) {
221219 syncedSubprocessData = data ;
220+ } else if ( event === 'end' ) {
221+ clean ( ) ;
222+ resolve ( {
223+ code : data . code || 0 ,
224+ events,
225+ execTime : Date . now ( ) - startedAt ,
226+ file,
227+ syncedSubprocessData,
228+ } ) ;
222229 } else {
230+ assert ( event ) ;
223231 events . push ( {
224232 data,
225233 event,
226234 type : 'runner' ,
227235 } ) ;
228236 }
229- } ) ;
237+ }
230238
231- test . stdout . on ( 'data' , function onStdoutData ( data : Buffer ) {
239+ function onStdoutData ( data : Buffer ) {
232240 events . push ( {
233241 data,
234242 event : undefined ,
235243 type : 'stdout' ,
236244 } ) ;
237- } ) ;
245+ }
238246
239- test . stderr . on ( 'data' , function onStderrData ( data : Buffer ) {
247+ function onStderrData ( data : Buffer ) {
240248 events . push ( {
241249 data,
242250 event : undefined ,
243251 type : 'stderr' ,
244252 } ) ;
245- } ) ;
246-
247- test . on ( 'close' , ( code ) => {
253+ }
254+ function onClose ( code ) {
248255 debugLog ( `Process for ${ file } exited with code ${ code } ` ) ;
249256
250257 resolve ( {
@@ -254,7 +261,21 @@ export default class MochaWrapper extends Mocha {
254261 file,
255262 syncedSubprocessData,
256263 } ) ;
257- } ) ;
264+ }
265+
266+ test . on ( 'message' , onMessageHandler ) ;
267+ test . stdout . on ( 'data' , onStdoutData ) ;
268+ test . stderr . on ( 'data' , onStderrData ) ;
269+ test . on ( 'close' , onClose ) ;
270+
271+ function clean ( ) {
272+ test . removeListener ( 'message' , onMessageHandler ) ;
273+ test . stdout . removeListener ( 'data' , onStdoutData ) ;
274+ test . stderr . removeListener ( 'data' , onStderrData ) ;
275+ test . removeListener ( 'close' , onClose ) ;
276+ test . destroy ( ) ;
277+ return null ;
278+ }
258279 } ) ;
259280 }
260281}
0 commit comments