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

Print full exception details in error message #2368

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions src/xunit.v3.common/v3/Abstractions/_IErrorMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public interface _IErrorMetadata
/// </summary>
string[] Messages { get; }

/// <summary>
/// Gets the details of the exception(s).
/// </summary>
string? Details { get; }

/// <summary>
/// Gets the stack trace(s) of the exception(s).
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_ErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _ErrorMessage : _MessageSinkMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand All @@ -56,6 +64,7 @@ public static _ErrorMessage FromException(Exception ex)
{
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestAssemblyCleanupFailure : _TestAssemblyMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand All @@ -61,6 +69,7 @@ public string[] Messages
AssemblyUniqueID = assemblyUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_TestCaseCleanupFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestCaseCleanupFailure : _TestCaseMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -75,6 +83,7 @@ public string[] Messages
TestCaseUniqueID = testCaseUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_TestClassCleanupFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestClassCleanupFailure : _TestClassMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -68,6 +76,7 @@ public string[] Messages
TestClassUniqueID = testClassUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_TestCleanupFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestCleanupFailure : _TestMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -79,6 +87,7 @@ public string[] Messages
TestUniqueID = testUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestCollectionCleanupFailure : _TestCollectionMessage, _IErrorMeta
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -65,6 +73,7 @@ public string[] Messages
TestCollectionUniqueID = testCollectionUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_TestFailed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class _TestFailed : _TestResultMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <summary>
Expand Down Expand Up @@ -49,6 +50,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -101,6 +109,7 @@ public string[] Messages
Output = output ?? string.Empty,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
9 changes: 9 additions & 0 deletions src/xunit.v3.common/v3/Messages/_TestMethodCleanupFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class _TestMethodCleanupFailure : _TestMethodMessage, _IErrorMetadata
int[]? exceptionParentIndices;
string?[]? exceptionTypes;
string[]? messages;
string? details;
string?[]? stackTraces;

/// <inheritdoc/>
Expand All @@ -35,6 +36,13 @@ public string[] Messages
set => messages = Guard.ArgumentNotNullOrEmpty(nameof(Messages), value);
}

/// <inheritdoc/>
public string? Details
{
get => details ?? throw new InvalidOperationException($"Attempted to get {nameof(Details)} on an uninitialized '{GetType().FullName} object");
set => details = Guard.ArgumentNotNull(nameof(Details), value);
}

/// <inheritdoc/>
public string?[] StackTraces
{
Expand Down Expand Up @@ -71,6 +79,7 @@ public string[] Messages
TestMethodUniqueID = testMethodUniqueID,
ExceptionTypes = errorMetadata.ExceptionTypes,
Messages = errorMetadata.Messages,
Details = ex.ToString(),
StackTraces = errorMetadata.StackTraces,
ExceptionParentIndices = errorMetadata.ExceptionParentIndices,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,26 @@ protected _ITestFrameworkExecutionOptions GetExecutionOptions(string? assemblyFi
foreach (var messageLine in ExceptionUtility.CombineMessages(errorMetadata).Split(new[] { Environment.NewLine }, StringSplitOptions.None))
Logger.LogImportantMessage(frameInfo, $" {messageLine}");

LogStackTrace(frameInfo, ExceptionUtility.CombineStackTraces(errorMetadata));
LogDetails(frameInfo, errorMetadata.Details);
}
}

/// <summary>
/// Logs the exception details to the logger.
/// </summary>
protected virtual void LogDetails(
StackFrameInfo frameInfo,
string? details)
{
if (string.IsNullOrEmpty(details))
return;

Logger.LogMessage(frameInfo, " Details:");

foreach (var messageLine in details.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
Logger.LogImportantMessage(frameInfo, $" {messageLine}");
}

/// <summary>
/// Logs a stack trace to the logger.
/// </summary>
Expand Down Expand Up @@ -492,7 +508,7 @@ protected virtual void HandleTestFailed(MessageHandlerArgs<_TestFailed> args)
foreach (var messageLine in ExceptionUtility.CombineMessages(testFailed).Split(new[] { Environment.NewLine }, StringSplitOptions.None))
Logger.LogImportantMessage(frameInfo, $" {messageLine}");

LogStackTrace(frameInfo, ExceptionUtility.CombineStackTraces(testFailed));
LogDetails(frameInfo, testFailed.Details);
LogOutput(frameInfo, testFailed.Output);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ class ErrorMetadata : _IErrorMetadata, IEnumerable
readonly List<int> exceptionParentIndices = new List<int>();
readonly List<string?> exceptionTypes = new List<string?>();
readonly List<string> messages = new List<string>();
string? details = null;
readonly List<string?> stackTraces = new List<string?>();

public string?[] ExceptionTypes => exceptionTypes.ToArray();

public string[] Messages => messages.ToArray();

public string? Details => details;

public string?[] StackTraces => stackTraces.ToArray();

public int[] ExceptionParentIndices => exceptionParentIndices.ToArray();
Expand All @@ -26,6 +29,7 @@ class ErrorMetadata : _IErrorMetadata, IEnumerable
Exception ex,
int index = -1)
{
details ??= ex.ToString();
Add(ex.GetType(), ex.Message, ex.StackTrace, index);
}

Expand Down
14 changes: 7 additions & 7 deletions src/xunit.v3.runner.utility/Runners/AssemblyRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,19 +394,19 @@ bool _IMessageSink.OnMessage(_MessageSinkMessage message)

if (OnErrorMessage != null)
{
if (DispatchMessage<_ErrorMessage>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.CatastrophicError, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_ErrorMessage>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.CatastrophicError, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestAssemblyCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestAssemblyCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestAssemblyCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestAssemblyCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestCaseCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCaseCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestCaseCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCaseCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestClassCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestClassCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestClassCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestClassCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestCollectionCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCollectionCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestCollectionCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCollectionCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<_TestMethodCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestMethodCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
if (DispatchMessage<_TestMethodCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestMethodCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.Details, m.StackTraces.FirstOrDefault()))))
return !cancelled;
}

Expand Down
7 changes: 7 additions & 0 deletions src/xunit.v3.runner.utility/Runners/ErrorMessageInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ public class ErrorMessageInfo
ErrorMessageType messageType,
string? exceptionType,
string? exceptionMessage,
string? exceptionDetails,
string? exceptionStackTrace)
{
MesssageType = messageType;
ExceptionType = exceptionType;
ExceptionMessage = exceptionMessage;
ExceptionDetails = exceptionDetails;
ExceptionStackTrace = exceptionStackTrace;
}

Expand All @@ -33,6 +35,11 @@ public class ErrorMessageInfo
/// </summary>
public string? ExceptionMessage { get; }

/// <summary>
/// The full details from the exception that caused the test failure.
/// </summary>
public string? ExceptionDetails { get; }

/// <summary>
/// The stack trace from the exception that caused the test failure.
/// </summary>
Expand Down