@@ -3,6 +3,7 @@ import { spawn } from 'child_process';
3
3
import { join } from 'path' ;
4
4
import type { Envelope , EnvelopeItemType , Event , SerializedSession } from '@sentry/types' ;
5
5
import axios from 'axios' ;
6
+ import { createBasicSentryServer } from './server' ;
6
7
7
8
export function assertSentryEvent ( actual : Event , expected : Event ) : void {
8
9
expect ( actual ) . toMatchObject ( {
@@ -37,6 +38,18 @@ export function cleanupChildProcesses(): void {
37
38
}
38
39
}
39
40
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
+
40
53
type Expected =
41
54
| {
42
55
event : Partial < Event > | ( ( event : Event ) => void ) ;
@@ -48,15 +61,15 @@ type Expected =
48
61
session : Partial < SerializedSession > | ( ( event : SerializedSession ) => void ) ;
49
62
} ;
50
63
51
- /** */
64
+ /** Creates a test runner */
52
65
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
53
66
export function createRunner ( ...paths : string [ ] ) {
54
67
const testPath = join ( ...paths ) ;
55
68
56
69
const expectedEnvelopes : Expected [ ] = [ ] ;
57
70
const flags : string [ ] = [ ] ;
58
71
const ignored : EnvelopeItemType [ ] = [ ] ;
59
- let hasExited = false ;
72
+ let withSentryServer = false ;
60
73
61
74
if ( testPath . endsWith ( '.ts' ) ) {
62
75
flags . push ( '-r' , 'ts-node/register' ) ;
@@ -71,71 +84,36 @@ export function createRunner(...paths: string[]) {
71
84
flags . push ( ...args ) ;
72
85
return this ;
73
86
} ,
87
+ withMockSentryServer : function ( ) {
88
+ withSentryServer = true ;
89
+ return this ;
90
+ } ,
74
91
ignore : function ( ...types : EnvelopeItemType [ ] ) {
75
92
ignored . push ( ...types ) ;
76
93
return this ;
77
94
} ,
78
95
start : function ( done ?: ( e ?: unknown ) => void ) {
79
96
const expectedEnvelopeCount = expectedEnvelopes . length ;
80
- let envelopeCount = 0 ;
81
- let serverPort : number | undefined ;
82
-
83
- const child = spawn ( 'node' , [ ...flags , testPath ] ) ;
84
97
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 ;
95
102
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 ) ;
105
106
}
106
107
107
108
/** Called after each expect callback to check if we're complete */
108
109
function expectCallbackCalled ( ) : void {
109
110
envelopeCount ++ ;
110
111
if ( envelopeCount === expectedEnvelopeCount ) {
111
- child . kill ( ) ;
112
- done ?.( ) ;
112
+ complete ( ) ;
113
113
}
114
114
}
115
115
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 {
139
117
for ( const item of envelope [ 1 ] ) {
140
118
const envelopeItemType = item [ 0 ] . type ;
141
119
@@ -190,22 +168,77 @@ export function createRunner(...paths: string[]) {
190
168
expectCallbackCalled ( ) ;
191
169
}
192
170
} catch ( e ) {
193
- done ?. ( e ) ;
171
+ complete ( e as Error ) ;
194
172
}
195
173
}
196
174
}
197
175
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
+ } ) ;
202
203
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
+ }
208
226
}
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
+ } ) ;
209
242
} ) ;
210
243
211
244
return {
@@ -218,13 +251,13 @@ export function createRunner(...paths: string[]) {
218
251
headers : Record < string , string > = { } ,
219
252
) : Promise < T | undefined > {
220
253
try {
221
- await waitForServerPort ( ) ;
254
+ await waitFor ( ( ) => scenarioServerPort !== undefined ) ;
222
255
} catch ( e ) {
223
- done ?. ( e ) ;
256
+ complete ( e as Error ) ;
224
257
return undefined ;
225
258
}
226
259
227
- const url = `http://localhost:${ serverPort } ${ path } ` ;
260
+ const url = `http://localhost:${ scenarioServerPort } ${ path } ` ;
228
261
if ( method === 'get' ) {
229
262
return ( await axios . get ( url , { headers } ) ) . data ;
230
263
} else {
0 commit comments