Skip to content
Merged
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
12 changes: 12 additions & 0 deletions TUnit.Core/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ public void AddAsyncLocalValues()

public string GetStandardOutput()
{
if (_outputBuilder.Length == 0)
{
return string.Empty;
}

_outputLock.EnterReadLock();

try
{
return _outputBuilder.ToString().Trim();
Expand All @@ -82,7 +88,13 @@ public string GetStandardOutput()

public string GetErrorOutput()
{
if (_errorOutputBuilder.Length == 0)
{
return string.Empty;
}

_errorOutputLock.EnterReadLock();

try
{
return _errorOutputBuilder.ToString().Trim();
Expand Down
3 changes: 3 additions & 0 deletions TUnit.Engine/Capabilities/TrxReportCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ internal class TrxReportCapability : ITrxReportCapability
{
public void Enable()
{
IsTrxEnabled = true;
}

public bool IsTrxEnabled { get; private set; }

public bool IsSupported => true;
}
226 changes: 194 additions & 32 deletions TUnit.Engine/Extensions/TestExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,191 @@
using Microsoft.Testing.Extensions.TrxReport.Abstractions;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Extensions.Messages;
using TUnit.Core;
using TUnit.Core.Extensions;
using TUnit.Engine.Capabilities;
#pragma warning disable TPEXP

namespace TUnit.Engine.Extensions;

internal static class TestExtensions
{
internal static TestNode ToTestNode(this TestContext testContext)
internal static TestNode ToTestNode(this TestContext testContext, TestNodeStateProperty stateProperty)
{
var testDetails = testContext.Metadata.TestDetails ?? throw new ArgumentNullException(nameof(testContext.Metadata.TestDetails));

var isFinalState = stateProperty is not DiscoveredTestNodeStateProperty and not InProgressTestNodeStateProperty;

var isTrxEnabled = isFinalState && IsTrxEnabled(testContext);

var estimatedCount = EstimateCount(testContext, stateProperty, isTrxEnabled);

var properties = new List<IProperty>(estimatedCount)
{
stateProperty,

new TestFileLocationProperty(testDetails.TestFilePath, new LinePositionSpan(
new LinePosition(testDetails.TestLineNumber, 0),
new LinePosition(testDetails.TestLineNumber, 0)
)),

new TestMethodIdentifierProperty(
@namespace: testDetails.MethodMetadata.Class.Type.Namespace ?? "",
assemblyFullName: testDetails.MethodMetadata.Class.Type.Assembly.GetName().FullName,
typeName: testContext.GetClassTypeName(),
methodName: testDetails.MethodName,
parameterTypeFullNames: CreateParameterTypeArray(testDetails.MethodMetadata.Parameters.Select(static p => p.Type).ToArray()),
returnTypeFullName: testDetails.ReturnType.FullName ?? typeof(void).FullName!,
methodArity: testDetails.MethodMetadata.GenericTypeCount
)
};

// Custom TUnit Properties
if (testDetails.Categories.Count > 0)
{
properties.AddRange(testDetails.Categories.Select(static category => new TestMetadataProperty(category)));
}

if (testDetails.CustomProperties.Count > 0)
{
properties.AddRange(ExtractProperties(testDetails));
}

// Artifacts
if(isFinalState && testContext.Output.Artifacts.Count > 0)
{
properties.AddRange(testContext.Artifacts.Select(static x => new FileArtifactProperty(x.File, x.DisplayName, x.Description)));
}

string? output = null;
if (isFinalState && testContext.GetStandardOutput() is {} standardOutput && !string.IsNullOrEmpty(standardOutput))
{
output = standardOutput;
properties.Add(new StandardOutputProperty(standardOutput));
}

string? error = null;
if (isFinalState && testContext.GetErrorOutput() is {} standardError && !string.IsNullOrEmpty(standardError))
{
error = standardError;
properties.Add(new StandardErrorProperty(standardError));
}

// TRX Report Properties
if (isFinalState && isTrxEnabled)
{
properties.Add(new TrxFullyQualifiedTypeNameProperty(testDetails.MethodMetadata.Class.Type.FullName ?? testDetails.ClassType.FullName ?? "UnknownType"));

if(testDetails.Categories.Count > 0)
{
properties.Add(new TrxCategoriesProperty([..testDetails.Categories]));
}

if (isFinalState && GetTrxMessages(testContext, output, error).ToArray() is { Length: > 0 } trxMessages)
{
properties.Add(new TrxMessagesProperty(trxMessages));
}

if(stateProperty is ErrorTestNodeStateProperty or FailedTestNodeStateProperty or TimeoutTestNodeStateProperty)
{
var (exception, explanation) = GetException(stateProperty);

if (exception is not null)
{
properties.Add(new TrxExceptionProperty(exception.Message, exception.StackTrace));
}
else if (!string.IsNullOrEmpty(explanation))
{
properties.Add(new TrxExceptionProperty(explanation, string.Empty));
}
}
}

if(isFinalState)
{
properties.Add(GetTimingProperty(testContext, testContext.Execution.TestStart.GetValueOrDefault()));
}

var testNode = new TestNode
{
Uid = new TestNodeUid(testDetails.TestId),
DisplayName = testContext.GetDisplayName(),
Properties = new PropertyBag(
[
new TestFileLocationProperty(testDetails.TestFilePath, new LinePositionSpan(
new LinePosition(testDetails.TestLineNumber, 0),
new LinePosition(testDetails.TestLineNumber, 0)
)),
new TestMethodIdentifierProperty(
@namespace: testDetails.MethodMetadata.Class.Type.Namespace ?? "",
assemblyFullName: testDetails.MethodMetadata.Class.Type.Assembly.GetName().FullName,
typeName: testContext.GetClassTypeName(),
methodName: testDetails.MethodName,
parameterTypeFullNames: CreateParameterTypeArray(testDetails.MethodMetadata.Parameters.Select(static p => p.Type).ToArray()),
returnTypeFullName: testDetails.ReturnType.FullName ?? typeof(void).FullName!,
methodArity: testDetails.MethodMetadata.GenericTypeCount
),

// Custom TUnit Properties
..testDetails.Categories.Select(static category => new TestMetadataProperty(category)),
..ExtractProperties(testDetails),

// Artifacts
..testContext.Artifacts.Select(static x => new FileArtifactProperty(x.File, x.DisplayName, x.Description)),

// TRX Report Properties
new TrxFullyQualifiedTypeNameProperty(testDetails.MethodMetadata.Class?.Type.FullName ?? testDetails.ClassType?.FullName ?? "UnknownType"),
new TrxCategoriesProperty([..testDetails.Categories]),
])
Properties = new PropertyBag(properties)
};

return testNode;
}

public static IEnumerable<TestMetadataProperty> ExtractProperties(this TestDetails testDetails)
private static (Exception? Exception, string? Reason) GetException(TestNodeStateProperty stateProperty)
{
if (stateProperty is ErrorTestNodeStateProperty errorState)
{
return (errorState.Exception, errorState.Explanation);
}

if (stateProperty is FailedTestNodeStateProperty failedState)
{
return (failedState.Exception, failedState.Explanation);
}

if (stateProperty is TimeoutTestNodeStateProperty timeoutState)
{
return (timeoutState.Exception, timeoutState.Explanation);
}

return (null, null);
}

private static int EstimateCount(TestContext testContext, TestNodeStateProperty stateProperty, bool isTrxEnabled)
{
var isFinalState = stateProperty is not DiscoveredTestNodeStateProperty and not InProgressTestNodeStateProperty;

var testDetails = testContext.Metadata.TestDetails ?? throw new ArgumentNullException(nameof(testContext.Metadata.TestDetails));

var count = 3; // State + FileLocation + MethodIdentifier

count += testDetails.CustomProperties.Count;
count += testDetails.Categories.Count;

if (isFinalState)
{
count += 3; // Timing + StdOut + StdErr;
}

if (isTrxEnabled)
{
count += 2; // TRX TypeName + TRX Messages

if(testDetails.Categories.Count > 0)
{
count += 1; // TRX Categories
}

if(stateProperty is ErrorTestNodeStateProperty or FailedTestNodeStateProperty or TimeoutTestNodeStateProperty)
{
count += 1; // Trx Exception
}
}

return count;
}

private static bool IsTrxEnabled(TestContext testContext)
{
if(testContext.Services.GetService<ITestFrameworkCapabilities>() is not {} capabilities)
{
return false;
}

if(capabilities.GetCapability<ITrxReportCapability>() is not TrxReportCapability trxCapability)
{
return false;
}

return trxCapability.IsTrxEnabled;
}

private static IEnumerable<TestMetadataProperty> ExtractProperties(TestDetails testDetails)
{
foreach (var propertyGroup in testDetails.CustomProperties)
{
Expand All @@ -58,10 +196,34 @@ public static IEnumerable<TestMetadataProperty> ExtractProperties(this TestDetai
}
}

internal static TestNode WithProperty(this TestNode testNode, IProperty property)
private static TimingProperty GetTimingProperty(TestContext testContext, DateTimeOffset overallStart)
{
testNode.Properties.Add(property);
return testNode;
if (overallStart == default(DateTimeOffset))
{
return new TimingProperty(new TimingInfo());
}

var end = testContext.Execution.TestEnd ?? DateTimeOffset.Now;

return new TimingProperty(new TimingInfo(overallStart, end, end - overallStart), testContext.Timings.Select(x => new StepTimingInfo(x.StepName, x.StepName, new TimingInfo(x.Start, x.End, x.Duration))).ToArray());
}

private static IEnumerable<TrxMessage> GetTrxMessages(TestContext testContext, string? standardOutput, string? standardError)
{
if (!string.IsNullOrEmpty(standardOutput))
{
yield return new StandardOutputTrxMessage(standardOutput);
}

if (!string.IsNullOrEmpty(standardError))
{
yield return new StandardErrorTrxMessage(standardError);
}

if (!string.IsNullOrEmpty(testContext.SkipReason))
{
yield return new DebugOrTraceTrxMessage($"Skipped: {testContext.SkipReason}");
}
}

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions TUnit.Engine/Framework/TUnitServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public TUnitServiceProvider(IExtension extension,

TestContext.Configuration = new ConfigurationAdapter(configuration);

// Register capabilities so they're available to test contexts
Register<ITestFrameworkCapabilities>(capabilities);

VerbosityService = Register(new VerbosityService(CommandLineOptions, frameworkServiceProvider));

var logLevelProvider = Register(new LogLevelProvider(CommandLineOptions));
Expand Down
10 changes: 0 additions & 10 deletions TUnit.Engine/Models/DiscoveredTestExtensions.cs

This file was deleted.

Loading
Loading