@@ -18,6 +18,8 @@ import { MarkdownString, Location } from "vscode";
18
18
// eslint-disable-next-line @typescript-eslint/no-require-imports
19
19
import stripAnsi = require( "strip-ansi" ) ;
20
20
21
+ /* eslint-disable no-console */
22
+
21
23
/** Regex for parsing XCTest output */
22
24
interface TestRegex {
23
25
started : RegExp ;
@@ -151,117 +153,160 @@ export class XCTestOutputParser implements IXCTestOutputParser {
151
153
* @param output Output from `swift test`
152
154
*/
153
155
public parseResult ( rawOutput : string , runState : ITestRunState ) {
154
- console . log ( "RAW XCTEST OUTPUT:" , rawOutput . split ( "\n" ) . join ( "\n>>>> " ) ) ;
155
-
156
- // Windows is inserting ANSI codes into the output to do things like clear the cursor,
157
- // which we don't care about.
158
- const output = process . platform === "win32" ? stripAnsi ( rawOutput ) : rawOutput ;
159
- const output2 = output . replace ( / \r \n / g, "\n" ) ;
160
- const lines = output2 . split ( "\n" ) ;
161
- if ( runState . excess ) {
162
- lines [ 0 ] = runState . excess + lines [ 0 ] ;
163
- }
164
- // pop empty string off the end of the lines array
165
- if ( lines . length > 0 && lines [ lines . length - 1 ] === "" ) {
166
- lines . pop ( ) ;
167
- }
168
- // if submitted text does not end with a newline then pop that off and store in excess
169
- // for next call of parseResult
170
- if ( output2 [ output2 . length - 1 ] !== "\n" ) {
171
- runState . excess = lines . pop ( ) ;
172
- } else {
173
- runState . excess = undefined ;
174
- }
175
-
176
- // Non-Darwin test output does not include the test target name. The only way to find out
177
- // the target for a test is when it fails and returns a file name. If we find failed tests
178
- // first and then remove them from the list we cannot set them to passed by mistake.
179
- // We extract the file name from the error and use that to check whether the file belongs
180
- // to the target associated with the TestItem. This does not work 100% as the error could
181
- // occur in another target, so we revert to just searching for class and function name if
182
- // the above method is unsuccessful.
183
- for ( const line of lines ) {
184
- // Regex "Test Case '-[<test target> <class.function>]' started"
185
- const startedMatch = this . regex . started . exec ( line ) ;
186
- if ( startedMatch ) {
187
- const testName = `${ startedMatch [ 1 ] } /${ startedMatch [ 2 ] } ` ;
188
-
189
- // Save the active TestTarget.SuiteClass.
190
- // Note that TestTarget is only present on Darwin.
191
- runState . activeSuite = startedMatch [ 1 ] ;
192
- this . processPendingSuiteOutput ( runState , startedMatch [ 1 ] ) ;
193
-
194
- const startedTestIndex = runState . getTestItemIndex ( testName , undefined ) ;
195
- this . startTest ( startedTestIndex , runState ) ;
196
- this . appendTestOutput ( startedTestIndex , line , runState ) ;
197
- continue ;
198
- }
199
- // Regex "Test Case '-[<test target> <class.function>]' <completion_state> (<duration> seconds)"
200
- const finishedMatch = this . regex . finished . exec ( line ) ;
201
- if ( finishedMatch ) {
202
- const testName = `${ finishedMatch [ 1 ] } /${ finishedMatch [ 2 ] } ` ;
203
- const testIndex = runState . getTestItemIndex ( testName , undefined ) ;
204
- const state = finishedMatch [ 3 ] as TestCompletionState ;
205
- const duration = + finishedMatch [ 4 ] ;
206
- switch ( state ) {
207
- case TestCompletionState . failed :
208
- this . failTest ( testIndex , { duration } , runState ) ;
209
- break ;
210
- case TestCompletionState . passed :
211
- this . passTest ( testIndex , { duration } , runState ) ;
212
- break ;
213
- case TestCompletionState . skipped :
214
- this . skipTest ( testIndex , runState ) ;
215
- break ;
216
- }
217
-
218
- this . appendTestOutput ( testIndex , line , runState ) ;
219
- continue ;
220
- }
221
- // Regex "<path/to/test>:<line number>: error: <class>.<function> : <error>"
222
- const errorMatch = this . regex . error . exec ( line ) ;
223
- if ( errorMatch ) {
224
- const testName = `${ errorMatch [ 3 ] } /${ errorMatch [ 4 ] } ` ;
225
- const failedTestIndex = runState . getTestItemIndex ( testName , errorMatch [ 1 ] ) ;
226
- this . startErrorMessage (
227
- failedTestIndex ,
228
- errorMatch [ 5 ] ,
229
- errorMatch [ 1 ] ,
230
- errorMatch [ 2 ] ,
231
- runState
232
- ) ;
233
- this . appendTestOutput ( failedTestIndex , line , runState ) ;
234
- continue ;
235
- }
236
- // Regex "<path/to/test>:<line number>: <class>.<function> : Test skipped"
237
- const skippedMatch = this . regex . skipped . exec ( line ) ;
238
- if ( skippedMatch ) {
239
- const testName = `${ skippedMatch [ 3 ] } /${ skippedMatch [ 4 ] } ` ;
240
- const skippedTestIndex = runState . getTestItemIndex ( testName , skippedMatch [ 1 ] ) ;
241
- this . skipTest ( skippedTestIndex , runState ) ;
242
- this . appendTestOutput ( skippedTestIndex , line , runState ) ;
243
- continue ;
156
+ try {
157
+ console . log (
158
+ ">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" +
159
+ rawOutput . split ( "\n" ) . join ( "\n" )
160
+ ) ;
161
+ console . log ( ">>>>>>>>>>>>>>>> RAW XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>" ) ;
162
+ const annotated = rawOutput
163
+ . replaceAll ( "\r\n" , "🐀❌" )
164
+ . replaceAll ( "\n\r" , "❌🐀" )
165
+ . replaceAll ( "\n" , "❌" )
166
+ . replaceAll ( "\r" , "🐀" ) ;
167
+ console . log (
168
+ ">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT >>>>>>>>>>>>>>>>>>> :\n" + annotated
169
+ ) ;
170
+ console . log ( ">>>>>>>>>>>>>>>> ANNOTATED XCTEST OUTPUT COMPLETE >>>>>>>>>>>>>>>>>>>" ) ;
171
+
172
+ // Windows is inserting ANSI codes into the output to do things like clear the cursor,
173
+ // which we don't care about.
174
+ const output = process . platform === "win32" ? stripAnsi ( rawOutput ) : rawOutput ;
175
+ const output2 = output . replace ( / \r \n / g, "\n" ) ;
176
+ const lines = output2 . split ( "\n" ) ;
177
+ console . log ( ">>> Lines to Process:" , lines ) ;
178
+ console . log ( ">>> Run State Excess |" + runState . excess + "|" ) ;
179
+ if ( runState . excess ) {
180
+ console . log ( ">>> !Had Excess from previous output |" + runState . excess + "|" ) ;
181
+ lines [ 0 ] = runState . excess + lines [ 0 ] ;
182
+ console . log ( ">>> !New line[0] is now |" + lines [ 0 ] + "|" ) ;
244
183
}
245
- // Regex "Test Suite '-[<test target> <class.function>]' started"
246
- const startedSuiteMatch = this . regex . startedSuite . exec ( line ) ;
247
- if ( startedSuiteMatch ) {
248
- this . startTestSuite ( startedSuiteMatch [ 1 ] , line , runState ) ;
249
- continue ;
184
+ // pop empty string off the end of the lines array
185
+ if ( lines . length > 0 && lines [ lines . length - 1 ] === "" ) {
186
+ console . log ( ">>> Trimming last line" ) ;
187
+ lines . pop ( ) ;
250
188
}
251
- // Regex "Test Suite '-[<test target> <class.function>]' passed"
252
- const passedSuiteMatch = this . regex . passedSuite . exec ( line ) ;
253
- if ( passedSuiteMatch ) {
254
- this . completeSuite ( runState , line , this . passTestSuite ) ;
255
- continue ;
189
+ // if submitted text does not end with a newline then pop that off and store in excess
190
+ // for next call of parseResult
191
+ if ( output2 [ output2 . length - 1 ] !== "\n" ) {
192
+ runState . excess = lines . pop ( ) ;
193
+ console . log ( ">>> Saving Excess |" + runState . excess + "|" ) ;
194
+ } else {
195
+ console . log ( ">>> Unsetting Excess" ) ;
196
+ runState . excess = undefined ;
256
197
}
257
- // Regex "Test Suite '-[<test target> <class.function>]' failed"
258
- const failedSuiteMatch = this . regex . failedSuite . exec ( line ) ;
259
- if ( failedSuiteMatch ) {
260
- this . completeSuite ( runState , line , this . failTestSuite ) ;
261
- continue ;
198
+
199
+ console . log ( ">>> Lines to Process AFTER:" , lines ) ;
200
+
201
+ // Non-Darwin test output does not include the test target name. The only way to find out
202
+ // the target for a test is when it fails and returns a file name. If we find failed tests
203
+ // first and then remove them from the list we cannot set them to passed by mistake.
204
+ // We extract the file name from the error and use that to check whether the file belongs
205
+ // to the target associated with the TestItem. This does not work 100% as the error could
206
+ // occur in another target, so we revert to just searching for class and function name if
207
+ // the above method is unsuccessful.
208
+ for ( const line of lines ) {
209
+ console . log ( ">>> Process Line: |" + line + "|" ) ;
210
+ // Regex "Test Case '-[<test target> <class.function>]' started"
211
+ const startedMatch = this . regex . started . exec ( line ) ;
212
+ if ( startedMatch ) {
213
+ const testName = `${ startedMatch [ 1 ] } /${ startedMatch [ 2 ] } ` ;
214
+
215
+ // Save the active TestTarget.SuiteClass.
216
+ // Note that TestTarget is only present on Darwin.
217
+ runState . activeSuite = startedMatch [ 1 ] ;
218
+ this . processPendingSuiteOutput ( runState , startedMatch [ 1 ] ) ;
219
+
220
+ const startedTestIndex = runState . getTestItemIndex ( testName , undefined ) ;
221
+ this . startTest ( startedTestIndex , runState ) ;
222
+ this . appendTestOutput ( startedTestIndex , line , runState ) ;
223
+ continue ;
224
+ }
225
+ // Regex "Test Case '-[<test target> <class.function>]' <completion_state> (<duration> seconds)"
226
+ const finishedMatch = this . regex . finished . exec ( line ) ;
227
+ if ( finishedMatch ) {
228
+ const testName = `${ finishedMatch [ 1 ] } /${ finishedMatch [ 2 ] } ` ;
229
+ const testIndex = runState . getTestItemIndex ( testName , undefined ) ;
230
+ const state = finishedMatch [ 3 ] as TestCompletionState ;
231
+ const duration = + finishedMatch [ 4 ] ;
232
+ console . log (
233
+ "!!! >>>>> !!!! FINISHED MATCH ON LINE:" ,
234
+ line ,
235
+ "...STATE IS" ,
236
+ state ,
237
+ "... TEST INDEX IS" ,
238
+ testIndex ,
239
+ "... FAILED TEST IS" ,
240
+ runState . failedTest
241
+ ) ;
242
+ switch ( state ) {
243
+ case TestCompletionState . failed :
244
+ this . failTest ( testIndex , { duration } , runState ) ;
245
+ break ;
246
+ case TestCompletionState . passed :
247
+ this . passTest ( testIndex , { duration } , runState ) ;
248
+ break ;
249
+ case TestCompletionState . skipped :
250
+ this . skipTest ( testIndex , runState ) ;
251
+ break ;
252
+ }
253
+
254
+ this . appendTestOutput ( testIndex , line , runState ) ;
255
+ continue ;
256
+ }
257
+ // Regex "<path/to/test>:<line number>: error: <class>.<function> : <error>"
258
+ const errorMatch = this . regex . error . exec ( line ) ;
259
+ if ( errorMatch ) {
260
+ const testName = `${ errorMatch [ 3 ] } /${ errorMatch [ 4 ] } ` ;
261
+ const failedTestIndex = runState . getTestItemIndex ( testName , errorMatch [ 1 ] ) ;
262
+ console . log (
263
+ ">>> ERROR MATCH ON LINE:" ,
264
+ line ,
265
+ "...FAILED TEST INDEX IS" ,
266
+ failedTestIndex
267
+ ) ;
268
+ this . startErrorMessage (
269
+ failedTestIndex ,
270
+ errorMatch [ 5 ] ,
271
+ errorMatch [ 1 ] ,
272
+ errorMatch [ 2 ] ,
273
+ runState
274
+ ) ;
275
+ this . appendTestOutput ( failedTestIndex , line , runState ) ;
276
+ continue ;
277
+ }
278
+ // Regex "<path/to/test>:<line number>: <class>.<function> : Test skipped"
279
+ const skippedMatch = this . regex . skipped . exec ( line ) ;
280
+ if ( skippedMatch ) {
281
+ const testName = `${ skippedMatch [ 3 ] } /${ skippedMatch [ 4 ] } ` ;
282
+ const skippedTestIndex = runState . getTestItemIndex ( testName , skippedMatch [ 1 ] ) ;
283
+ this . skipTest ( skippedTestIndex , runState ) ;
284
+ this . appendTestOutput ( skippedTestIndex , line , runState ) ;
285
+ continue ;
286
+ }
287
+ // Regex "Test Suite '-[<test target> <class.function>]' started"
288
+ const startedSuiteMatch = this . regex . startedSuite . exec ( line ) ;
289
+ if ( startedSuiteMatch ) {
290
+ this . startTestSuite ( startedSuiteMatch [ 1 ] , line , runState ) ;
291
+ continue ;
292
+ }
293
+ // Regex "Test Suite '-[<test target> <class.function>]' passed"
294
+ const passedSuiteMatch = this . regex . passedSuite . exec ( line ) ;
295
+ if ( passedSuiteMatch ) {
296
+ this . completeSuite ( runState , line , this . passTestSuite ) ;
297
+ continue ;
298
+ }
299
+ // Regex "Test Suite '-[<test target> <class.function>]' failed"
300
+ const failedSuiteMatch = this . regex . failedSuite . exec ( line ) ;
301
+ if ( failedSuiteMatch ) {
302
+ this . completeSuite ( runState , line , this . failTestSuite ) ;
303
+ continue ;
304
+ }
305
+ // unrecognised output could be the continuation of a previous error message
306
+ this . continueErrorMessage ( line , runState ) ;
262
307
}
263
- // unrecognised output could be the continuation of a previous error message
264
- this . continueErrorMessage ( line , runState ) ;
308
+ } catch ( e ) {
309
+ console . error ( "!!! >>>>> !!! ERROR PARSING OUTPUT" , e ) ;
265
310
}
266
311
}
267
312
@@ -342,6 +387,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
342
387
private startTest ( testIndex : number , runState : ITestRunState ) {
343
388
runState . started ( testIndex ) ;
344
389
// clear error state
390
+ console . log ( ">>>>> startTest resetting failed test" , testIndex ) ;
345
391
runState . failedTest = undefined ;
346
392
}
347
393
@@ -352,6 +398,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
352
398
runState : ITestRunState
353
399
) {
354
400
runState . completed ( testIndex , timing ) ;
401
+ console . log ( ">>>>> passTest resetting failed test" , testIndex ) ;
355
402
runState . failedTest = undefined ;
356
403
}
357
404
@@ -363,6 +410,14 @@ export class XCTestOutputParser implements IXCTestOutputParser {
363
410
lineNumber : string ,
364
411
runState : ITestRunState
365
412
) {
413
+ console . log (
414
+ "!!! >>>>> !! START ERROR MESSAGE" ,
415
+ testIndex ,
416
+ message ,
417
+ file ,
418
+ lineNumber ,
419
+ ! ! runState . failedTest
420
+ ) ;
366
421
// If we were already capturing an error record it and start a new one
367
422
if ( runState . failedTest ) {
368
423
const location = sourceLocationToVSCodeLocation (
@@ -379,6 +434,7 @@ export class XCTestOutputParser implements IXCTestOutputParser {
379
434
lineNumber : parseInt ( lineNumber ) ,
380
435
complete : false ,
381
436
} ;
437
+ console . log ( ">>>>>>> SET FAILED TEST TO" , runState . failedTest ) ;
382
438
}
383
439
384
440
/** continue capturing error message */
@@ -407,16 +463,21 @@ export class XCTestOutputParser implements IXCTestOutputParser {
407
463
const diff = this . extractDiff ( message ) ;
408
464
runState . recordIssue ( testIndex , message , false , location , diff ) ;
409
465
} else {
410
- console . log ( ">>>>>>>>>> MARKED FAILED TEST WITH NO ATTACHED FAILED TEST >>>>>>>>>>>>>" ) ;
466
+ console . log (
467
+ `>>>>>>>>>> MARKED FAILED TEST WITH NO ATTACHED FAILED TEST ${ testIndex } >>>>>>>>>>>>>`
468
+ ) ;
411
469
runState . recordIssue ( testIndex , "Failed" , false ) ;
412
470
}
413
471
runState . completed ( testIndex , timing ) ;
472
+
473
+ console . log ( ">>>>> failTest resetting failed test" , testIndex ) ;
414
474
runState . failedTest = undefined ;
415
475
}
416
476
417
477
/** Flag we have skipped a test */
418
478
private skipTest ( testIndex : number , runState : ITestRunState ) {
419
479
runState . skipped ( testIndex ) ;
480
+ console . log ( ">>>>> skipTest resetting failed test" , testIndex ) ;
420
481
runState . failedTest = undefined ;
421
482
}
422
483
0 commit comments