@@ -7,19 +7,41 @@ import { join } from 'path';
77import { inspect } from 'util' ;
88import { expect } from 'vitest' ;
99
10- /** Promise only resolves when fn returns true */
11- async function waitFor ( fn : ( ) => boolean , timeout = 10_000 , message = 'Timed out waiting' ) : Promise < void > {
12- let remaining = timeout ;
13- while ( fn ( ) === false ) {
14- await new Promise < void > ( resolve => setTimeout ( resolve , 100 ) ) ;
15- remaining -= 100 ;
16- if ( remaining < 0 ) {
17- throw new Error ( message ) ;
18- }
10+ const CLEANUP_STEPS = new Set < ( ) => void > ( ) ;
11+
12+ export function cleanupChildProcesses ( ) : void {
13+ for ( const step of CLEANUP_STEPS ) {
14+ step ( ) ;
1915 }
16+ CLEANUP_STEPS . clear ( ) ;
2017}
2118
22- type VoidFunction = ( ) => void ;
19+ process . on ( 'exit' , cleanupChildProcesses ) ;
20+
21+ function deferredPromise < T = void > (
22+ done ?: ( ) => void ,
23+ ) : { resolve : ( val : T ) => void ; reject : ( reason ?: unknown ) => void ; promise : Promise < T > } {
24+ let resolve ;
25+ let reject ;
26+ const promise = new Promise < T > ( ( res , rej ) => {
27+ resolve = ( val : T ) => {
28+ done ?.( ) ;
29+ res ( val ) ;
30+ } ;
31+ reject = ( reason : Error ) => {
32+ done ?.( ) ;
33+ rej ( reason ) ;
34+ } ;
35+ } ) ;
36+ if ( ! resolve || ! reject ) {
37+ throw new Error ( 'Failed to create deferred promise' ) ;
38+ }
39+ return {
40+ resolve,
41+ reject,
42+ promise,
43+ } ;
44+ }
2345
2446type Expected = Envelope | ( ( envelope : Envelope ) => void ) ;
2547
@@ -41,7 +63,6 @@ export function createRunner(...paths: string[]) {
4163 throw new Error ( `Test scenario not found: ${ testPath } ` ) ;
4264 }
4365
44- const cleanupSteps = new Set < VoidFunction > ( ) ;
4566 const expectedEnvelopes : Expected [ ] = [ ] ;
4667 // By default, we ignore session & sessions
4768 const ignored : Set < EnvelopeItemType > = new Set ( [ 'session' , 'sessions' , 'client_report' ] ) ;
@@ -68,32 +89,18 @@ export function createRunner(...paths: string[]) {
6889 return this ;
6990 } ,
7091 start : function ( ) : StartResult {
71- let isComplete = false ;
72- let completeError : Error | undefined ;
73-
92+ const { resolve, reject, promise : isComplete } = deferredPromise ( cleanupChildProcesses ) ;
7493 const expectedEnvelopeCount = expectedEnvelopes . length ;
7594
7695 let envelopeCount = 0 ;
77- let scenarioServerPort : number | undefined ;
96+ const { resolve : setWorkerPort , promise : workerPortPromise } = deferredPromise < number > ( ) ;
7897 let child : ReturnType < typeof spawn > | undefined ;
7998
80- function complete ( error ?: Error ) : void {
81- if ( isComplete ) {
82- return ;
83- }
84-
85- isComplete = true ;
86- completeError = error || undefined ;
87- for ( const step of cleanupSteps ) {
88- step ( ) ;
89- }
90- }
91-
9299 /** Called after each expect callback to check if we're complete */
93100 function expectCallbackCalled ( ) : void {
94101 envelopeCount ++ ;
95102 if ( envelopeCount === expectedEnvelopeCount ) {
96- complete ( ) ;
103+ resolve ( ) ;
97104 }
98105 }
99106
@@ -121,87 +128,68 @@ export function createRunner(...paths: string[]) {
121128 }
122129 expectCallbackCalled ( ) ;
123130 } catch ( e ) {
124- complete ( e as Error ) ;
131+ reject ( e ) ;
125132 }
126133 }
127134
128135 createBasicSentryServer ( newEnvelope )
129136 . then ( ( [ mockServerPort , mockServerClose ] ) => {
130137 if ( mockServerClose ) {
131- cleanupSteps . add ( ( ) => {
138+ CLEANUP_STEPS . add ( ( ) => {
132139 mockServerClose ( ) ;
133140 } ) ;
134141 }
135142
136- // const env = { ...process.env, ...withEnv, SENTRY_DSN: `http://public@localhost :${mockServerPort}/1337` };
137-
138- const SENTRY_DSN = `http://public@localhost:${ mockServerPort } /1337` ;
139-
140- if ( process . env . DEBUG ) log ( 'starting scenario' , { testPath, SENTRY_DSN } ) ;
141-
142- const wranglerConfigPath = join ( testPath , 'wrangler.jsonc' ) ;
143-
144- child = spawn ( 'wrangler' , [ 'dev' , '--config' , wranglerConfigPath , '--var' , `SENTRY_DSN:${ SENTRY_DSN } ` ] ) ;
143+ if ( process . env . DEBUG ) log ( 'Starting scenario' , testPath ) ;
144+
145+ const stdio : ( 'inherit' | 'ipc' | 'ignore' ) [ ] = process . env . DEBUG
146+ ? [ 'inherit' , 'inherit' , 'inherit' , 'ipc' ]
147+ : [ 'ignore' , 'ignore' , 'ignore' , 'ipc' ] ;
148+
149+ child = spawn (
150+ 'wrangler' ,
151+ [
152+ 'dev' ,
153+ '--config' ,
154+ join ( testPath , 'wrangler.jsonc' ) ,
155+ '--show-interactive-dev-session' ,
156+ 'false' ,
157+ '--var' ,
158+ `SENTRY_DSN:http://public@localhost:${ mockServerPort } /1337` ,
159+ ] ,
160+ { stdio } ,
161+ ) ;
162+
163+ CLEANUP_STEPS . add ( ( ) => {
164+ child ?. kill ( ) ;
165+ } ) ;
145166
146167 child . on ( 'error' , e => {
147168 // eslint-disable-next-line no-console
148169 console . error ( 'Error starting child process:' , e ) ;
149- complete ( e ) ;
170+ reject ( e ) ;
150171 } ) ;
151172
152- cleanupSteps . add ( ( ) => {
153- child ?. kill ( ) ;
154- } ) ;
155-
156- if ( process . env . DEBUG ) {
157- child . stderr ?. on ( 'data' , ( data : Buffer ) => {
158- log ( 'stderr line' , data . toString ( ) ) ;
159- } ) ;
160- }
161-
162- child . stdout ?. on ( 'data' , ( data : Buffer ) => {
163- if ( scenarioServerPort === undefined ) {
164- const line = data . toString ( ) ;
165- const result = line . match ( / R e a d y o n h t t p : \/ \/ l o c a l h o s t : ( \d + ) / ) ;
166- if ( result ?. [ 1 ] ) {
167- scenarioServerPort = parseInt ( result [ 1 ] , 10 ) ;
168- }
169- }
170-
171- if ( process . env . DEBUG ) {
172- log ( 'stdout line' , data . toString ( ) ) ;
173+ child . on ( 'message' , ( message : string ) => {
174+ const msg = JSON . parse ( message ) as { event : string ; port ?: number } ;
175+ if ( msg . event === 'DEV_SERVER_READY' && typeof msg . port === 'number' ) {
176+ setWorkerPort ( msg . port ) ;
177+ if ( process . env . DEBUG ) log ( 'worker ready on port' , msg . port ) ;
173178 }
174179 } ) ;
175-
176- // Pass error to done to end the test quickly
177- child . on ( 'error' , e => {
178- if ( process . env . DEBUG ) log ( 'scenario error' , e ) ;
179- complete ( e ) ;
180- } ) ;
181180 } )
182- . catch ( e => complete ( e ) ) ;
181+ . catch ( e => reject ( e ) ) ;
183182
184183 return {
185184 completed : async function ( ) : Promise < void > {
186- await waitFor ( ( ) => isComplete , 120_000 , 'Timed out waiting for test to complete' ) ;
187-
188- if ( completeError ) {
189- throw completeError ;
190- }
185+ return isComplete ;
191186 } ,
192187 makeRequest : async function < T > (
193188 method : 'get' | 'post' ,
194189 path : string ,
195190 options : { headers ?: Record < string , string > ; data ?: BodyInit ; expectError ?: boolean } = { } ,
196191 ) : Promise < T | undefined > {
197- try {
198- await waitFor ( ( ) => scenarioServerPort !== undefined , 10_000 , 'Timed out waiting for server port' ) ;
199- } catch ( e ) {
200- complete ( e as Error ) ;
201- return ;
202- }
203-
204- const url = `http://localhost:${ scenarioServerPort } ${ path } ` ;
192+ const url = `http://localhost:${ await workerPortPromise } ${ path } ` ;
205193 const body = options . data ;
206194 const headers = options . headers || { } ;
207195 const expectError = options . expectError || false ;
@@ -213,14 +201,14 @@ export function createRunner(...paths: string[]) {
213201
214202 if ( ! res . ok ) {
215203 if ( ! expectError ) {
216- complete ( new Error ( `Expected request to "${ path } " to succeed, but got a ${ res . status } response` ) ) ;
204+ reject ( new Error ( `Expected request to "${ path } " to succeed, but got a ${ res . status } response` ) ) ;
217205 }
218206
219207 return ;
220208 }
221209
222210 if ( expectError ) {
223- complete ( new Error ( `Expected request to "${ path } " to fail, but got a ${ res . status } response` ) ) ;
211+ reject ( new Error ( `Expected request to "${ path } " to fail, but got a ${ res . status } response` ) ) ;
224212 return ;
225213 }
226214
@@ -234,7 +222,7 @@ export function createRunner(...paths: string[]) {
234222 return ;
235223 }
236224
237- complete ( e as Error ) ;
225+ reject ( e ) ;
238226 return ;
239227 }
240228 } ,
0 commit comments