1
+ import fs from 'fs/promises' ;
1
2
import * as common from '../common/index.mjs' ;
2
3
import * as fixtures from '../common/fixtures.mjs' ;
3
4
import tmpdir from '../common/tmpdir.js' ;
@@ -34,6 +35,8 @@ async function spawnWithRestarts({
34
35
watchedFile = file ,
35
36
restarts = 1 ,
36
37
isReady,
38
+ spawnOptions,
39
+ returnChild = false
37
40
} ) {
38
41
args ??= [ file ] ;
39
42
const printedArgs = inspect ( args . slice ( args . indexOf ( file ) ) . join ( ' ' ) ) ;
@@ -44,30 +47,36 @@ async function spawnWithRestarts({
44
47
let cancelRestarts ;
45
48
46
49
disableRestart = true ;
47
- const child = spawn ( execPath , [ '--watch' , '--no-warnings' , ...args ] , { encoding : 'utf8' } ) ;
48
- child . stderr . on ( 'data' , ( data ) => {
49
- stderr += data ;
50
- } ) ;
51
- child . stdout . on ( 'data' , async ( data ) => {
52
- if ( data . toString ( ) . includes ( 'Restarting' ) ) {
53
- disableRestart = true ;
54
- }
55
- stdout += data ;
56
- const restartsCount = stdout . match ( new RegExp ( `Restarting ${ printedArgs . replace ( / \\ / g, '\\\\' ) } ` , 'g' ) ) ?. length ?? 0 ;
57
- if ( restarts === 0 || ! isReady ( data . toString ( ) ) ) {
58
- return ;
59
- }
60
- if ( restartsCount >= restarts ) {
61
- cancelRestarts ?. ( ) ;
62
- child . kill ( ) ;
63
- return ;
64
- }
65
- cancelRestarts ??= restart ( watchedFile ) ;
66
- if ( isReady ( data . toString ( ) ) ) {
67
- disableRestart = false ;
68
- }
69
- } ) ;
50
+ const child = spawn ( execPath , [ '--watch' , '--no-warnings' , ...args ] , { encoding : 'utf8' , ...spawnOptions } ) ;
70
51
52
+ if ( ! returnChild ) {
53
+ child . stderr . on ( 'data' , ( data ) => {
54
+ stderr += data ;
55
+ } ) ;
56
+ child . stdout . on ( 'data' , async ( data ) => {
57
+ if ( data . toString ( ) . includes ( 'Restarting' ) ) {
58
+ disableRestart = true ;
59
+ }
60
+ stdout += data ;
61
+ const restartsCount = stdout . match ( new RegExp ( `Restarting ${ printedArgs . replace ( / \\ / g, '\\\\' ) } ` , 'g' ) ) ?. length ?? 0 ;
62
+ if ( restarts === 0 || ! isReady ( data . toString ( ) ) ) {
63
+ return ;
64
+ }
65
+ if ( restartsCount >= restarts ) {
66
+ cancelRestarts ?. ( ) ;
67
+ child . kill ( ) ;
68
+ return ;
69
+ }
70
+ cancelRestarts ??= restart ( watchedFile ) ;
71
+ if ( isReady ( data . toString ( ) ) ) {
72
+ disableRestart = false ;
73
+ }
74
+ } ) ;
75
+ }
76
+ else {
77
+ // this test is doing it's own thing
78
+ return { child } ;
79
+ }
71
80
await once ( child , 'exit' ) ;
72
81
cancelRestarts ?. ( ) ;
73
82
return { stderr, stdout } ;
@@ -248,6 +257,7 @@ describe('watch mode', { concurrency: false, timeout: 60_000 }, () => {
248
257
} ) ;
249
258
} ) ;
250
259
260
+
251
261
// TODO: Remove skip after https://github.com/nodejs/node/pull/45271 lands
252
262
it ( 'should not watch when running an missing file' , {
253
263
skip : ! supportsRecursive
@@ -307,4 +317,70 @@ describe('watch mode', { concurrency: false, timeout: 60_000 }, () => {
307
317
`Completed running ${ inspect ( file ) } ` ,
308
318
] ) ;
309
319
} ) ;
320
+
321
+ it ( 'should pass IPC messages from a spawning parent to the child and back' , async ( ) => {
322
+ const file = createTmpFile ( 'console.log("running");\nprocess.on("message", (message) => {\n if (message === "exit") {\n process.exit(0);\n } else {\n console.log("Received:", message);\n process.send(message);\n }\n})' ) ;
323
+ const { child } = await spawnWithRestarts ( {
324
+ file,
325
+ args : [ file ] ,
326
+ spawnOptions : {
327
+ stdio : [ 'pipe' , 'pipe' , 'pipe' , 'ipc' ] ,
328
+ } ,
329
+ returnChild : true ,
330
+ restarts : 2
331
+ } ) ;
332
+
333
+ let stderr = '' ;
334
+ let stdout = '' ;
335
+
336
+ child . stdout . on ( "data" , data => stdout += data ) ;
337
+ child . stderr . on ( "data" , data => stderr += data ) ;
338
+ async function waitForEcho ( msg ) {
339
+ const receivedPromise = new Promise ( ( resolve ) => {
340
+ const fn = ( message ) => {
341
+ if ( message === msg ) {
342
+ child . off ( "message" , fn ) ;
343
+ resolve ( ) ;
344
+ }
345
+ } ;
346
+ child . on ( "message" , fn ) ;
347
+ } ) ;
348
+ child . send ( msg ) ;
349
+ await receivedPromise ;
350
+ }
351
+ async function waitForText ( text ) {
352
+ const seenPromise = new Promise ( ( resolve ) => {
353
+ const fn = ( data ) => {
354
+ if ( data . toString ( ) . includes ( text ) ) {
355
+ resolve ( ) ;
356
+ child . stdout . off ( "data" , fn ) ;
357
+ }
358
+ }
359
+ child . stdout . on ( "data" , fn ) ;
360
+ } ) ;
361
+ await seenPromise ;
362
+ }
363
+
364
+ await waitForEcho ( "first message" ) ;
365
+ const stopRestarts = restart ( file ) ;
366
+ await waitForText ( "running" ) ;
367
+ stopRestarts ( ) ;
368
+ await waitForEcho ( "second message" ) ;
369
+ const exitedPromise = once ( child , 'exit' ) ;
370
+ child . send ( "exit" ) ;
371
+ await waitForText ( "Completed" ) ;
372
+ child . disconnect ( ) ;
373
+ child . kill ( ) ;
374
+ await exitedPromise ;
375
+ assert . strictEqual ( stderr , '' ) ;
376
+ const lines = stdout . split ( / \r ? \n / ) . filter ( Boolean ) ;
377
+ assert . deepStrictEqual ( lines , [
378
+ 'running' ,
379
+ 'Received: first message' ,
380
+ `Restarting '${ file } '` ,
381
+ 'running' ,
382
+ 'Received: second message' ,
383
+ `Completed running ${ inspect ( file ) } ` ,
384
+ ] ) ;
385
+ } ) ;
310
386
} ) ;
0 commit comments