diff --git a/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt index 488269909a..308ee96d63 100644 --- a/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt @@ -79,7 +79,7 @@ internal sealed class ArgsAsArrayTests_Params_TestSource_GUID : global::TUnit.Co typedInstance.Params(new string[0]); break; case 1: - typedInstance.Params(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + typedInstance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]) }); break; case 2: typedInstance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); @@ -109,7 +109,7 @@ internal sealed class ArgsAsArrayTests_Params_TestSource_GUID : global::TUnit.Co instance.Params(new string[0]); break; case 1: - instance.Params(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]) }); break; case 2: instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); diff --git a/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt index e256a787c9..0014e8ebbd 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt @@ -89,7 +89,7 @@ internal sealed class Tests_Test_TestSource_GUID : global::TUnit.Core.Interfaces typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); break; case 2: - typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); break; case 3: typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); @@ -119,7 +119,7 @@ internal sealed class Tests_Test_TestSource_GUID : global::TUnit.Core.Interfaces instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); break; case 2: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); break; case 3: instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); @@ -250,7 +250,7 @@ internal sealed class Tests_Test2_TestSource_GUID : global::TUnit.Core.Interface typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); break; case 2: - typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); break; case 3: typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); @@ -280,7 +280,7 @@ internal sealed class Tests_Test2_TestSource_GUID : global::TUnit.Core.Interface instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); break; case 2: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); break; case 3: instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs b/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs index e668c0d541..1c7918c2af 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs @@ -164,9 +164,9 @@ public static List GenerateArgumentAccessWithParams(IList({argumentsArrayName}[{regularParamCount}])"; - argumentExpressions.Add(singleArgExpression); + // Single argument for params - create array with single element + var singleElementExpression = $"TUnit.Core.Helpers.CastHelper.Cast<{elementType.GloballyQualified()}>({argumentsArrayName}[{regularParamCount}])"; + argumentExpressions.Add($"new {elementType.GloballyQualified()}[] {{ {singleElementExpression} }}"); } else { diff --git a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs index ac6af632c6..4237be559e 100644 --- a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs +++ b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs @@ -1443,34 +1443,114 @@ private static bool IsCovariantCompatible(Type paramType, Type argType) var parameters = methodToInvoke.GetParameters(); var castedArgs = new object?[parameters.Length]; - for (var i = 0; i < parameters.Length && i < args.Length; i++) + // Check if the last parameter is a params array + var lastParam = parameters.Length > 0 ? parameters[^1] : null; + var isParamsArray = lastParam != null && lastParam.IsDefined(typeof(ParamArrayAttribute), false); + + if (isParamsArray && lastParam != null) { - var paramType = parameters[i].ParameterType; - var arg = args[i]; - - if (arg == null) + // Handle params array parameter + var paramsElementType = lastParam.ParameterType.GetElementType(); + var regularParamsCount = parameters.Length - 1; + + // Process regular parameters first + for (var i = 0; i < regularParamsCount && i < args.Length; i++) { - castedArgs[i] = null; - continue; - } + var paramType = parameters[i].ParameterType; + var arg = args[i]; - var argType = arg.GetType(); + if (arg == null) + { + castedArgs[i] = null; + continue; + } - // If the argument is already assignable to the parameter type, use it directly - // This handles delegates and other non-convertible types - if (paramType.IsAssignableFrom(argType)) - { - castedArgs[i] = arg; + var argType = arg.GetType(); + + // If the argument is already assignable to the parameter type, use it directly + // This handles delegates and other non-convertible types + if (paramType.IsAssignableFrom(argType)) + { + castedArgs[i] = arg; + } + // Special handling for covariant interfaces like IEnumerable + else if (IsCovariantCompatible(paramType, argType)) + { + castedArgs[i] = arg; + } + else + { + // Otherwise use CastHelper for conversions + castedArgs[i] = CastHelper.Cast(paramType, arg); + } } - // Special handling for covariant interfaces like IEnumerable - else if (IsCovariantCompatible(paramType, argType)) + + // Collect remaining arguments into params array + var paramsStartIndex = regularParamsCount; + var paramsCount = Math.Max(0, args.Length - paramsStartIndex); + + if (paramsElementType != null) { - castedArgs[i] = arg; + var paramsArray = Array.CreateInstance(paramsElementType, paramsCount); + for (var i = 0; i < paramsCount; i++) + { + var arg = args[paramsStartIndex + i]; + if (arg != null) + { + var argType = arg.GetType(); + if (paramsElementType.IsAssignableFrom(argType)) + { + paramsArray.SetValue(arg, i); + } + else if (IsCovariantCompatible(paramsElementType, argType)) + { + paramsArray.SetValue(arg, i); + } + else + { + paramsArray.SetValue(CastHelper.Cast(paramsElementType, arg), i); + } + } + else + { + paramsArray.SetValue(null, i); + } + } + castedArgs[regularParamsCount] = paramsArray; } - else + } + else + { + // Normal parameter handling when no params array + for (var i = 0; i < parameters.Length && i < args.Length; i++) { - // Otherwise use CastHelper for conversions - castedArgs[i] = CastHelper.Cast(paramType, arg); + var paramType = parameters[i].ParameterType; + var arg = args[i]; + + if (arg == null) + { + castedArgs[i] = null; + continue; + } + + var argType = arg.GetType(); + + // If the argument is already assignable to the parameter type, use it directly + // This handles delegates and other non-convertible types + if (paramType.IsAssignableFrom(argType)) + { + castedArgs[i] = arg; + } + // Special handling for covariant interfaces like IEnumerable + else if (IsCovariantCompatible(paramType, argType)) + { + castedArgs[i] = arg; + } + else + { + // Otherwise use CastHelper for conversions + castedArgs[i] = CastHelper.Cast(paramType, arg); + } } } diff --git a/TUnit.TestProject/ParamsArgumentsTests.cs b/TUnit.TestProject/ParamsArgumentsTests.cs index 15a8aa9f79..78ad3eb7fe 100644 --- a/TUnit.TestProject/ParamsArgumentsTests.cs +++ b/TUnit.TestProject/ParamsArgumentsTests.cs @@ -1,26 +1,78 @@ -using TUnit.Core; +using TUnit.TestProject.Attributes; -namespace TUnit.TestProject +namespace TUnit.TestProject; + +[EngineTest(ExpectedResult.Pass)] +public class ParamsArgumentsTests { - public class ParamsArgumentsTests + [Test] + [Arguments(2, 2)] + [Arguments(20, 3, Operation.Kind.A)] + [Arguments(20, 6, Operation.Kind.Deposit, Operation.Kind.B)] + public void GetOperations(int dayDelta, int expectedNumberOfOperation, params Operation.Kind[] kinds) { - [Test] - [Arguments(2, 2)] - [Arguments(20, 3, Operation.Kind.A)] - [Arguments(20, 6, Operation.Kind.Deposit, Operation.Kind.B)] - public void GetOperations(int dayDelta, int expectedNumberOfOperation, params Operation.Kind[] kinds) - { - // Test implementation - } + // Test implementation } - public class Operation + [Test] + [Arguments("Foo", typeof(string))] + public async Task SingleTypeInParamsArray(string name, params Type[] types) + { + await Assert.That(name).IsEqualTo("Foo"); + await Assert.That(types).IsNotNull(); + await Assert.That(types.Length).IsEqualTo(1); + await Assert.That(types[0]).IsEqualTo(typeof(string)); + } + + [Test] + [Arguments("Bar", typeof(int), typeof(string))] + public async Task MultipleTypesInParamsArray(string name, params Type[] types) + { + await Assert.That(name).IsEqualTo("Bar"); + await Assert.That(types).IsNotNull(); + await Assert.That(types.Length).IsEqualTo(2); + await Assert.That(types[0]).IsEqualTo(typeof(int)); + await Assert.That(types[1]).IsEqualTo(typeof(string)); + } + + [Test] + [Arguments("Baz")] + public async Task EmptyParamsArray(string name, params Type[] types) + { + await Assert.That(name).IsEqualTo("Baz"); + await Assert.That(types).IsNotNull(); + await Assert.That(types.Length).IsEqualTo(0); + } + + [Test] + [Arguments(1, "single")] + public async Task SingleStringInParamsArray(int id, params string[] values) + { + await Assert.That(id).IsEqualTo(1); + await Assert.That(values).IsNotNull(); + await Assert.That(values.Length).IsEqualTo(1); + await Assert.That(values[0]).IsEqualTo("single"); + } + + [Test] + [Arguments(2, "first", "second", "third")] + public async Task MultipleStringsInParamsArray(int id, params string[] values) + { + await Assert.That(id).IsEqualTo(2); + await Assert.That(values).IsNotNull(); + await Assert.That(values.Length).IsEqualTo(3); + await Assert.That(values[0]).IsEqualTo("first"); + await Assert.That(values[1]).IsEqualTo("second"); + await Assert.That(values[2]).IsEqualTo("third"); + } +} + +public class Operation +{ + public enum Kind { - public enum Kind - { - A, - B, - Deposit - } + A, + B, + Deposit } -} \ No newline at end of file +}