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
52 changes: 47 additions & 5 deletions TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> - 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<T>() - 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<T>(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")
{
Expand All @@ -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<T>(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)
Expand Down
70 changes: 70 additions & 0 deletions TUnit.TestProject/GenericDependsOnTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write to static field from instance method, property, or constructor.

Copilot uses AI. Check for mistakes.
await Task.Delay(TimeSpan.FromSeconds(5));
}
}

// Test for generic DependsOn with method name: DependsOn<T>(methodName)
[EngineTest(ExpectedResult.Pass)]
public class GenericDependsOnTestsWithClass
{
private static DateTime _test2Start;

[Test, DependsOn<GenericDependsOnTestsClassA>(nameof(GenericDependsOnTestsClassA.Test1))]
public async Task Test2()
{
_test2Start = TestContext.Current!.Execution.TestStart!.Value.DateTime;
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write to static field from instance method, property, or constructor.

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write to static field from instance method, property, or constructor.

Copilot uses AI. Check for mistakes.
await Task.Delay(TimeSpan.FromSeconds(5));
}
}

// Test for generic DependsOn without method name: DependsOn<T>()
// This should depend on all tests in ClassB
[EngineTest(ExpectedResult.Pass)]
public class GenericDependsOnTestsWithClassNoMethod
{
private static DateTime _test2Start;

[Test, DependsOn<GenericDependsOnTestsClassB>]
public async Task Test2()
{
_test2Start = TestContext.Current!.Execution.TestStart!.Value.DateTime;
Comment on lines +58 to +61
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write to static field from instance method, property, or constructor.

Suggested change
[Test, DependsOn<GenericDependsOnTestsClassB>]
public async Task Test2()
{
_test2Start = TestContext.Current!.Execution.TestStart!.Value.DateTime;
private static void SetTest2Start(DateTime value)
{
_test2Start = value;
}
[Test, DependsOn<GenericDependsOnTestsClassB>]
public async Task Test2()
{
SetTest2Start(TestContext.Current!.Execution.TestStart!.Value.DateTime);

Copilot uses AI. Check for mistakes.
await Task.CompletedTask;
}

[After(Class)]
public static async Task AssertStartTimes()
{
await Assert.That(_test2Start).IsAfterOrEqualTo(GenericDependsOnTestsClassB.Test1Start.AddSeconds(4.9));
}
}
Loading