@@ -3,6 +3,7 @@ import { spawn } from 'child_process';
33import { join } from 'path' ;
44import type { Envelope , EnvelopeItemType , Event , SerializedSession } from '@sentry/types' ;
55import axios from 'axios' ;
6+ import { createBasicSentryServer } from './server' ;
67
78export function assertSentryEvent ( actual : Event , expected : Event ) : void {
89 expect ( actual ) . toMatchObject ( {
@@ -37,6 +38,18 @@ export function cleanupChildProcesses(): void {
3738 }
3839}
3940
41+ /** Promise only resolves when fn returns true */
42+ async function waitFor ( fn : ( ) => boolean , timeout = 10_000 ) : Promise < void > {
43+ let remaining = timeout ;
44+ while ( fn ( ) === false ) {
45+ await new Promise < void > ( resolve => setTimeout ( resolve , 100 ) ) ;
46+ remaining -= 100 ;
47+ if ( remaining < 0 ) {
48+ throw new Error ( 'Timed out waiting for server port' ) ;
49+ }
50+ }
51+ }
52+
4053type Expected =
4154 | {
4255 event : Partial < Event > | ( ( event : Event ) => void ) ;
@@ -48,15 +61,15 @@ type Expected =
4861 session : Partial < SerializedSession > | ( ( event : SerializedSession ) => void ) ;
4962 } ;
5063
51- /** */
64+ /** Creates a test runner */
5265// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
5366export function createRunner ( ...paths : string [ ] ) {
5467 const testPath = join ( ...paths ) ;
5568
5669 const expectedEnvelopes : Expected [ ] = [ ] ;
5770 const flags : string [ ] = [ ] ;
5871 const ignored : EnvelopeItemType [ ] = [ ] ;
59- let hasExited = false ;
72+ let withSentryServer = false ;
6073
6174 if ( testPath . endsWith ( '.ts' ) ) {
6275 flags . push ( '-r' , 'ts-node/register' ) ;
@@ -71,71 +84,36 @@ export function createRunner(...paths: string[]) {
7184 flags . push ( ...args ) ;
7285 return this ;
7386 } ,
87+ withMockSentryServer : function ( ) {
88+ withSentryServer = true ;
89+ return this ;
90+ } ,
7491 ignore : function ( ...types : EnvelopeItemType [ ] ) {
7592 ignored . push ( ...types ) ;
7693 return this ;
7794 } ,
7895 start : function ( done ?: ( e ?: unknown ) => void ) {
7996 const expectedEnvelopeCount = expectedEnvelopes . length ;
80- let envelopeCount = 0 ;
81- let serverPort : number | undefined ;
82-
83- const child = spawn ( 'node' , [ ...flags , testPath ] ) ;
8497
85- CHILD_PROCESSES . add ( child ) ;
86-
87- child . on ( 'close' , ( ) => {
88- hasExited = true ;
89- } ) ;
90-
91- // Pass error to done to end the test quickly
92- child . on ( 'error' , e => {
93- done ?.( e ) ;
94- } ) ;
98+ let envelopeCount = 0 ;
99+ let scenarioServerPort : number | undefined ;
100+ let hasExited = false ;
101+ let child : ReturnType < typeof spawn > | undefined ;
95102
96- async function waitForServerPort ( timeout = 10_000 ) : Promise < void > {
97- let remaining = timeout ;
98- while ( serverPort === undefined ) {
99- await new Promise < void > ( resolve => setTimeout ( resolve , 100 ) ) ;
100- remaining -= 100 ;
101- if ( remaining < 0 ) {
102- throw new Error ( 'Timed out waiting for server port' ) ;
103- }
104- }
103+ function complete ( error ?: Error ) : void {
104+ child ?. kill ( ) ;
105+ done ?.( error ) ;
105106 }
106107
107108 /** Called after each expect callback to check if we're complete */
108109 function expectCallbackCalled ( ) : void {
109110 envelopeCount ++ ;
110111 if ( envelopeCount === expectedEnvelopeCount ) {
111- child . kill ( ) ;
112- done ?.( ) ;
112+ complete ( ) ;
113113 }
114114 }
115115
116- function tryParseLine ( line : string ) : void {
117- // Lines can have leading '[something] [{' which we need to remove
118- const cleanedLine = line . replace ( / ^ .* ?] \[ { " / , '[{"' ) ;
119-
120- // See if we have a port message
121- if ( cleanedLine . startsWith ( '{"port":' ) ) {
122- const { port } = JSON . parse ( cleanedLine ) as { port : number } ;
123- serverPort = port ;
124- return ;
125- }
126-
127- // Skip any lines that don't start with envelope JSON
128- if ( ! cleanedLine . startsWith ( '[{' ) ) {
129- return ;
130- }
131-
132- let envelope : Envelope | undefined ;
133- try {
134- envelope = JSON . parse ( cleanedLine ) as Envelope ;
135- } catch ( _ ) {
136- return ;
137- }
138-
116+ function newEnvelope ( envelope : Envelope ) : void {
139117 for ( const item of envelope [ 1 ] ) {
140118 const envelopeItemType = item [ 0 ] . type ;
141119
@@ -190,22 +168,77 @@ export function createRunner(...paths: string[]) {
190168 expectCallbackCalled ( ) ;
191169 }
192170 } catch ( e ) {
193- done ?. ( e ) ;
171+ complete ( e as Error ) ;
194172 }
195173 }
196174 }
197175
198- let buffer = Buffer . alloc ( 0 ) ;
199- child . stdout . on ( 'data' , ( data : Buffer ) => {
200- // This is horribly memory inefficient but it's only for tests
201- buffer = Buffer . concat ( [ buffer , data ] ) ;
176+ const serverStartup : Promise < number | undefined > = withSentryServer
177+ ? createBasicSentryServer ( newEnvelope )
178+ : Promise . resolve ( undefined ) ;
179+
180+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
181+ serverStartup . then ( mockServerPort => {
182+ const env = mockServerPort
183+ ? { ...process . env , SENTRY_DSN : `http://public@localhost:${ mockServerPort } /1337` }
184+ : process . env ;
185+
186+ // eslint-disable-next-line no-console
187+ if ( process . env . DEBUG ) console . log ( 'starting scenario' , testPath , flags , env . SENTRY_DSN ) ;
188+
189+ child = spawn ( 'node' , [ ...flags , testPath ] , { env } ) ;
190+
191+ CHILD_PROCESSES . add ( child ) ;
192+
193+ child . on ( 'close' , ( ) => {
194+ hasExited = true ;
195+ } ) ;
196+
197+ // Pass error to done to end the test quickly
198+ child . on ( 'error' , e => {
199+ // eslint-disable-next-line no-console
200+ if ( process . env . DEBUG ) console . log ( 'scenario error' , e ) ;
201+ complete ( e ) ;
202+ } ) ;
202203
203- let splitIndex = - 1 ;
204- while ( ( splitIndex = buffer . indexOf ( 0xa ) ) >= 0 ) {
205- const line = buffer . subarray ( 0 , splitIndex ) . toString ( ) ;
206- buffer = Buffer . from ( buffer . subarray ( splitIndex + 1 ) ) ;
207- tryParseLine ( line ) ;
204+ function tryParseEnvelopeFromStdoutLine ( line : string ) : void {
205+ // Lines can have leading '[something] [{' which we need to remove
206+ const cleanedLine = line . replace ( / ^ .* ?] \[ { " / , '[{"' ) ;
207+
208+ // See if we have a port message
209+ if ( cleanedLine . startsWith ( '{"port":' ) ) {
210+ const { port } = JSON . parse ( cleanedLine ) as { port : number } ;
211+ scenarioServerPort = port ;
212+ return ;
213+ }
214+
215+ // Skip any lines that don't start with envelope JSON
216+ if ( ! cleanedLine . startsWith ( '[{' ) ) {
217+ return ;
218+ }
219+
220+ try {
221+ const envelope = JSON . parse ( cleanedLine ) as Envelope ;
222+ newEnvelope ( envelope ) ;
223+ } catch ( _ ) {
224+ //
225+ }
208226 }
227+
228+ let buffer = Buffer . alloc ( 0 ) ;
229+ child . stdout . on ( 'data' , ( data : Buffer ) => {
230+ // This is horribly memory inefficient but it's only for tests
231+ buffer = Buffer . concat ( [ buffer , data ] ) ;
232+
233+ let splitIndex = - 1 ;
234+ while ( ( splitIndex = buffer . indexOf ( 0xa ) ) >= 0 ) {
235+ const line = buffer . subarray ( 0 , splitIndex ) . toString ( ) ;
236+ buffer = Buffer . from ( buffer . subarray ( splitIndex + 1 ) ) ;
237+ // eslint-disable-next-line no-console
238+ if ( process . env . DEBUG ) console . log ( 'line' , line ) ;
239+ tryParseEnvelopeFromStdoutLine ( line ) ;
240+ }
241+ } ) ;
209242 } ) ;
210243
211244 return {
@@ -218,13 +251,13 @@ export function createRunner(...paths: string[]) {
218251 headers : Record < string , string > = { } ,
219252 ) : Promise < T | undefined > {
220253 try {
221- await waitForServerPort ( ) ;
254+ await waitFor ( ( ) => scenarioServerPort !== undefined ) ;
222255 } catch ( e ) {
223- done ?. ( e ) ;
256+ complete ( e as Error ) ;
224257 return undefined ;
225258 }
226259
227- const url = `http://localhost:${ serverPort } ${ path } ` ;
260+ const url = `http://localhost:${ scenarioServerPort } ${ path } ` ;
228261 if ( method === 'get' ) {
229262 return ( await axios . get ( url , { headers } ) ) . data ;
230263 } else {
0 commit comments