@@ -135,156 +135,218 @@ type TestResult = {
135
135
result : 'PASS' | 'FAIL' | 'TIMEOUT' ;
136
136
} ;
137
137
138
+ type VersionResult = {
139
+ dependencyOverrides ?: Record < string , string > ;
140
+ buildFailed : boolean ;
141
+ testResults : TestResult [ ] ;
142
+ } ;
143
+
138
144
type RecipeResult = {
139
145
testApplicationName : string ;
140
146
testApplicationPath : string ;
141
- buildFailed : boolean ;
142
- testResults : TestResult [ ] ;
147
+ versionResults : VersionResult [ ] ;
143
148
} ;
144
149
145
- const recipeResults : RecipeResult [ ] = recipePaths . map ( recipePath => {
146
- type Recipe = {
147
- testApplicationName : string ;
148
- buildCommand ?: string ;
149
- buildTimeoutSeconds ?: number ;
150
- tests : {
151
- testName : string ;
152
- testCommand : string ;
153
- timeoutSeconds ?: number ;
154
- } [ ] ;
155
- } ;
150
+ type Recipe = {
151
+ testApplicationName : string ;
152
+ buildCommand ?: string ;
153
+ buildTimeoutSeconds ?: number ;
154
+ tests : {
155
+ testName : string ;
156
+ testCommand : string ;
157
+ timeoutSeconds ?: number ;
158
+ } [ ] ;
159
+ versions ?: { dependencyOverrides : Record < string , string > } [ ] ;
160
+ canaryVersions ?: { dependencyOverrides : Record < string , string > } [ ] ;
161
+ } ;
156
162
163
+ const recipeResults : RecipeResult [ ] = recipePaths . map ( recipePath => {
157
164
const recipe : Recipe = JSON . parse ( fs . readFileSync ( recipePath , 'utf-8' ) ) ;
165
+ const recipeDirname = path . dirname ( recipePath ) ;
158
166
159
- if ( recipe . buildCommand ) {
160
- console . log ( `Running E2E test build command for test application "${ recipe . testApplicationName } "` ) ;
161
- const buildCommandProcess = childProcess . spawnSync ( recipe . buildCommand , {
162
- cwd : path . dirname ( recipePath ) ,
163
- encoding : 'utf8' ,
164
- shell : true , // needed so we can pass the build command in as whole without splitting it up into args
165
- timeout : ( recipe . buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS ) * 1000 ,
166
- env : {
167
- ...process . env ,
168
- ...envVarsToInject ,
169
- } ,
170
- } ) ;
171
-
172
- // Prepends some text to the output build command's output so we can distinguish it from logging in this script
173
- console . log ( buildCommandProcess . stdout . replace ( / ^ / gm, '[BUILD OUTPUT] ' ) ) ;
174
- console . log ( buildCommandProcess . stderr . replace ( / ^ / gm, '[BUILD OUTPUT] ' ) ) ;
167
+ function runRecipe ( dependencyOverrides : Record < string , string > | undefined ) : VersionResult {
168
+ const dependencyOverridesInformationString = dependencyOverrides
169
+ ? ` (Dependency overrides: ${ JSON . stringify ( dependencyOverrides ) } )`
170
+ : '' ;
175
171
176
- const error : undefined | ( Error & { code ?: string } ) = buildCommandProcess . error ;
177
-
178
- if ( error ?. code === 'ETIMEDOUT' ) {
179
- processShouldExitWithError = true ;
180
-
181
- printCIErrorMessage (
182
- `Build command in test application "${ recipe . testApplicationName } " (${ path . dirname ( recipePath ) } ) timed out!` ,
183
- ) ;
184
-
185
- return {
186
- testApplicationName : recipe . testApplicationName ,
187
- testApplicationPath : recipePath ,
188
- buildFailed : true ,
189
- testResults : [ ] ,
190
- } ;
191
- } else if ( buildCommandProcess . status !== 0 ) {
192
- processShouldExitWithError = true ;
193
-
194
- printCIErrorMessage (
195
- `Build command in test application "${ recipe . testApplicationName } " (${ path . dirname ( recipePath ) } ) failed!` ,
172
+ if ( recipe . buildCommand ) {
173
+ console . log (
174
+ `Running E2E test build command for test application "${ recipe . testApplicationName } "${ dependencyOverridesInformationString } ` ,
196
175
) ;
197
-
198
- return {
199
- testApplicationName : recipe . testApplicationName ,
200
- testApplicationPath : recipePath ,
201
- buildFailed : true ,
202
- testResults : [ ] ,
203
- } ;
176
+ const buildCommandProcess = childProcess . spawnSync ( recipe . buildCommand , {
177
+ cwd : path . dirname ( recipePath ) ,
178
+ encoding : 'utf8' ,
179
+ shell : true , // needed so we can pass the build command in as whole without splitting it up into args
180
+ timeout : ( recipe . buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS ) * 1000 ,
181
+ env : {
182
+ ...process . env ,
183
+ ...envVarsToInject ,
184
+ } ,
185
+ } ) ;
186
+
187
+ // Prepends some text to the output build command's output so we can distinguish it from logging in this script
188
+ console . log ( buildCommandProcess . stdout . replace ( / ^ / gm, '[BUILD OUTPUT] ' ) ) ;
189
+ console . log ( buildCommandProcess . stderr . replace ( / ^ / gm, '[BUILD OUTPUT] ' ) ) ;
190
+
191
+ const error : undefined | ( Error & { code ?: string } ) = buildCommandProcess . error ;
192
+
193
+ if ( error ?. code === 'ETIMEDOUT' ) {
194
+ processShouldExitWithError = true ;
195
+
196
+ printCIErrorMessage (
197
+ `Build command in test application "${ recipe . testApplicationName } " (${ path . dirname ( recipePath ) } ) timed out!` ,
198
+ ) ;
199
+
200
+ return {
201
+ dependencyOverrides,
202
+ buildFailed : true ,
203
+ testResults : [ ] ,
204
+ } ;
205
+ } else if ( buildCommandProcess . status !== 0 ) {
206
+ processShouldExitWithError = true ;
207
+
208
+ printCIErrorMessage (
209
+ `Build command in test application "${ recipe . testApplicationName } " (${ path . dirname ( recipePath ) } ) failed!` ,
210
+ ) ;
211
+
212
+ return {
213
+ dependencyOverrides,
214
+ buildFailed : true ,
215
+ testResults : [ ] ,
216
+ } ;
217
+ }
204
218
}
205
- }
206
219
207
- const testResults : TestResult [ ] = recipe . tests . map ( test => {
208
- console . log (
209
- `Running E2E test command for test application "${ recipe . testApplicationName } ", test "${ test . testName } "` ,
210
- ) ;
220
+ const testResults : TestResult [ ] = recipe . tests . map ( test => {
221
+ console . log (
222
+ `Running E2E test command for test application "${ recipe . testApplicationName } ", test "${ test . testName } "${ dependencyOverridesInformationString } ` ,
223
+ ) ;
211
224
212
- const testProcessResult = childProcess . spawnSync ( test . testCommand , {
213
- cwd : path . dirname ( recipePath ) ,
214
- timeout : ( test . timeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS ) * 1000 ,
215
- encoding : 'utf8' ,
216
- shell : true , // needed so we can pass the test command in as whole without splitting it up into args
217
- env : {
218
- ...process . env ,
219
- ...envVarsToInject ,
220
- } ,
225
+ const testProcessResult = childProcess . spawnSync ( test . testCommand , {
226
+ cwd : path . dirname ( recipePath ) ,
227
+ timeout : ( test . timeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS ) * 1000 ,
228
+ encoding : 'utf8' ,
229
+ shell : true , // needed so we can pass the test command in as whole without splitting it up into args
230
+ env : {
231
+ ...process . env ,
232
+ ...envVarsToInject ,
233
+ } ,
234
+ } ) ;
235
+
236
+ // Prepends some text to the output test command's output so we can distinguish it from logging in this script
237
+ console . log ( testProcessResult . stdout . replace ( / ^ / gm, '[TEST OUTPUT] ' ) ) ;
238
+ console . log ( testProcessResult . stderr . replace ( / ^ / gm, '[TEST OUTPUT] ' ) ) ;
239
+
240
+ const error : undefined | ( Error & { code ?: string } ) = testProcessResult . error ;
241
+
242
+ if ( error ?. code === 'ETIMEDOUT' ) {
243
+ processShouldExitWithError = true ;
244
+ printCIErrorMessage (
245
+ `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
246
+ recipePath ,
247
+ ) } ) timed out.`,
248
+ ) ;
249
+ return {
250
+ testName : test . testName ,
251
+ result : 'TIMEOUT' ,
252
+ } ;
253
+ } else if ( testProcessResult . status !== 0 ) {
254
+ processShouldExitWithError = true ;
255
+ printCIErrorMessage (
256
+ `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
257
+ recipePath ,
258
+ ) } ) failed.`,
259
+ ) ;
260
+ return {
261
+ testName : test . testName ,
262
+ result : 'FAIL' ,
263
+ } ;
264
+ } else {
265
+ console . log (
266
+ `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
267
+ recipePath ,
268
+ ) } ) succeeded.`,
269
+ ) ;
270
+ return {
271
+ testName : test . testName ,
272
+ result : 'PASS' ,
273
+ } ;
274
+ }
221
275
} ) ;
222
276
223
- // Prepends some text to the output test command's output so we can distinguish it from logging in this script
224
- console . log ( testProcessResult . stdout . replace ( / ^ / gm, '[TEST OUTPUT] ' ) ) ;
225
- console . log ( testProcessResult . stderr . replace ( / ^ / gm, '[TEST OUTPUT] ' ) ) ;
277
+ return {
278
+ dependencyOverrides,
279
+ buildFailed : false ,
280
+ testResults,
281
+ } ;
282
+ }
226
283
227
- const error : undefined | ( Error & { code ?: string } ) = testProcessResult . error ;
284
+ const versionsToRun : {
285
+ dependencyOverrides ?: Record < string , string > ;
286
+ } [ ] = ( process . env . CANARY_E2E_TEST ? recipe . canaryVersions : recipe . versions ) ?? [ { } ] ;
228
287
229
- if ( error ?. code === 'ETIMEDOUT' ) {
230
- processShouldExitWithError = true ;
231
- printCIErrorMessage (
232
- `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
233
- recipePath ,
234
- ) } ) timed out.`,
235
- ) ;
236
- return {
237
- testName : test . testName ,
238
- result : 'TIMEOUT' ,
239
- } ;
240
- } else if ( testProcessResult . status !== 0 ) {
241
- processShouldExitWithError = true ;
242
- printCIErrorMessage (
243
- `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
244
- recipePath ,
245
- ) } ) failed.`,
246
- ) ;
247
- return {
248
- testName : test . testName ,
249
- result : 'FAIL' ,
250
- } ;
251
- } else {
252
- console . log (
253
- `Test "${ test . testName } " in test application "${ recipe . testApplicationName } " (${ path . dirname (
254
- recipePath ,
255
- ) } ) succeeded.`,
288
+ const versionResults = versionsToRun . map ( ( { dependencyOverrides } ) => {
289
+ if ( dependencyOverrides ) {
290
+ // Back up original package.json
291
+ fs . copyFileSync ( path . resolve ( recipeDirname , 'package.json' ) , path . resolve ( recipeDirname , 'package.json.bak' ) ) ;
292
+
293
+ // Override dependencies
294
+ const packageJson : { dependencies ?: Record < string , string > } = JSON . parse (
295
+ fs . readFileSync ( path . resolve ( recipeDirname , 'package.json' ) , { encoding : 'utf-8' } ) ,
256
296
) ;
257
- return {
258
- testName : test . testName ,
259
- result : 'PASS' ,
260
- } ;
297
+ packageJson . dependencies = packageJson . dependencies
298
+ ? { ...packageJson . dependencies , ...dependencyOverrides }
299
+ : dependencyOverrides ;
300
+ fs . writeFileSync ( path . resolve ( recipeDirname , 'package.json' ) , JSON . stringify ( packageJson , null , 2 ) , {
301
+ encoding : 'utf-8' ,
302
+ } ) ;
303
+ }
304
+
305
+ try {
306
+ return runRecipe ( dependencyOverrides ) ;
307
+ } finally {
308
+ if ( dependencyOverrides ) {
309
+ // Restore original package.json
310
+ fs . rmSync ( path . resolve ( recipeDirname , 'package.json' ) , { force : true } ) ;
311
+ fs . copyFileSync ( path . resolve ( recipeDirname , 'package.json.bak' ) , path . resolve ( recipeDirname , 'package.json' ) ) ;
312
+ fs . rmSync ( path . resolve ( recipeDirname , 'package.json.bak' ) , { force : true } ) ;
313
+ }
261
314
}
262
315
} ) ;
263
316
264
317
return {
265
318
testApplicationName : recipe . testApplicationName ,
266
319
testApplicationPath : recipePath ,
267
- buildFailed : false ,
268
- testResults,
320
+ versionResults,
269
321
} ;
270
322
} ) ;
271
323
272
324
console . log ( '--------------------------------------' ) ;
273
325
console . log ( 'Test Result Summary:' ) ;
274
326
275
327
recipeResults . forEach ( recipeResult => {
276
- if ( recipeResult . buildFailed ) {
277
- console . log (
278
- `● BUILD FAILED - ${ recipeResult . testApplicationName } (${ path . dirname ( recipeResult . testApplicationPath ) } )` ,
279
- ) ;
280
- } else {
281
- console . log (
282
- `● BUILD SUCCEEDED - ${ recipeResult . testApplicationName } (${ path . dirname ( recipeResult . testApplicationPath ) } )` ,
283
- ) ;
284
- recipeResult . testResults . forEach ( testResult => {
285
- console . log ( ` ● ${ testResult . result . padEnd ( 7 , ' ' ) } ${ testResult . testName } ` ) ;
286
- } ) ;
287
- }
328
+ recipeResult . versionResults . forEach ( versionResult => {
329
+ const dependencyOverridesInformationString = versionResult . dependencyOverrides
330
+ ? ` (Dependency overrides: ${ JSON . stringify ( versionResult . dependencyOverrides ) } )`
331
+ : '' ;
332
+
333
+ if ( versionResult . buildFailed ) {
334
+ console . log (
335
+ `● BUILD FAILED - ${ recipeResult . testApplicationName } (${ path . dirname (
336
+ recipeResult . testApplicationPath ,
337
+ ) } )${ dependencyOverridesInformationString } `,
338
+ ) ;
339
+ } else {
340
+ console . log (
341
+ `● BUILD SUCCEEDED - ${ recipeResult . testApplicationName } (${ path . dirname (
342
+ recipeResult . testApplicationPath ,
343
+ ) } )${ dependencyOverridesInformationString } `,
344
+ ) ;
345
+ versionResult . testResults . forEach ( testResult => {
346
+ console . log ( ` ● ${ testResult . result . padEnd ( 7 , ' ' ) } ${ testResult . testName } ` ) ;
347
+ } ) ;
348
+ }
349
+ } ) ;
288
350
} ) ;
289
351
290
352
groupCIOutput ( 'Cleanup' , ( ) => {
0 commit comments