Skip to content

Commit 9aaa6ee

Browse files
authored
Backport fix displaying inner exceptions (#3965)
1 parent d0b9266 commit 9aaa6ee

File tree

3 files changed

+78
-44
lines changed

3 files changed

+78
-44
lines changed

src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs

+52-21
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ internal void TestCompleted(
373373
TestOutcome outcome,
374374
TimeSpan duration,
375375
string? errorMessage,
376-
string? errorStackTrace,
376+
Exception? exception,
377377
string? expected,
378378
string? actual)
379379
{
@@ -410,7 +410,7 @@ internal void TestCompleted(
410410
outcome,
411411
duration,
412412
errorMessage,
413-
errorStackTrace,
413+
exception,
414414
expected,
415415
actual));
416416
}
@@ -425,7 +425,7 @@ internal void TestCompleted(
425425
TestOutcome outcome,
426426
TimeSpan duration,
427427
string? errorMessage,
428-
string? errorStackTrace,
428+
Exception? exception,
429429
string? expected,
430430
string? actual)
431431
{
@@ -469,9 +469,10 @@ internal void TestCompleted(
469469

470470
terminal.AppendLine();
471471

472-
FormatErrorMessage(terminal, errorMessage);
472+
FormatErrorMessage(terminal, errorMessage, exception, outcome);
473473
FormatExpectedAndActual(terminal, expected, actual);
474-
FormatStackTrace(terminal, errorStackTrace);
474+
FormatStackTrace(terminal, exception);
475+
FormatInnerExceptions(terminal, exception);
475476
}
476477

477478
private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal terminal, string assembly, string? targetFramework, string? architecture)
@@ -495,25 +496,16 @@ private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal t
495496
}
496497
}
497498

498-
private static void FormatStackTrace(ITerminal terminal, string? errorStackTrace)
499+
private static void FormatStackTrace(ITerminal terminal, Exception? exception)
499500
{
500-
if (RoslynString.IsNullOrWhiteSpace(errorStackTrace))
501+
if (exception?.StackTrace is not { } stackTrace)
501502
{
502503
return;
503504
}
504505

505-
terminal.SetColor(TerminalColor.Red);
506-
terminal.Append(SingleIndentation);
507-
terminal.Append(PlatformResources.StackTrace);
508-
terminal.AppendLine(":");
509-
510-
if (!errorStackTrace.Contains('\n'))
511-
{
512-
AppendStackFrame(terminal, errorStackTrace);
513-
return;
514-
}
506+
terminal.SetColor(TerminalColor.DarkGray);
515507

516-
string[] lines = errorStackTrace.Split(NewLineStrings, StringSplitOptions.None);
508+
string[] lines = stackTrace.Split(NewLineStrings, StringSplitOptions.None);
517509
foreach (string line in lines)
518510
{
519511
AppendStackFrame(terminal, line);
@@ -581,18 +573,57 @@ private static void FormatExpectedAndActual(ITerminal terminal, string? expected
581573
terminal.ResetColor();
582574
}
583575

584-
private static void FormatErrorMessage(ITerminal terminal, string? errorMessage)
576+
private static void FormatErrorMessage(ITerminal terminal, string? errorMessage, Exception? exception, TestOutcome outcome)
585577
{
586-
if (RoslynString.IsNullOrWhiteSpace(errorMessage))
578+
if (RoslynString.IsNullOrWhiteSpace(errorMessage) && exception is null)
587579
{
588580
return;
589581
}
590582

591583
terminal.SetColor(TerminalColor.Red);
592-
AppendIndentedLine(terminal, errorMessage, SingleIndentation);
584+
585+
if (exception is null)
586+
{
587+
AppendIndentedLine(terminal, errorMessage, SingleIndentation);
588+
}
589+
else if (outcome == TestOutcome.Fail)
590+
{
591+
// For failed tests, we don't prefix the message with the exception type because it is most likely an assertion specific exception like AssertionFailedException, and we prefer to show that without the exception type to avoid additional noise.
592+
AppendIndentedLine(terminal, errorMessage ?? exception.Message, SingleIndentation);
593+
}
594+
else
595+
{
596+
AppendIndentedLine(terminal, $"{exception.GetType().FullName}: {errorMessage ?? exception.Message}", SingleIndentation);
597+
}
598+
593599
terminal.ResetColor();
594600
}
595601

602+
private static void FormatInnerExceptions(ITerminal terminal, Exception? exception)
603+
{
604+
IEnumerable<Exception?> aggregateExceptions = exception switch
605+
{
606+
AggregateException aggregate => aggregate.Flatten().InnerExceptions,
607+
_ => [exception?.InnerException],
608+
};
609+
610+
foreach (Exception? aggregate in aggregateExceptions)
611+
{
612+
Exception? currentException = aggregate;
613+
while (currentException is not null)
614+
{
615+
terminal.SetColor(TerminalColor.Red);
616+
terminal.Append(SingleIndentation);
617+
terminal.Append("--->");
618+
FormatErrorMessage(terminal, null, currentException, TestOutcome.Error);
619+
620+
FormatStackTrace(terminal, currentException);
621+
622+
currentException = currentException.InnerException;
623+
}
624+
}
625+
}
626+
596627
private static void AppendIndentedLine(ITerminal terminal, string? message, string indent)
597628
{
598629
if (RoslynString.IsNullOrWhiteSpace(message))

src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
411411
testNodeStateChanged.TestNode.DisplayName,
412412
TestOutcome.Error,
413413
duration,
414-
errorMessage: errorState.Exception?.Message ?? errorState.Explanation,
415-
errorState.Exception?.StackTrace,
414+
errorState.Explanation,
415+
errorState.Exception,
416416
expected: null,
417417
actual: null);
418418
break;
@@ -425,8 +425,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
425425
testNodeStateChanged.TestNode.DisplayName,
426426
TestOutcome.Fail,
427427
duration,
428-
errorMessage: failedState.Exception?.Message ?? failedState.Explanation,
429-
failedState.Exception?.StackTrace,
428+
failedState.Explanation,
429+
failedState.Exception,
430430
expected: failedState.Exception?.Data["assert.expected"] as string,
431431
actual: failedState.Exception?.Data["assert.actual"] as string);
432432
break;
@@ -439,8 +439,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
439439
testNodeStateChanged.TestNode.DisplayName,
440440
TestOutcome.Timeout,
441441
duration,
442-
errorMessage: timeoutState.Exception?.Message ?? timeoutState.Explanation,
443-
timeoutState.Exception?.StackTrace,
442+
timeoutState.Explanation,
443+
timeoutState.Exception,
444444
expected: null,
445445
actual: null);
446446
break;
@@ -453,8 +453,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
453453
testNodeStateChanged.TestNode.DisplayName,
454454
TestOutcome.Canceled,
455455
duration,
456-
errorMessage: cancelledState.Exception?.Message ?? cancelledState.Explanation,
457-
cancelledState.Exception?.StackTrace,
456+
cancelledState.Explanation,
457+
cancelledState.Exception,
458458
expected: null,
459459
actual: null);
460460
break;
@@ -468,7 +468,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
468468
outcome: TestOutcome.Passed,
469469
duration: duration,
470470
errorMessage: null,
471-
errorStackTrace: null,
471+
exception: null,
472472
expected: null,
473473
actual: null);
474474
break;
@@ -482,7 +482,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
482482
TestOutcome.Skipped,
483483
duration,
484484
errorMessage: null,
485-
errorStackTrace: null,
485+
exception: null,
486486
expected: null,
487487
actual: null);
488488
break;

test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs

+16-13
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,16 @@ public void OutputFormattingIsCorrect()
7070

7171
terminalReporter.AssemblyRunStarted(assembly, targetFramework, architecture);
7272
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "PassedTest1", TestOutcome.Passed, TimeSpan.FromSeconds(10),
73-
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
73+
errorMessage: null, exception: null, expected: null, actual: null);
7474
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "SkippedTest1", TestOutcome.Skipped, TimeSpan.FromSeconds(10),
75-
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
75+
errorMessage: null, exception: null, expected: null, actual: null);
7676
// timed out + cancelled + failed should all report as failed in summary
7777
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "TimedoutTest1", TestOutcome.Timeout, TimeSpan.FromSeconds(10),
78-
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
78+
errorMessage: null, exception: null, expected: null, actual: null);
7979
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "CanceledTest1", TestOutcome.Canceled, TimeSpan.FromSeconds(10),
80-
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
80+
errorMessage: null, exception: null, expected: null, actual: null);
8181
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "FailedTest1", TestOutcome.Fail, TimeSpan.FromSeconds(10),
82-
errorMessage: "Tests failed", errorStackTrace: @$" at FailingTest() in {folder}codefile.cs:line 10", expected: "ABC", actual: "DEF");
82+
errorMessage: "Tests failed", exception: new StackTraceException(@$" at FailingTest() in {folder}codefile.cs:line 10"), expected: "ABC", actual: "DEF");
8383
terminalReporter.ArtifactAdded(outOfProcess: true, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact1.txt");
8484
terminalReporter.ArtifactAdded(outOfProcess: false, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact2.txt");
8585
terminalReporter.AssemblyRunCompleted(assembly, targetFramework, architecture);
@@ -98,9 +98,8 @@ public void OutputFormattingIsCorrect()
9898
ABC
9999
Actual
100100
DEF
101-
␛[m␛[91m Stack Trace:
102-
␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m
103-
101+
␛[m␛[90m ␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m
102+
␛[m
104103
105104
Out of process file artifacts produced:
106105
- ␛[90m␛]8;;file:///{folderLink}artifact1.txt␛\{folder}artifact1.txt␛]8;;␛\␛[m
@@ -115,13 +114,10 @@ public void OutputFormattingIsCorrect()
115114
116115
""";
117116

118-
EnsureAnsiMatch(expected, output);
117+
Assert.AreEqual(expected, ShowEscape(output));
119118
}
120119

121-
private void EnsureAnsiMatch(string expected, string actual)
122-
=> Assert.AreEqual(expected, ShowEscape(actual));
123-
124-
private string? ShowEscape(string? text)
120+
private static string? ShowEscape(string? text)
125121
{
126122
string visibleEsc = "\x241b";
127123
return text?.Replace(AnsiCodes.Esc, visibleEsc);
@@ -229,4 +225,11 @@ public void SetColor(TerminalColor color)
229225

230226
public void StopUpdate() => throw new NotImplementedException();
231227
}
228+
229+
private class StackTraceException : Exception
230+
{
231+
public StackTraceException(string stackTrace) => StackTrace = stackTrace;
232+
233+
public override string? StackTrace { get; }
234+
}
232235
}

0 commit comments

Comments
 (0)