Skip to content

Commit ba1f998

Browse files
authored
Allow ArgumentsSource to reference methods in other types (#2748)
* Allow ArgumentsSource to reference methods in other types * Update docs for custom ArgumentsSource type * Allow ParamsSource to reference methods in other types * Update docs for custom ParamsSource type * Add test for ArgumentsSource with custom type * Add test for ParamsSource with custom type
1 parent 67664a3 commit ba1f998

File tree

8 files changed

+112
-17
lines changed

8 files changed

+112
-17
lines changed

docs/articles/samples/IntroArgumentsSource.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ You can mark one or several fields or properties in your class by the
1111
[`[ArgumentsSource]`](xref:BenchmarkDotNet.Attributes.ArgumentsSourceAttribute) attribute.
1212
In this attribute, you have to specify the name of public method/property which is going to provide the values
1313
(something that implements `IEnumerable`).
14-
The source must be within benchmarked type!
14+
The source may be instance or static. If the source is not in the same type as the benchmark, the type containing the source must be specified in the attribute constructor.
1515

1616
### Source code
1717

docs/articles/samples/IntroParamsSource.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ You can mark one or several fields or properties in your class by the
1010
[`[Params]`](xref:BenchmarkDotNet.Attributes.ParamsAttribute) attribute.
1111
In this attribute, you have to specify the name of public method/property which is going to provide the values
1212
(something that implements `IEnumerable`).
13-
The source must be within benchmarked type!
13+
The source may be instance or static. If the source is not in the same type as the benchmark, the type containing the source must be specified in the attribute constructor.
1414

1515
### Source code
1616

samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ public class IntroArgumentsSource
2020
}
2121

2222
[Benchmark]
23-
[ArgumentsSource(nameof(TimeSpans))]
23+
[ArgumentsSource(typeof(BenchmarkArguments), nameof(BenchmarkArguments.TimeSpans))] // when the arguments come from a different type, specify that type here
2424
public void SingleArgument(TimeSpan time) => Thread.Sleep(time);
25+
}
2526

27+
public class BenchmarkArguments
28+
{
2629
public IEnumerable<object> TimeSpans() // for single argument it's an IEnumerable of objects (object)
2730
{
2831
yield return TimeSpan.FromMilliseconds(10);

samples/BenchmarkDotNet.Samples/IntroParamsSource.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ public class IntroParamsSource
2020
// public static method
2121
public static IEnumerable<int> ValuesForB() => new[] { 10, 20 };
2222

23+
// public field getting its params from a method in another type
24+
[ParamsSource(typeof(ParamsValues), nameof(ParamsValues.ValuesForC))]
25+
public int C;
26+
2327
[Benchmark]
24-
public void Benchmark() => Thread.Sleep(A + B + 5);
28+
public void Benchmark() => Thread.Sleep(A + B + C + 5);
29+
}
30+
31+
public static class ParamsValues
32+
{
33+
public static IEnumerable<int> ValuesForC() => new[] { 1000, 2000 };
2534
}
2635
}

src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ namespace BenchmarkDotNet.Attributes
66
public class ArgumentsSourceAttribute : PriorityAttribute
77
{
88
public string Name { get; }
9+
public Type? Type { get; }
910

10-
public ArgumentsSourceAttribute(string name) => Name = name;
11+
public ArgumentsSourceAttribute(string name)
12+
{
13+
Name = name;
14+
Type = null;
15+
}
16+
17+
public ArgumentsSourceAttribute(Type type, string name)
18+
{
19+
Name = name;
20+
Type = type;
21+
}
1122
}
1223
}

src/BenchmarkDotNet.Annotations/Attributes/ParamsSourceAttribute.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ namespace BenchmarkDotNet.Attributes
66
public class ParamsSourceAttribute : PriorityAttribute
77
{
88
public string Name { get; }
9+
public Type? Type { get; }
910

10-
public ParamsSourceAttribute(string name) => Name = name;
11+
public ParamsSourceAttribute(string name)
12+
{
13+
Name = name;
14+
Type = null;
15+
}
16+
17+
public ParamsSourceAttribute(Type type, string name)
18+
{
19+
Name = name;
20+
Type = type;
21+
}
1122
}
1223
}

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ IEnumerable<ParameterDefinition> GetDefinitions<TAttribute>(Func<TAttribute, Typ
198198

199199
var paramsSourceDefinitions = GetDefinitions<ParamsSourceAttribute>((attribute, parameterType) =>
200200
{
201-
var paramsValues = GetValidValuesForParamsSource(type, attribute.Name);
201+
var targetType = attribute.Type ?? type;
202+
203+
var paramsValues = GetValidValuesForParamsSource(targetType, attribute.Name);
202204
return SmartParamBuilder.CreateForParams(parameterType, paramsValues.source, paramsValues.values);
203205
});
204206

@@ -208,7 +210,7 @@ IEnumerable<ParameterDefinition> GetDefinitions<TAttribute>(Func<TAttribute, Typ
208210
return new ParameterDefinitions(definitions);
209211
}
210212

211-
private static IEnumerable<ParameterInstances> GetArgumentsDefinitions(MethodInfo benchmark, Type target, SummaryStyle summaryStyle)
213+
private static IEnumerable<ParameterInstances> GetArgumentsDefinitions(MethodInfo benchmark, Type benchmarkType, SummaryStyle summaryStyle)
212214
{
213215
var argumentsAttributes = benchmark.GetCustomAttributes<PriorityAttribute>();
214216
int priority = argumentsAttributes.Select(attribute => attribute.Priority).Sum();
@@ -244,8 +246,9 @@ private static IEnumerable<ParameterInstances> GetArgumentsDefinitions(MethodInf
244246
yield break;
245247

246248
var argumentsSourceAttribute = benchmark.GetCustomAttribute<ArgumentsSourceAttribute>();
249+
var targetType = argumentsSourceAttribute.Type ?? benchmarkType;
247250

248-
var valuesInfo = GetValidValuesForParamsSource(target, argumentsSourceAttribute.Name);
251+
var valuesInfo = GetValidValuesForParamsSource(targetType, argumentsSourceAttribute.Name);
249252
for (int sourceIndex = 0; sourceIndex < valuesInfo.values.Length; sourceIndex++)
250253
yield return SmartParamBuilder.CreateForArguments(benchmark, parameterDefinitions, valuesInfo, sourceIndex, summaryStyle);
251254
}
@@ -303,25 +306,25 @@ private static object Map(object providedValue, Type type)
303306
return providedValue;
304307
}
305308

306-
private static (MemberInfo source, object[] values) GetValidValuesForParamsSource(Type parentType, string sourceName)
309+
private static (MemberInfo source, object[] values) GetValidValuesForParamsSource(Type sourceType, string sourceName)
307310
{
308-
var paramsSourceMethod = parentType.GetAllMethods().SingleOrDefault(method => method.Name == sourceName && method.IsPublic);
311+
var paramsSourceMethod = sourceType.GetAllMethods().SingleOrDefault(method => method.Name == sourceName && method.IsPublic);
309312

310313
if (paramsSourceMethod != default)
311314
return (paramsSourceMethod, ToArray(
312-
paramsSourceMethod.Invoke(paramsSourceMethod.IsStatic ? null : Activator.CreateInstance(parentType), null),
315+
paramsSourceMethod.Invoke(paramsSourceMethod.IsStatic ? null : Activator.CreateInstance(sourceType), null),
313316
paramsSourceMethod,
314-
parentType));
317+
sourceType));
315318

316-
var paramsSourceProperty = parentType.GetAllProperties().SingleOrDefault(property => property.Name == sourceName && property.GetMethod.IsPublic);
319+
var paramsSourceProperty = sourceType.GetAllProperties().SingleOrDefault(property => property.Name == sourceName && property.GetMethod.IsPublic);
317320

318321
if (paramsSourceProperty != default)
319322
return (paramsSourceProperty, ToArray(
320-
paramsSourceProperty.GetValue(paramsSourceProperty.GetMethod.IsStatic ? null : Activator.CreateInstance(parentType)),
323+
paramsSourceProperty.GetValue(paramsSourceProperty.GetMethod.IsStatic ? null : Activator.CreateInstance(sourceType)),
321324
paramsSourceProperty,
322-
parentType));
325+
sourceType));
323326

324-
throw new InvalidBenchmarkDeclarationException($"{parentType.Name} has no public, accessible method/property called {sourceName}, unable to read values for [ParamsSource]");
327+
throw new InvalidBenchmarkDeclarationException($"{sourceType.Name} has no public, accessible method/property called {sourceName}, unable to read values for [ParamsSource]");
325328
}
326329

327330
private static object[] ToArray(object sourceValue, MemberInfo memberInfo, Type type)

tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,28 @@ public IEnumerable<object[]> ArgumentsProvider()
8181
}
8282
}
8383

84+
[Theory, MemberData(nameof(GetToolchains))]
85+
public void ArgumentsFromSourceInAnotherClassArePassedToBenchmarks(IToolchain toolchain) => CanExecute<WithArgumentsSourceInAnotherClass>(toolchain);
86+
87+
public class WithArgumentsSourceInAnotherClass
88+
{
89+
[Benchmark]
90+
[ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.ArgumentsProvider))]
91+
public void Simple(bool boolean, int number)
92+
{
93+
if (boolean && number != 1 || !boolean && number != 2)
94+
throw new InvalidOperationException("Incorrect values were passed");
95+
}
96+
}
97+
public static class ExternalClassWithArgumentsSource
98+
{
99+
public static IEnumerable<object[]> ArgumentsProvider()
100+
{
101+
yield return new object[] { true, 1 };
102+
yield return new object[] { false, 2 };
103+
}
104+
}
105+
84106
[Theory, MemberData(nameof(GetToolchains))]
85107
public void ArgumentsCanBePassedByReferenceToBenchmark(IToolchain toolchain) => CanExecute<WithRefArguments>(toolchain);
86108

@@ -747,6 +769,42 @@ public void TestProperty(int argument)
747769
}
748770
}
749771

772+
[Theory, MemberData(nameof(GetToolchains))]
773+
public void MethodsAndPropertiesFromAnotherClassCanBeUsedAsSources(IToolchain toolchain)
774+
=> CanExecute<ParamsSourcePointingToAnotherClass>(toolchain);
775+
776+
public class ParamsSourcePointingToAnotherClass
777+
{
778+
[ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.Method))]
779+
public int ParamOne { get; set; }
780+
781+
[ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.Property))]
782+
public int ParamTwo { get; set; }
783+
784+
[Benchmark]
785+
public void Test()
786+
{
787+
if (ParamOne != 123)
788+
throw new ArgumentException("The ParamOne value is incorrect!");
789+
if (ParamTwo != 456)
790+
throw new ArgumentException("The ParamTwo value is incorrect!");
791+
}
792+
}
793+
public static class ExternalClassWithParamsSource
794+
{
795+
public static IEnumerable<int> Method()
796+
{
797+
yield return 123;
798+
}
799+
public static IEnumerable<int> Property
800+
{
801+
get
802+
{
803+
yield return 456;
804+
}
805+
}
806+
}
807+
750808
[Theory, MemberData(nameof(GetToolchains))]
751809
public void VeryLongStringsAreSupported(IToolchain toolchain) => CanExecute<WithVeryLongString>(toolchain);
752810

0 commit comments

Comments
 (0)