11import { setTimeout } from 'node:timers/promises' ;
2+ import { on } from 'node:events' ;
23import {
34 describe , test , onFinish , onTestFail , expect ,
45} from 'manten' ;
@@ -42,6 +43,58 @@ const isProcessAlive = (pid: number) => {
4243 return false ;
4344} ;
4445
46+ const isAbortError = (
47+ error : unknown ,
48+ ) => error instanceof Error && error . name === 'AbortError' ;
49+
50+ const waitForSignalRelay = async (
51+ stdoutStream : NodeJS . ReadableStream ,
52+ signalName : string ,
53+ sendSignal : ( ) => void ,
54+ ) => {
55+ let stdout = '' ;
56+ let sentFirstSignal = false ;
57+ let sentSecondSignal = isWindows ;
58+
59+ try {
60+ for await ( const [ data ] of on ( stdoutStream , 'data' , {
61+ close : [ 'end' , 'close' ] ,
62+ signal : AbortSignal . timeout ( 10_000 ) ,
63+ } ) ) {
64+ stdout += data . toString ( ) ;
65+
66+ if ( ! sentFirstSignal && stdout . includes ( 'READY' ) ) {
67+ sentFirstSignal = true ;
68+ sendSignal ( ) ;
69+
70+ if ( sentSecondSignal ) {
71+ return stdout ;
72+ }
73+ }
74+
75+ if ( ! sentSecondSignal && stdout . includes ( `${ signalName } PRESS AGAIN` ) ) {
76+ sentSecondSignal = true ;
77+ sendSignal ( ) ;
78+ return stdout ;
79+ }
80+ }
81+ } catch ( error ) {
82+ if ( isAbortError ( error ) ) {
83+ throw Object . assign (
84+ new Error ( `Timed out waiting for ${ signalName } relay` ) ,
85+ { stdout } ,
86+ ) ;
87+ }
88+
89+ throw error ;
90+ }
91+
92+ throw Object . assign (
93+ new Error ( `Process exited before ${ signalName } relay completed` ) ,
94+ { stdout } ,
95+ ) ;
96+ } ;
97+
4598export const cli = ( node : NodeApis ) => describe ( 'CLI' , ( ) => {
4699 const { tsx } = node ;
47100 describe ( 'argv' , async ( ) => {
@@ -183,10 +236,10 @@ export const cli = (node: NodeApis) => describe('CLI', () => {
183236
184237 for (const name of signals) {
185238 process.once(name, () => {
186- console.log(name, 'PRESS AGAIN');
187239 process.once(name, () => {
188240 process.exit(200);
189241 });
242+ console.log(name, 'PRESS AGAIN');
190243 });
191244 }
192245
@@ -234,25 +287,27 @@ export const cli = (node: NodeApis) => describe('CLI', () => {
234287 fixture . getPath ( 'catch-signals.js' ) ,
235288 ] ) ;
236289
237- tsxProcess . stdout ! . once ( 'data' , ( ) => {
238- tsxProcess . kill ( signal , {
239- forceKillAfterTimeout : false ,
240- } ) ;
290+ let stdout = '' ;
291+ let tsxProcessResolved : Awaited < typeof tsxProcess > | undefined ;
241292
242- tsxProcess . stdout ! . once ( 'data' , ( ) => {
243- tsxProcess . kill ( signal , {
244- forceKillAfterTimeout : false ,
245- } ) ;
293+ onTestFail ( ( ) => {
294+ console . log ( {
295+ tsxProcessResolved ,
296+ stdout ,
246297 } ) ;
247298 } ) ;
248299
249- const tsxProcessResolved = await tsxProcess ;
300+ stdout = await waitForSignalRelay (
301+ tsxProcess . stdout ! ,
302+ signal ,
303+ ( ) => tsxProcess . kill ( signal , {
304+ forceKillAfterTimeout : false ,
305+ } ) ,
306+ ) ;
250307
251- onTestFail ( ( ) => {
252- console . log ( tsxProcessResolved ) ;
253- } ) ;
308+ tsxProcessResolved = await tsxProcess ;
254309
255- if ( process . platform === 'win32' ) {
310+ if ( isWindows ) {
256311 /**
257312 * Windows doesn't support sending signals to processes.
258313 * https://nodejs.org/api/process.html#signal-events
@@ -261,10 +316,10 @@ export const cli = (node: NodeApis) => describe('CLI', () => {
261316 * of the target process, and afterwards, subprocess will report that the process
262317 * was terminated by signal.
263318 */
264- expect ( tsxProcessResolved . stdout ) . toBe ( 'READY' ) ;
319+ expect ( stdout . trim ( ) ) . toBe ( 'READY' ) ;
265320 } else {
266321 expect ( tsxProcessResolved . exitCode ) . toBe ( 200 ) ;
267- expectMatchInOrder ( tsxProcessResolved . stdout , [
322+ expectMatchInOrder ( stdout , [
268323 'READY\n' ,
269324 `${ signal } PRESS AGAIN` ,
270325 ] ) ;
0 commit comments