diff --git a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt index ea365df2ae..9b3fa0a86d 100644 --- a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt @@ -1365,3 +1365,117 @@ internal static class TUnit_TestProject_MatrixTests_Exclusion__int_int_ModuleIni global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.MatrixTests), new TUnit_TestProject_MatrixTests_Exclusion__int_int_TestSource()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class TUnit_TestProject_MatrixTests_MatrixMethod_WithEnumParameter_UsesOnlyMethodValues__bool_CountToTenEnum_TestSource : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "MatrixMethod_WithEnumParameter_UsesOnlyMethodValues", + TestClassType = typeof(global::TUnit.TestProject.MatrixTests), + TestMethodName = "MatrixMethod_WithEnumParameter_UsesOnlyMethodValues", + Dependencies = global::System.Array.Empty(), + AttributeFactory = static () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass) + ], + DataSources = new global::TUnit.Core.IDataSourceAttribute[] + { + new global::TUnit.Core.MatrixDataSourceAttribute(), + }, + ClassDataSources = global::System.Array.Empty(), + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 197, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.MatrixTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.MatrixTests)), + Name = "MatrixMethod_WithEnumParameter_UsesOnlyMethodValues", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = new global::TUnit.Core.ParameterMetadata[] + { + new global::TUnit.Core.ParameterMetadata(typeof(bool)) + { + Name = "flag", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(bool)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.MatrixTests).GetMethod("MatrixMethod_WithEnumParameter_UsesOnlyMethodValues", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(bool), typeof(global::TUnit.TestProject.MatrixTests.CountToTenEnum) }, null)!.GetParameters()[0] + }, + new global::TUnit.Core.ParameterMetadata(typeof(global::TUnit.TestProject.MatrixTests.CountToTenEnum)) + { + Name = "enum", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.MatrixTests.CountToTenEnum)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.MatrixTests).GetMethod("MatrixMethod_WithEnumParameter_UsesOnlyMethodValues", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(bool), typeof(global::TUnit.TestProject.MatrixTests.CountToTenEnum) }, null)!.GetParameters()[1] + } + }, + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.MatrixTests", static () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.MatrixTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.MatrixTests)), + Name = "MatrixTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", static () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = global::System.Array.Empty(), + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => new global::TUnit.TestProject.MatrixTests(), + InvokeTypedTest = static (instance, args, cancellationToken) => + { + try + { + switch (args.Length) + { + case 2: + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixMethod_WithEnumParameter_UsesOnlyMethodValues(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } + default: + throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); + } + } + catch (global::System.Exception ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex)); + } + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class TUnit_TestProject_MatrixTests_MatrixMethod_WithEnumParameter_UsesOnlyMethodValues__bool_CountToTenEnum_ModuleInitializer +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.MatrixTests), new TUnit_TestProject_MatrixTests_MatrixMethod_WithEnumParameter_UsesOnlyMethodValues__bool_CountToTenEnum_TestSource()); + } +} diff --git a/TUnit.Core.SourceGenerator/CodeGenerationHelpers.cs b/TUnit.Core.SourceGenerator/CodeGenerationHelpers.cs index 31ecc4b5e8..7555f29138 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerationHelpers.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerationHelpers.cs @@ -49,14 +49,16 @@ public static string GenerateParameterMetadataArray(IMethodSymbol method) } // Generate cached data source attributes for AOT compatibility + // Include both IDataSourceAttribute and IDataSourceMemberAttribute implementations var dataSourceAttributes = param.GetAttributes() .Where(attr => attr.AttributeClass != null && - attr.AttributeClass.AllInterfaces.Any(i => i.Name == "IDataSourceAttribute")) + attr.AttributeClass.AllInterfaces.Any(i => + i.Name == "IDataSourceAttribute" || i.Name == "IDataSourceMemberAttribute")) .ToArray(); if (dataSourceAttributes.Length > 0) { - writer.AppendLine($"CachedDataSourceAttributes = new global::TUnit.Core.IDataSourceAttribute[]"); + writer.AppendLine($"CachedDataSourceAttributes = new global::System.Attribute[]"); writer.AppendLine("{"); writer.SetIndentLevel(3); foreach (var attr in dataSourceAttributes) diff --git a/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs b/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs index 4d8ab15ef5..53a1a697ee 100644 --- a/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs +++ b/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs @@ -126,7 +126,9 @@ public sealed class CombinedDataSourcesAttribute : AsyncUntypedDataSourceGenerat if (parameterMetadata.CachedDataSourceAttributes != null) { // Source-generated mode: use cached attributes (no reflection!) - dataSourceAttributes = parameterMetadata.CachedDataSourceAttributes; + dataSourceAttributes = parameterMetadata.CachedDataSourceAttributes + .OfType() + .ToArray(); } else { diff --git a/TUnit.Core/Attributes/TestData/IDataSourceMemberAttribute.cs b/TUnit.Core/Attributes/TestData/IDataSourceMemberAttribute.cs new file mode 100644 index 0000000000..dcb9c3db5c --- /dev/null +++ b/TUnit.Core/Attributes/TestData/IDataSourceMemberAttribute.cs @@ -0,0 +1,9 @@ +namespace TUnit.Core; + +/// +/// Marker interface for attributes that provide data values for individual parameters +/// within a data source context (e.g., matrix testing). +/// Attributes implementing this interface will be cached by the source generator +/// for AOT-compatible runtime access. +/// +public interface IDataSourceMemberAttribute; diff --git a/TUnit.Core/Attributes/TestData/MatrixSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MatrixSourceAttribute.cs index 7417dcb421..d1a7ccac7c 100644 --- a/TUnit.Core/Attributes/TestData/MatrixSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MatrixSourceAttribute.cs @@ -42,7 +42,7 @@ namespace TUnit.Core; /// /// The values to be used for this parameter in the test matrix. [AttributeUsage(AttributeTargets.Parameter)] -public class MatrixAttribute(params object?[]? objects) : TUnitAttribute +public class MatrixAttribute(params object?[]? objects) : TUnitAttribute, IDataSourceMemberAttribute { protected MatrixAttribute() : this(null) { diff --git a/TUnit.Core/Models/TestModels/ParameterMetadata.cs b/TUnit.Core/Models/TestModels/ParameterMetadata.cs index 6816a4e497..f3b24c53e4 100644 --- a/TUnit.Core/Models/TestModels/ParameterMetadata.cs +++ b/TUnit.Core/Models/TestModels/ParameterMetadata.cs @@ -56,9 +56,10 @@ public record ParameterMetadata([DynamicallyAccessedMembers(DynamicallyAccessedM /// Cached data source attributes to avoid reflection call. /// Set by source generator for AOT compatibility. /// When null, falls back to using ReflectionInfo.GetCustomAttributes(). + /// Includes attributes implementing IDataSourceAttribute or IDataSourceMemberAttribute. /// [EditorBrowsable(EditorBrowsableState.Never)] - public IDataSourceAttribute[]? CachedDataSourceAttributes { get; internal init; } + public Attribute[]? CachedDataSourceAttributes { get; internal init; } /// /// Position of this parameter in the method/constructor signature. diff --git a/TUnit.Engine.Tests/MatrixTests.cs b/TUnit.Engine.Tests/MatrixTests.cs index 0daaafa2ec..d50a5bda37 100644 --- a/TUnit.Engine.Tests/MatrixTests.cs +++ b/TUnit.Engine.Tests/MatrixTests.cs @@ -8,7 +8,7 @@ public class MatrixTests(TestMode testMode) : InvokableTestBase(testMode) [Test] public async Task Test() { - var expectedCount = IsNetFramework ? 133 : 271; + var expectedCount = IsNetFramework ? 137 : 275; await RunTestsWithFilter( "/*/*/MatrixTests/*", diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index d17dfa1d33..23efc08b5c 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -864,6 +864,7 @@ namespace bool SkipIfEmpty { get; set; } .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } + public interface IDataSourceMemberAttribute { } public interface IDynamicTestCreatorLocation { string? CreatorFilePath { get; set; } @@ -909,7 +910,7 @@ namespace public InvalidTestMetadataException(string message, .TestMetadata metadata, innerException) { } } [(.Parameter)] - public class MatrixAttribute : .TUnitAttribute + public class MatrixAttribute : .TUnitAttribute, .IDataSourceMemberAttribute { protected MatrixAttribute() { } public MatrixAttribute(params object?[]? objects) { } @@ -1053,7 +1054,7 @@ namespace public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } - public .IDataSourceAttribute[]? CachedDataSourceAttributes { get; } + public []? CachedDataSourceAttributes { get; } public object? CachedDefaultValue { get; } public bool? CachedIsOptional { get; } public bool? CachedIsParams { get; } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index c8c1e43783..83d0d32987 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -864,6 +864,7 @@ namespace bool SkipIfEmpty { get; set; } .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } + public interface IDataSourceMemberAttribute { } public interface IDynamicTestCreatorLocation { string? CreatorFilePath { get; set; } @@ -909,7 +910,7 @@ namespace public InvalidTestMetadataException(string message, .TestMetadata metadata, innerException) { } } [(.Parameter)] - public class MatrixAttribute : .TUnitAttribute + public class MatrixAttribute : .TUnitAttribute, .IDataSourceMemberAttribute { protected MatrixAttribute() { } public MatrixAttribute(params object?[]? objects) { } @@ -1053,7 +1054,7 @@ namespace public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } - public .IDataSourceAttribute[]? CachedDataSourceAttributes { get; } + public []? CachedDataSourceAttributes { get; } public object? CachedDefaultValue { get; } public bool? CachedIsOptional { get; } public bool? CachedIsParams { get; } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 9c65473252..36ed6038ef 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -864,6 +864,7 @@ namespace bool SkipIfEmpty { get; set; } .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } + public interface IDataSourceMemberAttribute { } public interface IDynamicTestCreatorLocation { string? CreatorFilePath { get; set; } @@ -909,7 +910,7 @@ namespace public InvalidTestMetadataException(string message, .TestMetadata metadata, innerException) { } } [(.Parameter)] - public class MatrixAttribute : .TUnitAttribute + public class MatrixAttribute : .TUnitAttribute, .IDataSourceMemberAttribute { protected MatrixAttribute() { } public MatrixAttribute(params object?[]? objects) { } @@ -1053,7 +1054,7 @@ namespace public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } - public .IDataSourceAttribute[]? CachedDataSourceAttributes { get; } + public []? CachedDataSourceAttributes { get; } public object? CachedDefaultValue { get; } public bool? CachedIsOptional { get; } public bool? CachedIsParams { get; } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 9035d876c9..387faefc64 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -841,6 +841,7 @@ namespace bool SkipIfEmpty { get; set; } .<<.>> GetDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } + public interface IDataSourceMemberAttribute { } public interface IDynamicTestCreatorLocation { string? CreatorFilePath { get; set; } @@ -886,7 +887,7 @@ namespace public InvalidTestMetadataException(string message, .TestMetadata metadata, innerException) { } } [(.Parameter)] - public class MatrixAttribute : .TUnitAttribute + public class MatrixAttribute : .TUnitAttribute, .IDataSourceMemberAttribute { protected MatrixAttribute() { } public MatrixAttribute(params object?[]? objects) { } @@ -1016,7 +1017,7 @@ namespace public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { public ParameterMetadata( Type) { } - public .IDataSourceAttribute[]? CachedDataSourceAttributes { get; } + public []? CachedDataSourceAttributes { get; } public object? CachedDefaultValue { get; } public bool? CachedIsOptional { get; } public bool? CachedIsParams { get; } diff --git a/TUnit.TestProject/MatrixTests.cs b/TUnit.TestProject/MatrixTests.cs index 0e88957bc2..8c3018788f 100644 --- a/TUnit.TestProject/MatrixTests.cs +++ b/TUnit.TestProject/MatrixTests.cs @@ -192,4 +192,22 @@ public static object ObjectMethod() { return 1; } + + // Test for GitHub Discussion #4145: MatrixMethod with enum parameter + [Test] + [MatrixDataSource] + public async Task MatrixMethod_WithEnumParameter_UsesOnlyMethodValues( + [Matrix(true, false)] bool flag, + [MatrixMethod(nameof(GetSpecificEnumValues))] CountToTenEnum @enum) + { + // This test verifies that MatrixMethod returns only the specific enum values + // and doesn't auto-generate all enum values (should only be One or Five) + await Assert.That(@enum == CountToTenEnum.One || @enum == CountToTenEnum.Five).IsTrue(); + } + + public static IEnumerable GetSpecificEnumValues() + { + yield return CountToTenEnum.One; + yield return CountToTenEnum.Five; + } }