Skip to content

Commit

Permalink
Backport fix displaying inner exceptions (#3965)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evangelink authored Oct 30, 2024
1 parent d0b9266 commit 9aaa6ee
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ internal void TestCompleted(
TestOutcome outcome,
TimeSpan duration,
string? errorMessage,
string? errorStackTrace,
Exception? exception,
string? expected,
string? actual)
{
Expand Down Expand Up @@ -410,7 +410,7 @@ internal void TestCompleted(
outcome,
duration,
errorMessage,
errorStackTrace,
exception,
expected,
actual));
}
Expand All @@ -425,7 +425,7 @@ internal void TestCompleted(
TestOutcome outcome,
TimeSpan duration,
string? errorMessage,
string? errorStackTrace,
Exception? exception,
string? expected,
string? actual)
{
Expand Down Expand Up @@ -469,9 +469,10 @@ internal void TestCompleted(

terminal.AppendLine();

FormatErrorMessage(terminal, errorMessage);
FormatErrorMessage(terminal, errorMessage, exception, outcome);
FormatExpectedAndActual(terminal, expected, actual);
FormatStackTrace(terminal, errorStackTrace);
FormatStackTrace(terminal, exception);
FormatInnerExceptions(terminal, exception);
}

private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal terminal, string assembly, string? targetFramework, string? architecture)
Expand All @@ -495,25 +496,16 @@ private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal t
}
}

private static void FormatStackTrace(ITerminal terminal, string? errorStackTrace)
private static void FormatStackTrace(ITerminal terminal, Exception? exception)
{
if (RoslynString.IsNullOrWhiteSpace(errorStackTrace))
if (exception?.StackTrace is not { } stackTrace)
{
return;
}

terminal.SetColor(TerminalColor.Red);
terminal.Append(SingleIndentation);
terminal.Append(PlatformResources.StackTrace);
terminal.AppendLine(":");

if (!errorStackTrace.Contains('\n'))
{
AppendStackFrame(terminal, errorStackTrace);
return;
}
terminal.SetColor(TerminalColor.DarkGray);

string[] lines = errorStackTrace.Split(NewLineStrings, StringSplitOptions.None);
string[] lines = stackTrace.Split(NewLineStrings, StringSplitOptions.None);
foreach (string line in lines)
{
AppendStackFrame(terminal, line);
Expand Down Expand Up @@ -581,18 +573,57 @@ private static void FormatExpectedAndActual(ITerminal terminal, string? expected
terminal.ResetColor();
}

private static void FormatErrorMessage(ITerminal terminal, string? errorMessage)
private static void FormatErrorMessage(ITerminal terminal, string? errorMessage, Exception? exception, TestOutcome outcome)
{
if (RoslynString.IsNullOrWhiteSpace(errorMessage))
if (RoslynString.IsNullOrWhiteSpace(errorMessage) && exception is null)
{
return;
}

terminal.SetColor(TerminalColor.Red);
AppendIndentedLine(terminal, errorMessage, SingleIndentation);

if (exception is null)
{
AppendIndentedLine(terminal, errorMessage, SingleIndentation);
}
else if (outcome == TestOutcome.Fail)
{
// 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.
AppendIndentedLine(terminal, errorMessage ?? exception.Message, SingleIndentation);
}
else
{
AppendIndentedLine(terminal, $"{exception.GetType().FullName}: {errorMessage ?? exception.Message}", SingleIndentation);
}

terminal.ResetColor();
}

private static void FormatInnerExceptions(ITerminal terminal, Exception? exception)
{
IEnumerable<Exception?> aggregateExceptions = exception switch
{
AggregateException aggregate => aggregate.Flatten().InnerExceptions,
_ => [exception?.InnerException],
};

foreach (Exception? aggregate in aggregateExceptions)
{
Exception? currentException = aggregate;
while (currentException is not null)
{
terminal.SetColor(TerminalColor.Red);
terminal.Append(SingleIndentation);
terminal.Append("--->");
FormatErrorMessage(terminal, null, currentException, TestOutcome.Error);

FormatStackTrace(terminal, currentException);

currentException = currentException.InnerException;
}
}
}

private static void AppendIndentedLine(ITerminal terminal, string? message, string indent)
{
if (RoslynString.IsNullOrWhiteSpace(message))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
testNodeStateChanged.TestNode.DisplayName,
TestOutcome.Error,
duration,
errorMessage: errorState.Exception?.Message ?? errorState.Explanation,
errorState.Exception?.StackTrace,
errorState.Explanation,
errorState.Exception,
expected: null,
actual: null);
break;
Expand All @@ -425,8 +425,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
testNodeStateChanged.TestNode.DisplayName,
TestOutcome.Fail,
duration,
errorMessage: failedState.Exception?.Message ?? failedState.Explanation,
failedState.Exception?.StackTrace,
failedState.Explanation,
failedState.Exception,
expected: failedState.Exception?.Data["assert.expected"] as string,
actual: failedState.Exception?.Data["assert.actual"] as string);
break;
Expand All @@ -439,8 +439,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
testNodeStateChanged.TestNode.DisplayName,
TestOutcome.Timeout,
duration,
errorMessage: timeoutState.Exception?.Message ?? timeoutState.Explanation,
timeoutState.Exception?.StackTrace,
timeoutState.Explanation,
timeoutState.Exception,
expected: null,
actual: null);
break;
Expand All @@ -453,8 +453,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
testNodeStateChanged.TestNode.DisplayName,
TestOutcome.Canceled,
duration,
errorMessage: cancelledState.Exception?.Message ?? cancelledState.Explanation,
cancelledState.Exception?.StackTrace,
cancelledState.Explanation,
cancelledState.Exception,
expected: null,
actual: null);
break;
Expand All @@ -468,7 +468,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
outcome: TestOutcome.Passed,
duration: duration,
errorMessage: null,
errorStackTrace: null,
exception: null,
expected: null,
actual: null);
break;
Expand All @@ -482,7 +482,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
TestOutcome.Skipped,
duration,
errorMessage: null,
errorStackTrace: null,
exception: null,
expected: null,
actual: null);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ public void OutputFormattingIsCorrect()

terminalReporter.AssemblyRunStarted(assembly, targetFramework, architecture);
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "PassedTest1", TestOutcome.Passed, TimeSpan.FromSeconds(10),
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
errorMessage: null, exception: null, expected: null, actual: null);
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "SkippedTest1", TestOutcome.Skipped, TimeSpan.FromSeconds(10),
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
errorMessage: null, exception: null, expected: null, actual: null);
// timed out + cancelled + failed should all report as failed in summary
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "TimedoutTest1", TestOutcome.Timeout, TimeSpan.FromSeconds(10),
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
errorMessage: null, exception: null, expected: null, actual: null);
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "CanceledTest1", TestOutcome.Canceled, TimeSpan.FromSeconds(10),
errorMessage: null, errorStackTrace: null, expected: null, actual: null);
errorMessage: null, exception: null, expected: null, actual: null);
terminalReporter.TestCompleted(assembly, targetFramework, architecture, "FailedTest1", TestOutcome.Fail, TimeSpan.FromSeconds(10),
errorMessage: "Tests failed", errorStackTrace: @$" at FailingTest() in {folder}codefile.cs:line 10", expected: "ABC", actual: "DEF");
errorMessage: "Tests failed", exception: new StackTraceException(@$" at FailingTest() in {folder}codefile.cs:line 10"), expected: "ABC", actual: "DEF");
terminalReporter.ArtifactAdded(outOfProcess: true, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact1.txt");
terminalReporter.ArtifactAdded(outOfProcess: false, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact2.txt");
terminalReporter.AssemblyRunCompleted(assembly, targetFramework, architecture);
Expand All @@ -98,9 +98,8 @@ public void OutputFormattingIsCorrect()
ABC
Actual
DEF
␛[m␛[91m Stack Trace:
␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m
␛[m␛[90m ␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m
␛[m
Out of process file artifacts produced:
- ␛[90m␛]8;;file:///{folderLink}artifact1.txt␛\{folder}artifact1.txt␛]8;;␛\␛[m
Expand All @@ -115,13 +114,10 @@ public void OutputFormattingIsCorrect()
""";

EnsureAnsiMatch(expected, output);
Assert.AreEqual(expected, ShowEscape(output));
}

private void EnsureAnsiMatch(string expected, string actual)
=> Assert.AreEqual(expected, ShowEscape(actual));

private string? ShowEscape(string? text)
private static string? ShowEscape(string? text)
{
string visibleEsc = "\x241b";
return text?.Replace(AnsiCodes.Esc, visibleEsc);
Expand Down Expand Up @@ -229,4 +225,11 @@ public void SetColor(TerminalColor color)

public void StopUpdate() => throw new NotImplementedException();
}

private class StackTraceException : Exception
{
public StackTraceException(string stackTrace) => StackTrace = stackTrace;

public override string? StackTrace { get; }
}
}

0 comments on commit 9aaa6ee

Please sign in to comment.