diff --git a/TUnit.Core/TestDependency.cs b/TUnit.Core/TestDependency.cs index 5fa0ff358d..3c73db35be 100644 --- a/TUnit.Core/TestDependency.cs +++ b/TUnit.Core/TestDependency.cs @@ -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 diff --git a/TUnit.UnitTests/ComplexGenericInheritanceTests.cs b/TUnit.UnitTests/ComplexGenericInheritanceTests.cs new file mode 100644 index 0000000000..e285f49a96 --- /dev/null +++ b/TUnit.UnitTests/ComplexGenericInheritanceTests.cs @@ -0,0 +1,125 @@ +using TUnit.Core; + +namespace TUnit.UnitTests; + +public class ComplexGenericInheritanceTests +{ + private static TestMetadata CreateTestMetadata(string testId, string methodName) where T : class + { + return new TestMetadata + { + 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("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("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("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("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 : 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 : SecondClassBase + 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 + { + public Task Test300() => Task.Delay(50); + public Task Test301() => Task.Delay(50); + public Task Test302() => Task.Delay(50); + } +} \ No newline at end of file diff --git a/TUnit.UnitTests/DependsOnTests.cs b/TUnit.UnitTests/DependsOnTests.cs index fed6b013d5..ec25a78992 100644 --- a/TUnit.UnitTests/DependsOnTests.cs +++ b/TUnit.UnitTests/DependsOnTests.cs @@ -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("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("test1", "Test300"); + + var matches = dependency.Matches(testMetadata); + + await Assert.That(matches).IsTrue(); + } + [Test] public async Task TestDependency_FromClassAndMethod_MatchesInheritedMethod() { @@ -238,4 +263,35 @@ public class DerivedFromGenericClass : GenericBaseClass [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 : 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 : SecondClassBase + 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 + { + public Task Test300() => Task.Delay(50); + public Task Test301() => Task.Delay(50); + public Task Test302() => Task.Delay(50); + } } diff --git a/global.json b/global.json index b777c9bed9..39667c87d8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.304", + "version": "9.0.303", "rollForward": "latestFeature" } }