Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport fix displaying inner exceptions #3965

Merged
merged 6 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:///C:/work/codefile.cs␛\C:\work\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; }
}
}
Loading