From a5164fe4b58eb933f550ba547b44ebe528100024 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:44:58 +0000 Subject: [PATCH 1/2] Initial plan From 37665eb1a3c50f2c7e07eddd673d6672a0f5bd72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:09:30 +0000 Subject: [PATCH 2/2] Fix generic DependsOn attribute to correctly extract type from type argument Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../Generators/TestMetadataGenerator.cs | 52 ++++++++++++-- TUnit.TestProject/GenericDependsOnTests.cs | 70 +++++++++++++++++++ 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 TUnit.TestProject/GenericDependsOnTests.cs 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)); + } +}