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
22 changes: 11 additions & 11 deletions src/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2085,23 +2085,23 @@ public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecut
/// <summary>
/// Infers generic type mappings from parameter and argument types
/// </summary>
private static void InferGenericTypeMapping(Type paramType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type argType, Dictionary<Type, Type> typeMapping)
private static void InferGenericTypeMapping(Type paramType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type argType, Dictionary<Type, Type> typeMapping, bool isTopLevel = false)
{
// Direct generic parameter
if (paramType.IsGenericParameter)
{
// Check if we already have a mapping for this parameter
if (typeMapping.TryGetValue(paramType, out var existingType))
if (isTopLevel)
{
// If the existing type is more specific (derived from current), keep it
if (existingType.IsAssignableFrom(argType))
{
typeMapping[paramType] = argType;
}
// Otherwise keep the more general type
// A repeated type parameter takes the type of its last non-null declared argument,
// which is what TestGenericTypeResolver and source-generated mode infer. Keeping
// the first (or most specific) type here made the invoker use a different type
// argument from the one the test was named with.
typeMapping[paramType] = argType;
}
else
else if (!typeMapping.TryGetValue(paramType, out var existingType) || existingType.IsAssignableFrom(argType))
{
// Nested inference (e.g. the element type of a params T[]) must not replace a more
// specific type already inferred from a leading T argument.
typeMapping[paramType] = argType;
}
return;
Expand Down Expand Up @@ -2324,7 +2324,7 @@ private static bool MatchesCovariantInterface(Type paramType, Type paramGenericD
{
var paramType = methodParams[j].ParameterType;
var argType = arg.GetType();
InferGenericTypeMapping(paramType, argType, typeMapping);
InferGenericTypeMapping(paramType, argType, typeMapping, isTopLevel: true);
}
}

Expand Down
25 changes: 25 additions & 0 deletions tests/TUnit.Engine.Tests/Issue7121Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

/// <summary>
/// https://github.com/thomhurst/TUnit/issues/7121: a generic test whose type parameter occurs more
/// than once must run with the same type argument reflection mode reports.
/// </summary>
public class Issue7121Tests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task Repeated_Type_Parameter_Runs_With_Reported_Type()
{
await RunTestsWithFilter(
"/*/TUnit.TestProject.Bugs._7121/RepeatedTypeParameterTests/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(1),
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
result => result.Results.Single().TestName.ShouldStartWith("Repeated<int>")
]);
}
}
23 changes: 23 additions & 0 deletions tests/TUnit.TestProject/Bugs/_7121/RepeatedTypeParameterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using TUnit.TestProject.Attributes;

namespace TUnit.TestProject.Bugs._7121;

[EngineTest(ExpectedResult.Pass)]
public class RepeatedTypeParameterTests
{
public static IEnumerable<Func<(string, int)>> Rows()
{
yield return () => ("1", 2);
}

// T occurs twice. Source-gen mode infers T from its last occurrence (int) and converts "1"
// through CastHelper. Reflection mode must instantiate the same type it reports.
[Test]
[MethodDataSource(nameof(Rows))]
public async Task Repeated<T>(T a, T b) where T : struct
{
await Assert.That(typeof(T)).IsEqualTo(typeof(int));
await Assert.That(a).IsEqualTo((T)(object)1);
await Assert.That(b).IsEqualTo((T)(object)2);
}
}
Loading