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
4 changes: 2 additions & 2 deletions src/TUnit.Engine/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static TestClassJson ToJsonModel(this ClassHookContext context)

return new TestClassJson
{
Type = context.ClassType.FullName,
Type = TestExtensions.GetReportedClassFullName(context.ClassType),
Tests = tests
};
}
Expand Down Expand Up @@ -83,7 +83,7 @@ public static TestJson ToJsonModel(this TestContext context)
return new TestJson
{
Categories = testDetails.Categories,
ClassType = testDetails.MethodMetadata.Class.Type.FullName ?? testDetails.ClassType.FullName ?? "Unknown",
ClassType = TestExtensions.GetReportedClassFullName(testDetails.MethodMetadata.Class.Type) ?? TestExtensions.GetReportedClassFullName(testDetails.ClassType) ?? "Unknown",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Result = context.Execution.Result?.ToJsonModel(),
Timeout = testDetails.Timeout,
CustomProperties = testDetails.CustomProperties.ToDictionary(
Expand Down
14 changes: 13 additions & 1 deletion src/TUnit.Engine/Extensions/TestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static CachedTestNodeProperties CreateCachedProperties(TestContext testContext,
}
}

var trxTypeName = testDetails.MethodMetadata.Class.Type.FullName ?? testDetails.ClassType.FullName ?? "UnknownType";
var trxTypeName = GetReportedClassFullName(testDetails.MethodMetadata.Class.Type) ?? GetReportedClassFullName(testDetails.ClassType) ?? "UnknownType";

TrxCategoriesProperty? trxCategories = null;
if (categories is { Count: > 0 })
Expand Down Expand Up @@ -383,6 +383,18 @@ private static TrxMessage[] GetTrxMessages(TestContext testContext, string? stan
return result;
}

/// <summary>
/// The class name reported to TRX and JSON output. A constructed generic class is reported by its
/// generic type definition (<c>Ns.Class`1</c>), as source-generated mode already does, so every
/// instantiation shares one name and reflection mode does not leak assembly-qualified type arguments.
/// </summary>
internal static string? GetReportedClassFullName(Type type)
{
return type.IsConstructedGenericType
? type.GetGenericTypeDefinition().FullName
: type.FullName;
}

private static string[] CreateParameterTypeArray(ParameterMetadata[] parameters)
{
if (parameters.Length == 0)
Expand Down
29 changes: 29 additions & 0 deletions tests/TUnit.Engine.Tests/Issue7151Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

/// <summary>
/// The TRX className of a test on a generic class is the generic type definition's full name
/// (<c>Ns.Class`1</c>) in both modes, so every instantiation groups under one class (#7151).
/// Reflection mode used to report the closed type with assembly-qualified type arguments.
/// </summary>
public class Issue7151Tests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
[Arguments("GenericClassSameTypeRows", 3)]
[Arguments("GenericClassMixedTypeRows", 4)]
[Arguments("GenericClassMixedTypeMethodRows", 3)]
[Arguments("GenerateGenericTestWithMethodRows", 2)]
public async Task Generic_Class_Trx_ClassName_Is_The_Generic_Type_Definition(string className, int expectedTests)
{
await RunTestsWithFilter(
$"/*/TUnit.TestProject.Bugs._7106/{className}/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Passed.ShouldBe(expectedTests),
result => result.TestDefinitions.Select(x => x.TestMethod.ClassName).Distinct().ToArray()
.ShouldBe(new[] { $"TUnit.TestProject.Bugs._7106.{className}`1" })
Comment thread
thomhurst marked this conversation as resolved.
Resolved
]);
}
}
34 changes: 34 additions & 0 deletions tests/TUnit.UnitTests/ReportedClassFullNameTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using TUnit.Engine.Extensions;

namespace TUnit.UnitTests;

/// <summary>
/// The class name reported for a test (TRX className and the JSON model's ClassType) is the generic
/// type definition's full name, so reflection mode's closed types report the same name as
/// source-generated mode's open ones (#7151).
/// </summary>
public class ReportedClassFullNameTests
{
[Test]
public async Task Closed_Generic_Type_Reports_Its_Generic_Type_Definition()
{
await Assert.That(TestExtensions.GetReportedClassFullName(typeof(Holder<int>)))
.IsEqualTo("TUnit.UnitTests.ReportedClassFullNameTests+Holder`1");
}

[Test]
public async Task Open_Generic_Type_Reports_Its_Full_Name()
{
await Assert.That(TestExtensions.GetReportedClassFullName(typeof(Holder<>)))
.IsEqualTo("TUnit.UnitTests.ReportedClassFullNameTests+Holder`1");
}

[Test]
public async Task Non_Generic_Type_Reports_Its_Full_Name()
{
await Assert.That(TestExtensions.GetReportedClassFullName(typeof(ReportedClassFullNameTests)))
.IsEqualTo("TUnit.UnitTests.ReportedClassFullNameTests");
}

private sealed class Holder<T>;
}
Loading