55 */
66
77import assert from 'node:assert' ;
8- import { execFile , spawn , type ChildProcess } from 'node:child_process' ;
8+ import { spawn , type ChildProcess , type SpawnOptions } from 'node:child_process' ;
99import fs from 'node:fs' ;
1010import os from 'node:os' ;
1111import path from 'node:path' ;
12- import { describe , it , type TestContext as NodeTestContext } from 'node:test' ;
12+ import { describe , it } from 'node:test' ;
1313
1414const SERVER_PATH = path . resolve ( 'build/src/main.js' ) ;
1515const WATCHDOG_START_PATTERN = / W a t c h d o g s t a r t e d [ \s \S ] * ?" p i d " : \s * ( \d + ) / ;
@@ -99,126 +99,16 @@ function cleanupTest(ctx: TestContext): void {
9999}
100100
101101describe ( 'Telemetry E2E' , ( ) => {
102- async function runSignalTest ( signal : NodeJS . Signals ) : Promise < void > {
102+ async function runTelemetryTest (
103+ killFn : ( ctx : TestContext ) => void ,
104+ testName : string ,
105+ spawnOptions ?: SpawnOptions ,
106+ ) : Promise < void > {
103107 const ctx : TestContext = {
104- logFile : createLogFilePath ( signal ) ,
108+ logFile : createLogFilePath ( testName ) ,
105109 } ;
106110
107111 try {
108- ctx . process = spawn (
109- process . execPath ,
110- [
111- SERVER_PATH ,
112- `--log-file=${ ctx . logFile } ` ,
113- '--usage-statistics' ,
114- '--headless' ,
115- ] ,
116- { stdio : [ 'pipe' , 'pipe' , 'pipe' ] } ,
117- ) ;
118-
119- const match = await waitForLogPattern (
120- ctx . logFile ,
121- WATCHDOG_START_PATTERN ,
122- ) ;
123- assert . ok ( match , 'Watchdog start log not found' ) ;
124- ctx . watchdogPid = parseInt ( match [ 1 ] , 10 ) ;
125- assert . ok ( ctx . watchdogPid > 0 , 'Invalid watchdog PID' ) ;
126-
127- ctx . process . kill ( signal ) ;
128- await waitForProcessExit ( ctx . watchdogPid ) ;
129-
130- const shutdownMatch = await waitForLogPattern (
131- ctx . logFile ,
132- SHUTDOWN_PATTERN ,
133- 2000 ,
134- ) ;
135- assert . ok ( shutdownMatch , 'server_shutdown not logged' ) ;
136-
137- const deathMatch = await waitForLogPattern (
138- ctx . logFile ,
139- PARENT_DEATH_PATTERN ,
140- 2000 ,
141- ) ;
142- assert . ok ( deathMatch , 'Parent death not detected' ) ;
143- } finally {
144- cleanupTest ( ctx ) ;
145- }
146- }
147-
148- async function runWindowsTaskkillTest ( t : NodeTestContext ) : Promise < void > {
149- if ( process . platform !== 'win32' ) {
150- t . skip ( 'Windows only test' ) ;
151- return ;
152- }
153- const ctx : TestContext = {
154- logFile : createLogFilePath ( 'taskkill' ) ,
155- } ;
156-
157- try {
158- ctx . process = spawn (
159- process . execPath ,
160- [
161- SERVER_PATH ,
162- `--log-file=${ ctx . logFile } ` ,
163- '--usage-statistics' ,
164- '--headless' ,
165- ] ,
166- { stdio : [ 'pipe' , 'pipe' , 'pipe' ] } ,
167- ) ;
168-
169- const match = await waitForLogPattern (
170- ctx . logFile ,
171- WATCHDOG_START_PATTERN ,
172- ) ;
173- assert . ok ( match , 'Watchdog start log not found' ) ;
174- ctx . watchdogPid = parseInt ( match [ 1 ] , 10 ) ;
175- assert . ok ( ctx . watchdogPid > 0 , 'Invalid watchdog PID' ) ;
176-
177- await new Promise < void > ( ( resolve , reject ) => {
178- execFile (
179- 'taskkill' ,
180- [ '/PID' , ctx . process ! . pid ! . toString ( ) , '/T' , '/F' ] ,
181- ( error : unknown ) => {
182- if ( error ) {
183- reject ( error ) ;
184- } else {
185- resolve ( ) ;
186- }
187- } ,
188- ) ;
189- } ) ;
190-
191- await waitForProcessExit ( ctx . watchdogPid ) ;
192-
193- const shutdownMatch = await waitForLogPattern (
194- ctx . logFile ,
195- SHUTDOWN_PATTERN ,
196- 2000 ,
197- ) ;
198- assert . ok ( shutdownMatch , 'server_shutdown not logged' ) ;
199-
200- const deathMatch = await waitForLogPattern (
201- ctx . logFile ,
202- PARENT_DEATH_PATTERN ,
203- 2000 ,
204- ) ;
205- assert . ok ( deathMatch , 'Parent death not detected' ) ;
206- } finally {
207- cleanupTest ( ctx ) ;
208- }
209- }
210-
211- async function runPosixGroupKillTest ( t : NodeTestContext ) : Promise < void > {
212- if ( process . platform === 'win32' ) {
213- t . skip ( 'POSIX only test' ) ;
214- return ;
215- }
216- const ctx : TestContext = {
217- logFile : createLogFilePath ( 'sigterm-group' ) ,
218- } ;
219-
220- try {
221- // Spawn detached to create a new process group
222112 ctx . process = spawn (
223113 process . execPath ,
224114 [
@@ -229,7 +119,7 @@ describe('Telemetry E2E', () => {
229119 ] ,
230120 {
231121 stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
232- detached : true ,
122+ ... spawnOptions ,
233123 } ,
234124 ) ;
235125
@@ -241,8 +131,7 @@ describe('Telemetry E2E', () => {
241131 ctx . watchdogPid = parseInt ( match [ 1 ] , 10 ) ;
242132 assert . ok ( ctx . watchdogPid > 0 , 'Invalid watchdog PID' ) ;
243133
244- // Kill the process group
245- process . kill ( - ctx . process . pid ! , 'SIGTERM' ) ;
134+ killFn ( ctx ) ;
246135 await waitForProcessExit ( ctx . watchdogPid ) ;
247136
248137 const shutdownMatch = await waitForLogPattern (
@@ -263,8 +152,26 @@ describe('Telemetry E2E', () => {
263152 }
264153 }
265154
266- it ( 'handles SIGKILL' , ( ) => runSignalTest ( 'SIGKILL' ) ) ;
267- it ( 'handles SIGTERM' , ( ) => runSignalTest ( 'SIGTERM' ) ) ;
268- it ( 'handles Windows taskkill /T /F' , t => runWindowsTaskkillTest ( t ) ) ;
269- it ( 'handles POSIX process group SIGTERM' , t => runPosixGroupKillTest ( t ) ) ;
155+ it ( 'handles SIGKILL' , ( ) =>
156+ runTelemetryTest ( ctx => {
157+ ctx . process ! . kill ( 'SIGKILL' ) ;
158+ } , 'SIGKILL' ) ) ;
159+
160+ it ( 'handles SIGTERM' , ( ) =>
161+ runTelemetryTest ( ctx => {
162+ ctx . process ! . kill ( 'SIGTERM' ) ;
163+ } , 'SIGTERM' ) ) ;
164+
165+ it (
166+ 'handles POSIX process group SIGTERM' ,
167+ { skip : process . platform === 'win32' } ,
168+ ( ) =>
169+ runTelemetryTest (
170+ ctx => {
171+ process . kill ( - ctx . process ! . pid ! , 'SIGTERM' ) ;
172+ } ,
173+ 'sigterm-group' ,
174+ { detached : true } ,
175+ ) ,
176+ ) ;
270177} ) ;
0 commit comments