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
31 changes: 26 additions & 5 deletions TUnit.Core/TestDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,53 @@ public bool Matches(TestMetadata test, TestMetadata? dependentTest = null)
{
var testClassType = test.TestClassType;

if (ClassType.IsGenericTypeDefinition && testClassType.IsGenericType)
if (ClassType.IsGenericTypeDefinition)
{
if (testClassType.GetGenericTypeDefinition() != ClassType)
// Early exit if test class type has no generic inheritance at all
if (!testClassType.IsGenericType && testClassType.BaseType == null)
{
var currentType = testClassType.BaseType;
return false;
}

// Quick check: if test class type is generic and matches directly, we're done
if (testClassType.IsGenericType && testClassType.GetGenericTypeDefinition() == ClassType)
{
// Direct match found, continue to method checks
}
else
{
// Only traverse inheritance if we have a base type and it's likely to be generic
var found = false;
while (currentType != null && !found)
var currentType = testClassType.BaseType;
var depth = 0;
const int maxInheritanceDepth = 10; // Reduced from 50 to 10 for better performance

while (currentType != null && !found && depth < maxInheritanceDepth)
{
// Only check generic types to avoid unnecessary reflection calls
if (currentType.IsGenericType && currentType.GetGenericTypeDefinition() == ClassType)
{
found = true;
}
currentType = currentType.BaseType;
depth++;
}

if (!found)
{
return false;
}
}

// For generic type definitions, we don't need to check arity against the test class
// because the test class may inherit from a closed generic version of the dependency
}
else if (!ClassType.IsAssignableFrom(testClassType))
{
return false;
}

if (ClassGenericArity > 0)
if (ClassGenericArity > 0 && !ClassType.IsGenericTypeDefinition)
{
var testGenericArgs = testClassType.IsGenericType
? testClassType.GetGenericArguments().Length
Expand Down
125 changes: 125 additions & 0 deletions TUnit.UnitTests/ComplexGenericInheritanceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using TUnit.Core;

namespace TUnit.UnitTests;

public class ComplexGenericInheritanceTests
{
private static TestMetadata<T> CreateTestMetadata<T>(string testId, string methodName) where T : class
{
return new TestMetadata<T>
{
TestClassType = typeof(T),
TestMethodName = methodName,
TestName = methodName,
FilePath = "Unknown",
LineNumber = 0,
AttributeFactory = () => [],
MethodMetadata = new MethodMetadata
{
Type = typeof(T),
TypeReference = TypeReference.CreateConcrete(typeof(T).AssemblyQualifiedName ?? typeof(T).FullName ?? typeof(T).Name),
Name = methodName,
GenericTypeCount = 0,
ReturnType = typeof(void),
ReturnTypeReference = TypeReference.CreateConcrete(typeof(void).AssemblyQualifiedName ?? "System.Void"),
Parameters = [],
Class = new ClassMetadata
{
Type = typeof(T),
TypeReference = TypeReference.CreateConcrete(typeof(T).AssemblyQualifiedName ?? typeof(T).FullName ?? typeof(T).Name),
Name = typeof(T).Name,
Namespace = typeof(T).Namespace ?? string.Empty,
Assembly = new AssemblyMetadata
{
Name = typeof(T).Assembly.GetName().Name ?? string.Empty
},
Parent = null,
Parameters = [],
Properties = []
}
},
DataSources = [],
ClassDataSources = [],
PropertyDataSources = []
};
}

[Test]
public async Task TestDependency_ProblemStatement_FirstClassBaseDependency()
{
// Test that FirstClassBase dependency matches ComplexGenericDependsOn tests
var dependency = TestDependency.FromClass(typeof(FirstClassBase));
var testMetadata = CreateTestMetadata<ComplexGenericDependsOn>("test1", "Test300");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_ProblemStatement_SecondClassBaseDependency()
{
// Test that SecondClassBase<,> dependency matches ComplexGenericDependsOn tests
var dependency = TestDependency.FromClass(typeof(SecondClassBase<,>));
var testMetadata = CreateTestMetadata<ComplexGenericDependsOn>("test1", "Test300");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_ProblemStatement_ThirdClassBaseDependency()
{
// Test that ThirdClassBase<,> dependency matches ComplexGenericDependsOn tests
var dependency = TestDependency.FromClass(typeof(ThirdClassBase<,>));
var testMetadata = CreateTestMetadata<ComplexGenericDependsOn>("test1", "Test300");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_ProblemStatement_ConcreteClassDependency()
{
// Test that ComplexGenericDependsOn dependency matches its own tests
var dependency = TestDependency.FromClass(typeof(ComplexGenericDependsOn));
var testMetadata = CreateTestMetadata<ComplexGenericDependsOn>("test1", "Test300");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

// Test classes from the problem statement
public abstract class FirstClassBase
{
public Task Test001() => Task.Delay(50);
public Task Test002() => Task.Delay(50);
public Task Test003() => Task.Delay(50);
}

public abstract class SecondClassBase<TContent, TValue> : FirstClassBase
where TContent : class
{
public Task Test100() => Task.Delay(50);
public Task Test101() => Task.Delay(50);
public Task Test102() => Task.Delay(50);
}

public abstract class ThirdClassBase<TContent, TValue> : SecondClassBase<TContent, TValue>
where TContent : class
{
public Task Test200() => Task.Delay(50);
public Task Test201() => Task.Delay(50);
public Task Test202() => Task.Delay(50);
}

public class ComplexGenericDependsOn : ThirdClassBase<string, int>
{
public Task Test300() => Task.Delay(50);
public Task Test301() => Task.Delay(50);
public Task Test302() => Task.Delay(50);
}
}
56 changes: 56 additions & 0 deletions TUnit.UnitTests/DependsOnTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ public async Task TestDependency_WithGenericBase_MatchesInheritedTests()
await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_WithOpenGenericBase_MatchesInheritedTests()
{
// Test with open generic type (like in the problem statement)
var dependency = TestDependency.FromClass(typeof(GenericBaseClass<>));
var testMetadata = CreateTestMetadata<DerivedFromGenericClass>("test1", "Test");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_ComplexGenericInheritance_MatchesCorrectly()
{
// Test complex inheritance like in the problem statement
// Testing if SecondClassBase<,> dependency matches ComplexGenericDependsOn tests
var dependency = TestDependency.FromClass(typeof(SecondClassBase<,>));
var testMetadata = CreateTestMetadata<ComplexGenericDependsOn>("test1", "Test300");

var matches = dependency.Matches(testMetadata);

await Assert.That(matches).IsTrue();
}

[Test]
public async Task TestDependency_FromClassAndMethod_MatchesInheritedMethod()
{
Expand Down Expand Up @@ -238,4 +263,35 @@ public class DerivedFromGenericClass : GenericBaseClass<string>
[Test]
public void Test() { }
}

// Add classes to reproduce the problem statement
public abstract class FirstClassBase
{
public Task Test001() => Task.Delay(50);
public Task Test002() => Task.Delay(50);
public Task Test003() => Task.Delay(50);
}

public abstract class SecondClassBase<TContent, TValue> : FirstClassBase
where TContent : class
{
public Task Test100() => Task.Delay(50);
public Task Test101() => Task.Delay(50);
public Task Test102() => Task.Delay(50);
}

public abstract class ThirdClassBase<TContent, TValue> : SecondClassBase<TContent, TValue>
where TContent : class
{
public Task Test200() => Task.Delay(50);
public Task Test201() => Task.Delay(50);
public Task Test202() => Task.Delay(50);
}

public class ComplexGenericDependsOn : ThirdClassBase<string, int>
{
public Task Test300() => Task.Delay(50);
public Task Test301() => Task.Delay(50);
public Task Test302() => Task.Delay(50);
}
}
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.304",
"version": "9.0.303",
"rollForward": "latestFeature"
}
}
Loading