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
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[]>(args[0]));
typedInstance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast<string>(args[0]) });
break;
case 2:
typedInstance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast<string>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<string>(args[1]) });
Expand Down Expand Up @@ -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<string[]>(args[0]));
instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast<string>(args[0]) });
break;
case 2:
instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast<string>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<string>(args[1]) });
Expand Down
8 changes: 4 additions & 4 deletions TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ internal sealed class Tests_Test_TestSource_GUID : global::TUnit.Core.Interfaces
typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[0]);
break;
case 2:
typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<long[]>(args[1]));
typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]) });
break;
case 3:
typedInstance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]), TUnit.Core.Helpers.CastHelper.Cast<long>(args[2]) });
Expand Down Expand Up @@ -119,7 +119,7 @@ internal sealed class Tests_Test_TestSource_GUID : global::TUnit.Core.Interfaces
instance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[0]);
break;
case 2:
instance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<long[]>(args[1]));
instance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]) });
break;
case 3:
instance.Test(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]), TUnit.Core.Helpers.CastHelper.Cast<long>(args[2]) });
Expand Down Expand Up @@ -250,7 +250,7 @@ internal sealed class Tests_Test2_TestSource_GUID : global::TUnit.Core.Interface
typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[0]);
break;
case 2:
typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<long[]>(args[1]));
typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]) });
break;
case 3:
typedInstance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]), TUnit.Core.Helpers.CastHelper.Cast<long>(args[2]) });
Expand Down Expand Up @@ -280,7 +280,7 @@ internal sealed class Tests_Test2_TestSource_GUID : global::TUnit.Core.Interface
instance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[0]);
break;
case 2:
instance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), TUnit.Core.Helpers.CastHelper.Cast<long[]>(args[1]));
instance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]) });
break;
case 3:
instance.Test2(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast<long>(args[1]), TUnit.Core.Helpers.CastHelper.Cast<long>(args[2]) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@

// Parse argument count - can be an int or a string expression
int argCount;
string argCountExpression = null;

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 72 in TUnit.Core.SourceGenerator/CodeGenerators/Helpers/TupleArgumentHelper.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Converting null literal or possible null value to non-nullable type.
if (argumentCount is int count)
{
argCount = count;
Expand Down Expand Up @@ -164,9 +164,9 @@
}
else if (remainingArgCount == 1)
{
// Single argument for params - can pass directly or as array
var singleArgExpression = $"TUnit.Core.Helpers.CastHelper.Cast<{paramsParam.Type.GloballyQualified()}>({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
{
Expand Down
120 changes: 100 additions & 20 deletions TUnit.Engine/Discovery/ReflectionTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>
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<T>
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<T>
else if (IsCovariantCompatible(paramType, argType))
{
castedArgs[i] = arg;
}
else
{
// Otherwise use CastHelper for conversions
castedArgs[i] = CastHelper.Cast(paramType, arg);
}
}
}

Expand Down
90 changes: 71 additions & 19 deletions TUnit.TestProject/ParamsArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading