@@ -148,6 +148,7 @@ export enum TestSymbol {
148
148
difference = "difference" ,
149
149
warning = "warning" ,
150
150
details = "details" ,
151
+ none = "none" ,
151
152
}
152
153
153
154
export interface EventMessage {
@@ -222,9 +223,29 @@ export class SwiftTestingOutputParser {
222
223
* @param chunk A chunk of stdout emitted during a test run.
223
224
*/
224
225
public parseStdout = ( ( ) => {
225
- const values = Object . values ( TestSymbol ) . map ( symbol =>
226
- regexEscapedString ( SymbolRenderer . eventMessageSymbol ( symbol ) )
227
- ) ;
226
+ const values = [
227
+ ...Object . values ( TestSymbol )
228
+ . filter ( symbol => symbol !== TestSymbol . none )
229
+ . map ( symbol =>
230
+ regexEscapedString (
231
+ // Trim the ANSI reset code from the search since some lines
232
+ // are fully colorized from the symbol to the end of line.
233
+ SymbolRenderer . eventMessageSymbol ( symbol ) . replace (
234
+ SymbolRenderer . resetANSIEscapeCode ,
235
+ ""
236
+ )
237
+ )
238
+ ) ,
239
+ // It is possible there is no symbol for a line produced by swift-testing,
240
+ // for instance if the user has a multi line comment before a failing expectation
241
+ // only the first line of the printed comment will have a symbol, but to make the
242
+ // indentation consistent the subsequent lines will have three spaces. We don't want
243
+ // to treat this as output produced by the user during the test run, so omit these.
244
+ // This isn't ideal since this will swallow lines the user prints if they start with
245
+ // three spaces, but until we have user output as part of the JSON event stream we have
246
+ // this workaround.
247
+ " " ,
248
+ ] ;
228
249
229
250
// Build a regex of all the line beginnings that come out of swift-testing events.
230
251
const isSwiftTestingLineBeginning = new RegExp ( `^${ values . join ( "|" ) } ` ) ;
@@ -313,13 +334,47 @@ export class SwiftTestingOutputParser {
313
334
testIndex : number | undefined
314
335
) {
315
336
messages . forEach ( message => {
316
- runState . recordOutput (
317
- testIndex ,
318
- `${ SymbolRenderer . eventMessageSymbol ( message . symbol ) } ${ message . text } \r\n`
319
- ) ;
337
+ runState . recordOutput ( testIndex , `${ MessageRenderer . render ( message ) } \r\n` ) ;
320
338
} ) ;
321
339
}
322
340
341
+ /**
342
+ * Partitions a collection of messages in to issues and details about the issues.
343
+ * This is used to print the issues first, followed by the details.
344
+ */
345
+ private partitionIssueMessages ( messages : EventMessage [ ] ) : {
346
+ issues : EventMessage [ ] ;
347
+ details : EventMessage [ ] ;
348
+ } {
349
+ return messages . reduce (
350
+ ( buckets , message ) => {
351
+ const key =
352
+ message . symbol === "details" ||
353
+ message . symbol === "default" ||
354
+ message . symbol === "none"
355
+ ? "details"
356
+ : "issues" ;
357
+ return { ...buckets , [ key ] : [ ...buckets [ key ] , message ] } ;
358
+ } ,
359
+ {
360
+ issues : [ ] ,
361
+ details : [ ] ,
362
+ }
363
+ ) ;
364
+ }
365
+
366
+ /*
367
+ * A multi line comment preceeding an issue will have a 'default' symbol for
368
+ * all lines except the first one. To match the swift-testing command line we
369
+ * should show no symbol on these lines.
370
+ */
371
+ private transformIssueMessageSymbols ( messages : EventMessage [ ] ) : EventMessage [ ] {
372
+ return messages . map ( message => ( {
373
+ ...message ,
374
+ symbol : message . symbol === "default" ? TestSymbol . none : message . symbol ,
375
+ } ) ) ;
376
+ }
377
+
323
378
private parse ( item : SwiftTestEvent , runState : ITestRunState ) {
324
379
if (
325
380
item . kind === "test" &&
@@ -391,14 +446,31 @@ export class SwiftTestingOutputParser {
391
446
sourceLocation . line ,
392
447
sourceLocation . column
393
448
) ;
394
- item . payload . messages . forEach ( message => {
395
- runState . recordIssue ( testIndex , message . text , isKnown , location ) ;
449
+
450
+ const messages = this . transformIssueMessageSymbols ( item . payload . messages ) ;
451
+ const { issues, details } = this . partitionIssueMessages ( messages ) ;
452
+
453
+ // Order the details after the issue text.
454
+ const additionalDetails = details
455
+ . map ( message => MessageRenderer . render ( message ) )
456
+ . join ( "\n" ) ;
457
+
458
+ issues . forEach ( message => {
459
+ runState . recordIssue (
460
+ testIndex ,
461
+ additionalDetails . length > 0
462
+ ? `${ MessageRenderer . render ( message ) } \n${ additionalDetails } `
463
+ : MessageRenderer . render ( message ) ,
464
+ isKnown ,
465
+ location
466
+ ) ;
396
467
} ) ;
397
- this . recordOutput ( runState , item . payload . messages , testIndex ) ;
468
+
469
+ this . recordOutput ( runState , messages , testIndex ) ;
398
470
399
471
if ( item . payload . _testCase && testID !== item . payload . testID ) {
400
472
const testIndex = this . getTestCaseIndex ( runState , item . payload . testID ) ;
401
- item . payload . messages . forEach ( message => {
473
+ messages . forEach ( message => {
402
474
runState . recordIssue ( testIndex , message . text , isKnown , location ) ;
403
475
} ) ;
404
476
}
@@ -439,6 +511,32 @@ export class SwiftTestingOutputParser {
439
511
}
440
512
}
441
513
514
+ export class MessageRenderer {
515
+ /**
516
+ * Converts a swift-testing `EventMessage` to a colorized symbol and message text.
517
+ *
518
+ * @param message An event message, typically found on an `EventRecordPayload`.
519
+ * @returns A string colorized with ANSI escape codes.
520
+ */
521
+ static render ( message : EventMessage ) : string {
522
+ return `${ SymbolRenderer . eventMessageSymbol ( message . symbol ) } ${ MessageRenderer . colorize ( message . symbol , message . text ) } ` ;
523
+ }
524
+
525
+ private static colorize ( symbolType : TestSymbol , message : string ) : string {
526
+ const ansiEscapeCodePrefix = "\u{001B}[" ;
527
+ const resetANSIEscapeCode = `${ ansiEscapeCodePrefix } 0m` ;
528
+ switch ( symbolType ) {
529
+ case TestSymbol . details :
530
+ case TestSymbol . skip :
531
+ case TestSymbol . difference :
532
+ case TestSymbol . passWithKnownIssue :
533
+ return `${ ansiEscapeCodePrefix } 90m${ message } ${ resetANSIEscapeCode } ` ;
534
+ default :
535
+ return message ;
536
+ }
537
+ }
538
+ }
539
+
442
540
export class SymbolRenderer {
443
541
/**
444
542
* Converts a swift-testing symbol identifier in to a colorized unicode symbol.
@@ -450,6 +548,9 @@ export class SymbolRenderer {
450
548
return this . colorize ( symbol , this . symbol ( symbol ) ) ;
451
549
}
452
550
551
+ static ansiEscapeCodePrefix = "\u{001B}[" ;
552
+ static resetANSIEscapeCode = `${ SymbolRenderer . ansiEscapeCodePrefix } 0m` ;
553
+
453
554
// This is adapted from
454
555
// https://github.com/apple/swift-testing/blob/786ade71421eb1d8a9c1d99c902cf1c93096e7df/Sources/Testing/Events/Recorder/Event.Symbol.swift#L102
455
556
public static symbol ( symbol : TestSymbol ) : string {
@@ -469,6 +570,8 @@ export class SymbolRenderer {
469
570
return "\u{25B2}" ; // Unicode: BLACK UP-POINTING TRIANGLE
470
571
case TestSymbol . details :
471
572
return "\u{2192}" ; // Unicode: RIGHTWARDS ARROW
573
+ case TestSymbol . none :
574
+ return "" ;
472
575
}
473
576
} else {
474
577
switch ( symbol ) {
@@ -486,28 +589,29 @@ export class SymbolRenderer {
486
589
return "\u{26A0}\u{FE0E}" ; // Unicode: WARNING SIGN + VARIATION SELECTOR-15 (disable emoji)
487
590
case TestSymbol . details :
488
591
return "\u{21B3}" ; // Unicode: DOWNWARDS ARROW WITH TIP RIGHTWARDS
592
+ case TestSymbol . none :
593
+ return " " ;
489
594
}
490
595
}
491
596
}
492
597
493
598
// This is adapted from
494
599
// https://github.com/apple/swift-testing/blob/786ade71421eb1d8a9c1d99c902cf1c93096e7df/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift#L164
495
600
private static colorize ( symbolType : TestSymbol , symbol : string ) : string {
496
- const ansiEscapeCodePrefix = "\u{001B}[" ;
497
- const resetANSIEscapeCode = `${ ansiEscapeCodePrefix } 0m` ;
498
601
switch ( symbolType ) {
499
602
case TestSymbol . default :
603
+ case TestSymbol . details :
500
604
case TestSymbol . skip :
501
605
case TestSymbol . difference :
502
606
case TestSymbol . passWithKnownIssue :
503
- return `${ ansiEscapeCodePrefix } 90m${ symbol } ${ resetANSIEscapeCode } ` ;
607
+ return `${ SymbolRenderer . ansiEscapeCodePrefix } 90m${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
504
608
case TestSymbol . pass :
505
- return `${ ansiEscapeCodePrefix } 92m${ symbol } ${ resetANSIEscapeCode } ` ;
609
+ return `${ SymbolRenderer . ansiEscapeCodePrefix } 92m${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
506
610
case TestSymbol . fail :
507
- return `${ ansiEscapeCodePrefix } 91m${ symbol } ${ resetANSIEscapeCode } ` ;
611
+ return `${ SymbolRenderer . ansiEscapeCodePrefix } 91m${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
508
612
case TestSymbol . warning :
509
- return `${ ansiEscapeCodePrefix } 93m${ symbol } ${ resetANSIEscapeCode } ` ;
510
- case TestSymbol . details :
613
+ return `${ SymbolRenderer . ansiEscapeCodePrefix } 93m${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
614
+ case TestSymbol . none :
511
615
default :
512
616
return symbol ;
513
617
}
0 commit comments