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
29 changes: 23 additions & 6 deletions src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation;

namespace BenchmarkDotNet.Parameters
{
Expand All @@ -33,9 +34,10 @@ internal static ParameterInstances CreateForArguments(MethodInfo benchmark, Para

if (unwrappedValue is object[] array)
{
Type firstParamType = benchmark.GetParameters().FirstOrDefault()?.ParameterType;
// the user provided object[] for a benchmark accepting a single argument
if (parameterDefinitions.Length == 1 && array.Length == 1
&& array[0]?.GetType() == benchmark.GetParameters().FirstOrDefault()?.ParameterType) // the benchmark that accepts an object[] as argument
&& (array[0]?.GetType() == firstParamType || (firstParamType != null && firstParamType.IsStackOnlyWithImplicitCast(array[0])))) // the benchmark that accepts an object[] as argument
{
return new ParameterInstances(
new[] { Create(parameterDefinitions, array[0], valuesInfo.source, sourceIndex, argumentIndex: 0, summaryStyle) });
Expand Down Expand Up @@ -91,16 +93,31 @@ public SmartArgument(ParameterDefinition[] parameterDefinitions, object value, M

public string ToSourceCode()
{
string cast = $"({parameterDefinitions[argumentIndex].ParameterType.GetCorrectCSharpTypeName()})"; // it's an object so we need to cast it to the right type
Type paramType = parameterDefinitions[argumentIndex].ParameterType;
bool isParamRefLike = RunnableReflectionHelpers.IsRefLikeType(paramType);

string cast = isParamRefLike ? $"({Value.GetType().GetCorrectCSharpTypeName()})"
: $"({paramType.GetCorrectCSharpTypeName()})"; // it's an object so we need to cast it to the right type

string callPostfix = source is PropertyInfo ? string.Empty : "()";

string indexPostfix = parameterDefinitions.Length > 1
? $"[{argumentIndex}]" // IEnumerable<object[]>
: string.Empty; // IEnumerable<object>
MethodInfo sourceAsMethodInfo = source as MethodInfo;
PropertyInfo sourceAsPropertyInfo = source as PropertyInfo;

Type indexableType = typeof(IEnumerable<object[]>);

string indexPostfix;
if (sourceAsMethodInfo?.ReturnType == indexableType ||
sourceAsPropertyInfo?.GetMethod.ReturnType == indexableType) {
indexPostfix = $"[{argumentIndex}]";
}
else
{
indexPostfix = string.Empty; // IEnumerable<object>
}

string methodCall;
if ((source as MethodInfo)?.IsStatic ?? (source as PropertyInfo)?.GetMethod.IsStatic ?? throw new Exception($"{nameof(source)} was not {nameof(MethodInfo)} nor {nameof(PropertyInfo)}"))
if (sourceAsMethodInfo?.IsStatic ?? sourceAsPropertyInfo?.GetMethod.IsStatic ?? throw new Exception($"{nameof(source)} was not {nameof(MethodInfo)} nor {nameof(PropertyInfo)}"))
{
// If the source member is static, we need to place the fully qualified type name before it, in case the source member is from another type that this generated type does not inherit from.
methodCall = $"{source.DeclaringType.GetCorrectCSharpTypeName()}.{source.Name}";
Expand Down
48 changes: 48 additions & 0 deletions tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,29 @@ public void AcceptsSpan(Span<int> span)
}
}

[Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)]
public void AnArrayFromArgumentsSourceCanBePassedToBenchmarkAsSpan(IToolchain toolchain) => CanExecute<WithArrayFromArgumentsSourceToSpan>(toolchain);

public class WithArrayFromArgumentsSourceToSpan
{
public IEnumerable<object[]> GetArray()
{
yield return new object[] { new[] { 0, 1, 2 } };
}

[Benchmark]
[ArgumentsSource(nameof(GetArray))]
public void AcceptsSpanFromArgumentsSource(Span<int> span)
{
if (span.Length != 3)
throw new ArgumentException("Invalid length");

for (int i = 0; i < 3; i++)
if (span[i] != i)
throw new ArgumentException("Invalid value");
}
}

[TheoryEnvSpecific("The implicit cast operator is available only in .NET Core 2.1+ (See https://github.com/dotnet/corefx/issues/30121 for more)",
EnvRequirement.DotNetCoreOnly)]
[MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)]
Expand All @@ -425,6 +448,31 @@ public void AcceptsReadOnlySpan(ReadOnlySpan<char> notString)
}
}

[TheoryEnvSpecific("The implicit cast operator is available only in .NET Core 2.1+ (See https://github.com/dotnet/corefx/issues/30121 for more)",
EnvRequirement.DotNetCoreOnly)]
[MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)]
public void StringFromArgumentsSourceCanBePassedToBenchmarkAsReadOnlySpan(IToolchain toolchain) => CanExecute<WithStringFromArgumentsSourceToReadOnlySpan>(toolchain);

public class WithStringFromArgumentsSourceToReadOnlySpan
{
private const string expectedString = "very nice string";

public IEnumerable<object[]> GetString()
{
yield return new object[] { expectedString };
}

[Benchmark]
[ArgumentsSource(nameof(GetString))]
public void AcceptsReadOnlySpanFromArgumentsSource(ReadOnlySpan<char> notString)
{
string aString = notString.ToString();

if (aString != expectedString)
throw new ArgumentException("Invalid value");
}
}

[Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)]
public void AnArrayOfStringsCanBeUsedAsArgument(IToolchain toolchain) =>
CanExecute<WithArrayOfStringFromArgumentSource>(toolchain);
Expand Down
Loading