diff --git a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs index 8e2e044856..89d65e6431 100644 --- a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs @@ -2164,15 +2164,44 @@ private static void GenerateTestDependency(CodeWriter writer, AttributeData attr // Extract ProceedOnFailure property value var proceedOnFailure = GetProceedOnFailureValue(attributeData); + // Check if this is a generic DependsOnAttribute - extract the type from the type argument + ITypeSymbol? genericTypeArgument = null; + if (attributeData.AttributeClass is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } genericAttr) + { + genericTypeArgument = genericAttr.TypeArguments[0]; + } + // Handle the different constructor overloads of DependsOnAttribute - if (constructorArgs.Length == 1) + if (constructorArgs.Length == 0 && genericTypeArgument != null) + { + // DependsOnAttribute() - dependency on all tests in class T + var className = genericTypeArgument.GloballyQualified(); + var genericArity = genericTypeArgument is INamedTypeSymbol { IsGenericType: true } namedType + ? namedType.Arity + : 0; + writer.AppendLine($"new global::TUnit.Core.TestDependency {{ ClassType = typeof({className}), ClassGenericArity = {genericArity}, ProceedOnFailure = {proceedOnFailure.ToString().ToLower()} }}"); + } + else if (constructorArgs.Length == 1) { var arg = constructorArgs[0]; if (arg.Type?.Name == "String") { - // DependsOnAttribute(string testName) - dependency on test in same class var testName = arg.Value?.ToString() ?? ""; - writer.AppendLine($"new global::TUnit.Core.TestDependency {{ MethodName = \"{testName}\", ProceedOnFailure = {proceedOnFailure.ToString().ToLower()} }}"); + + if (genericTypeArgument != null) + { + // DependsOnAttribute(string testName) - dependency on specific test in class T + var className = genericTypeArgument.GloballyQualified(); + var genericArity = genericTypeArgument is INamedTypeSymbol { IsGenericType: true } namedType + ? namedType.Arity + : 0; + writer.AppendLine($"new global::TUnit.Core.TestDependency {{ ClassType = typeof({className}), ClassGenericArity = {genericArity}, MethodName = \"{testName}\", ProceedOnFailure = {proceedOnFailure.ToString().ToLower()} }}"); + } + else + { + // DependsOnAttribute(string testName) - dependency on test in same class + writer.AppendLine($"new global::TUnit.Core.TestDependency {{ MethodName = \"{testName}\", ProceedOnFailure = {proceedOnFailure.ToString().ToLower()} }}"); + } } else if (arg.Type?.TypeKind == TypeKind.Class || arg.Type?.Name == "Type") { @@ -2194,9 +2223,22 @@ private static void GenerateTestDependency(CodeWriter writer, AttributeData attr if (firstArg.Type?.Name == "String" && secondArg.Type is IArrayTypeSymbol) { - // DependsOnAttribute(string testName, Type[] parameterTypes) var testName = firstArg.Value?.ToString() ?? ""; - writer.Append($"new global::TUnit.Core.TestDependency {{ MethodName = \"{testName}\""); + + if (genericTypeArgument != null) + { + // DependsOnAttribute(string testName, Type[] parameterTypes) - dependency on specific test with parameters in class T + var className = genericTypeArgument.GloballyQualified(); + var genericArity = genericTypeArgument is INamedTypeSymbol { IsGenericType: true } namedType + ? namedType.Arity + : 0; + writer.Append($"new global::TUnit.Core.TestDependency {{ ClassType = typeof({className}), ClassGenericArity = {genericArity}, MethodName = \"{testName}\""); + } + else + { + // DependsOnAttribute(string testName, Type[] parameterTypes) + writer.Append($"new global::TUnit.Core.TestDependency {{ MethodName = \"{testName}\""); + } // Handle parameter types if (secondArg.Values.Length > 0) diff --git a/TUnit.TestProject/GenericDependsOnTests.cs b/TUnit.TestProject/GenericDependsOnTests.cs new file mode 100644 index 0000000000..d13078b431 --- /dev/null +++ b/TUnit.TestProject/GenericDependsOnTests.cs @@ -0,0 +1,70 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject; + +// Test class with test methods to depend on +public class GenericDependsOnTestsClassA +{ + internal static DateTime Test1Start; + + [Test] + public async Task Test1() + { + Test1Start = TestContext.Current!.Execution.TestStart!.Value.DateTime; + await Task.Delay(TimeSpan.FromSeconds(5)); + } +} + +// Test for generic DependsOn with method name: DependsOn(methodName) +[EngineTest(ExpectedResult.Pass)] +public class GenericDependsOnTestsWithClass +{ + private static DateTime _test2Start; + + [Test, DependsOn(nameof(GenericDependsOnTestsClassA.Test1))] + public async Task Test2() + { + _test2Start = TestContext.Current!.Execution.TestStart!.Value.DateTime; + await Task.CompletedTask; + } + + [After(Class)] + public static async Task AssertStartTimes() + { + await Assert.That(_test2Start).IsAfterOrEqualTo(GenericDependsOnTestsClassA.Test1Start.AddSeconds(4.9)); + } +} + +// Test class with test methods to depend on for the second test +public class GenericDependsOnTestsClassB +{ + internal static DateTime Test1Start; + + [Test] + public async Task Test1() + { + Test1Start = TestContext.Current!.Execution.TestStart!.Value.DateTime; + await Task.Delay(TimeSpan.FromSeconds(5)); + } +} + +// Test for generic DependsOn without method name: DependsOn() +// This should depend on all tests in ClassB +[EngineTest(ExpectedResult.Pass)] +public class GenericDependsOnTestsWithClassNoMethod +{ + private static DateTime _test2Start; + + [Test, DependsOn] + public async Task Test2() + { + _test2Start = TestContext.Current!.Execution.TestStart!.Value.DateTime; + await Task.CompletedTask; + } + + [After(Class)] + public static async Task AssertStartTimes() + { + await Assert.That(_test2Start).IsAfterOrEqualTo(GenericDependsOnTestsClassB.Test1Start.AddSeconds(4.9)); + } +}