Skip to content

Commit 3694910

Browse files
committed
Rework ReturnValueValidator/ExecutionValidator
1 parent b750633 commit 3694910

3 files changed

Lines changed: 161 additions & 129 deletions

File tree

src/BenchmarkDotNet/Validators/ExecutionValidator.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using BenchmarkDotNet.Running;
43

54
namespace BenchmarkDotNet.Validators
65
{
@@ -12,20 +11,20 @@ public class ExecutionValidator : ExecutionValidatorBase
1211
private ExecutionValidator(bool failOnError)
1312
: base(failOnError) { }
1413

15-
protected override void ExecuteBenchmarks(object benchmarkTypeInstance, IEnumerable<BenchmarkCase> benchmarks, List<ValidationError> errors)
14+
protected override void ExecuteBenchmarks(IEnumerable<BenchmarkExecutor> executors, List<ValidationError> errors)
1615
{
17-
foreach (var benchmark in benchmarks)
16+
foreach (var executor in executors)
1817
{
1918
try
2019
{
21-
benchmark.Descriptor.WorkloadMethod.Invoke(benchmarkTypeInstance, null);
20+
executor.Invoke();
2221
}
2322
catch (Exception ex)
2423
{
2524
errors.Add(new ValidationError(
2625
TreatsWarningsAsErrors,
27-
$"Failed to execute benchmark '{benchmark.DisplayInfo}', exception was: '{GetDisplayExceptionMessage(ex)}'",
28-
benchmark));
26+
$"Failed to execute benchmark '{executor.BenchmarkCase.DisplayInfo}', exception was: '{GetDisplayExceptionMessage(ex)}'",
27+
executor.BenchmarkCase));
2928
}
3029
}
3130
}

src/BenchmarkDotNet/Validators/ExecutionValidatorBase.cs

Lines changed: 141 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using BenchmarkDotNet.Attributes;
77
using BenchmarkDotNet.Extensions;
88
using BenchmarkDotNet.Running;
9+
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
910

1011
namespace BenchmarkDotNet.Validators
1112
{
@@ -24,29 +25,34 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara
2425

2526
foreach (var typeGroup in validationParameters.Benchmarks.GroupBy(benchmark => benchmark.Descriptor.Type))
2627
{
27-
if (!TryCreateBenchmarkTypeInstance(typeGroup.Key, errors, out var benchmarkTypeInstance))
28-
{
29-
continue;
30-
}
28+
var executors = new List<BenchmarkExecutor>();
3129

32-
if (!TryToSetParamsFields(benchmarkTypeInstance, errors))
30+
foreach (var benchmark in typeGroup)
3331
{
34-
continue;
35-
}
32+
if (!TryCreateBenchmarkTypeInstance(typeGroup.Key, errors, out var benchmarkTypeInstance))
33+
continue;
3634

37-
if (!TryToSetParamsProperties(benchmarkTypeInstance, errors))
38-
{
39-
continue;
40-
}
35+
if (!TryToFillParameters(benchmark, benchmarkTypeInstance, errors))
36+
continue;
4137

42-
if (!TryToCallGlobalSetup(benchmarkTypeInstance, errors))
43-
{
44-
continue;
38+
if (!TryToCallGlobalSetup(benchmarkTypeInstance, errors))
39+
continue;
40+
41+
if (!TryToCallIterationSetup(benchmarkTypeInstance, errors))
42+
continue;
43+
44+
executors.Add(new BenchmarkExecutor(benchmarkTypeInstance, benchmark));
4545
}
4646

47-
ExecuteBenchmarks(benchmarkTypeInstance, typeGroup, errors);
47+
ExecuteBenchmarks(executors, errors);
48+
49+
foreach (var executor in executors)
50+
{
51+
if (!TryToCallIterationCleanup(executor.Instance, errors))
52+
continue;
4853

49-
TryToCallGlobalCleanup(benchmarkTypeInstance, errors);
54+
TryToCallGlobalCleanup(executor.Instance, errors);
55+
}
5056
}
5157

5258
return errors;
@@ -73,15 +79,25 @@ private bool TryCreateBenchmarkTypeInstance(Type type, List<ValidationError> err
7379

7480
private bool TryToCallGlobalSetup(object benchmarkTypeInstance, List<ValidationError> errors)
7581
{
76-
return TryToCallGlobalMethod<GlobalSetupAttribute>(benchmarkTypeInstance, errors);
82+
return TryToCallGlobalMethod<GlobalSetupAttribute>(benchmarkTypeInstance, errors, true);
7783
}
7884

7985
private void TryToCallGlobalCleanup(object benchmarkTypeInstance, List<ValidationError> errors)
8086
{
81-
TryToCallGlobalMethod<GlobalCleanupAttribute>(benchmarkTypeInstance, errors);
87+
TryToCallGlobalMethod<GlobalCleanupAttribute>(benchmarkTypeInstance, errors, true);
88+
}
89+
90+
private bool TryToCallIterationSetup(object benchmarkTypeInstance, List<ValidationError> errors)
91+
{
92+
return TryToCallGlobalMethod<IterationSetupAttribute>(benchmarkTypeInstance, errors, false);
93+
}
94+
95+
private bool TryToCallIterationCleanup(object benchmarkTypeInstance, List<ValidationError> errors)
96+
{
97+
return TryToCallGlobalMethod<IterationCleanupAttribute>(benchmarkTypeInstance, errors, false);
8298
}
8399

84-
private bool TryToCallGlobalMethod<T>(object benchmarkTypeInstance, List<ValidationError> errors)
100+
private bool TryToCallGlobalMethod<T>(object benchmarkTypeInstance, List<ValidationError> errors, bool canBeAsync) where T : Attribute
85101
{
86102
var methods = benchmarkTypeInstance
87103
.GetType()
@@ -107,7 +123,16 @@ private bool TryToCallGlobalMethod<T>(object benchmarkTypeInstance, List<Validat
107123
{
108124
var result = methods.First().Invoke(benchmarkTypeInstance, null);
109125

110-
TryToGetTaskResult(result);
126+
var isAsyncMethod = TryAwaitTask(result, out _);
127+
128+
if (!canBeAsync && isAsyncMethod)
129+
{
130+
errors.Add(new ValidationError(
131+
TreatsWarningsAsErrors,
132+
$"[{GetAttributeName(typeof(T))}] cannot be async. Error in type {benchmarkTypeInstance.GetType().Name}"));
133+
134+
return false;
135+
}
111136
}
112137
catch (Exception ex)
113138
{
@@ -121,135 +146,115 @@ private bool TryToCallGlobalMethod<T>(object benchmarkTypeInstance, List<Validat
121146
return true;
122147
}
123148

124-
private static string GetAttributeName(Type type) => type.Name.Replace("Attribute", string.Empty);
125-
126-
private static void TryToGetTaskResult(object result)
149+
private static bool TryAwaitTask(object task, out object result)
127150
{
128-
if (result == null)
151+
result = null;
152+
153+
if (task is null)
129154
{
130-
return;
155+
return false;
131156
}
132157

133-
var returnType = result.GetType();
158+
var returnType = task.GetType();
134159
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
135160
{
136-
var asTaskMethod = result.GetType().GetMethod("AsTask");
137-
result = asTaskMethod.Invoke(result, null);
161+
var asTaskMethod = task.GetType().GetMethod("AsTask");
162+
task = asTaskMethod.Invoke(task, null);
138163
}
139164

140-
if (result is Task task)
141-
{
142-
task.GetAwaiter().GetResult();
143-
}
144-
else if (result is ValueTask valueTask)
165+
if (task is ValueTask valueTask)
166+
task = valueTask.AsTask();
167+
168+
if (task is Task t)
145169
{
146-
valueTask.GetAwaiter().GetResult();
170+
if (TryGetTaskResult(t, out var taskResult))
171+
result = taskResult;
172+
173+
return true;
147174
}
175+
176+
return false;
148177
}
149178

150-
private bool TryToSetParamsFields(object benchmarkTypeInstance, List<ValidationError> errors)
179+
// https://stackoverflow.com/a/52500763
180+
private static bool TryGetTaskResult(Task task, out object result)
151181
{
152-
var paramFields = benchmarkTypeInstance
153-
.GetType()
154-
.GetAllFields()
155-
.Where(fieldInfo => fieldInfo.GetCustomAttributes(false).OfType<ParamsAttribute>().Any())
156-
.ToArray();
182+
task.GetAwaiter().GetResult();
157183

158-
if (!paramFields.Any())
184+
result = null;
185+
186+
var voidTaskType = typeof(Task<>).MakeGenericType(Type.GetType("System.Threading.Tasks.VoidTaskResult"));
187+
if (voidTaskType.IsInstanceOfType(task))
159188
{
160-
return true;
189+
return false;
161190
}
162191

163-
foreach (var paramField in paramFields)
192+
var property = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
193+
if (property is null)
164194
{
165-
if (!paramField.IsPublic)
166-
{
167-
errors.Add(new ValidationError(
168-
TreatsWarningsAsErrors,
169-
$"Fields marked with [Params] must be public, {paramField.Name} of {benchmarkTypeInstance.GetType().Name} is not"));
170-
171-
return false;
172-
}
173-
174-
var values = paramField.GetCustomAttributes(false).OfType<ParamsAttribute>().Single().Values;
175-
if (!values.Any())
176-
{
177-
errors.Add(new ValidationError(
178-
TreatsWarningsAsErrors,
179-
$"Fields marked with [Params] must have some values defined, {paramField.Name} of {benchmarkTypeInstance.GetType().Name} has none"));
180-
181-
return false;
182-
}
183-
184-
try
185-
{
186-
paramField.SetValue(benchmarkTypeInstance, values.First());
187-
}
188-
catch (Exception ex)
189-
{
190-
errors.Add(new ValidationError(
191-
TreatsWarningsAsErrors,
192-
$"Failed to set {paramField.Name} of {benchmarkTypeInstance.GetType().Name} to {values.First()}, exception was: {GetDisplayExceptionMessage(ex)}"));
193-
194-
return false;
195-
}
195+
return false;
196196
}
197197

198+
result = property.GetValue(task);
198199
return true;
199200
}
200201

201-
private bool TryToSetParamsProperties(object benchmarkTypeInstance, List<ValidationError> errors)
202+
private bool TryToFillParameters(BenchmarkCase benchmark, object benchmarkTypeInstance, List<ValidationError> errors)
202203
{
203-
var paramProperties = benchmarkTypeInstance
204-
.GetType()
205-
.GetAllProperties()
206-
.Where(propertyInfo => propertyInfo.GetCustomAttributes(false).OfType<ParamsAttribute>().Any())
207-
.ToArray();
204+
if (ValidateMembers<ParamsAttribute>(benchmarkTypeInstance, errors))
205+
return false;
208206

209-
if (!paramProperties.Any())
210-
{
211-
return true;
212-
}
207+
if (ValidateMembers<ParamsSourceAttribute>(benchmarkTypeInstance, errors))
208+
return false;
209+
210+
bool hasError = false;
213211

214-
foreach (var paramProperty in paramProperties)
212+
foreach (var parameter in benchmark.Parameters.Items)
215213
{
216-
var setter = paramProperty.SetMethod;
217-
if (setter == null || !setter.IsPublic)
214+
// InProcessNoEmitToolchain does not support arguments
215+
if (!parameter.IsArgument)
218216
{
219-
errors.Add(new ValidationError(
220-
TreatsWarningsAsErrors,
221-
$"Properties marked with [Params] must have public setter, {paramProperty.Name} of {benchmarkTypeInstance.GetType().Name} has not"));
222-
223-
return false;
217+
try
218+
{
219+
InProcessNoEmitRunner.FillMember(benchmarkTypeInstance, benchmark, parameter);
220+
}
221+
catch (Exception ex)
222+
{
223+
hasError = true;
224+
errors.Add(new ValidationError(
225+
TreatsWarningsAsErrors,
226+
ex.Message,
227+
benchmark));
228+
}
224229
}
230+
}
225231

226-
var values = paramProperty.GetCustomAttributes(false).OfType<ParamsAttribute>().Single().Values;
227-
if (!values.Any())
228-
{
229-
errors.Add(new ValidationError(
230-
TreatsWarningsAsErrors,
231-
$"Properties marked with [Params] must have some values defined, {paramProperty.Name} of {benchmarkTypeInstance.GetType().Name} has not"));
232+
return !hasError;
233+
}
232234

233-
return false;
234-
}
235+
private bool ValidateMembers<T>(object benchmarkTypeInstance, List<ValidationError> errors) where T : Attribute
236+
{
237+
const BindingFlags reflectionFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
235238

236-
try
237-
{
238-
setter.Invoke(benchmarkTypeInstance, new[] { values.First() });
239-
}
240-
catch (Exception ex)
239+
bool hasError = false;
240+
241+
foreach (var member in benchmarkTypeInstance.GetType().GetTypeMembersWithGivenAttribute<T>(reflectionFlags))
242+
{
243+
if (!member.IsPublic)
241244
{
242245
errors.Add(new ValidationError(
243246
TreatsWarningsAsErrors,
244-
$"Failed to set {paramProperty.Name} of {benchmarkTypeInstance.GetType().Name} to {values.First()}, exception was: {GetDisplayExceptionMessage(ex)}"));
247+
$"Member \"{member.Name}\" must be public if it has the [{GetAttributeName(typeof(T))}] attribute applied to it, {member.Name} of {benchmarkTypeInstance.GetType().Name} has not"));
245248

246-
return false;
249+
hasError = true;
247250
}
248251
}
249252

250-
return true;
253+
return hasError;
251254
}
252255

256+
private static string GetAttributeName(Type type) => type.Name.Replace("Attribute", string.Empty);
257+
253258
protected static string GetDisplayExceptionMessage(Exception ex)
254259
{
255260
if (ex is TargetInvocationException targetInvocationException)
@@ -258,6 +263,33 @@ protected static string GetDisplayExceptionMessage(Exception ex)
258263
return ex?.Message ?? "Unknown error";
259264
}
260265

261-
protected abstract void ExecuteBenchmarks(object benchmarkTypeInstance, IEnumerable<BenchmarkCase> benchmarks, List<ValidationError> errors);
266+
protected abstract void ExecuteBenchmarks(IEnumerable<BenchmarkExecutor> executors, List<ValidationError> errors);
267+
268+
protected class BenchmarkExecutor
269+
{
270+
public object Instance { get; }
271+
public BenchmarkCase BenchmarkCase { get; }
272+
273+
public BenchmarkExecutor(object instance, BenchmarkCase benchmarkCase)
274+
{
275+
Instance = instance;
276+
BenchmarkCase = benchmarkCase;
277+
}
278+
279+
public object Invoke()
280+
{
281+
var arguments = BenchmarkCase.Parameters.Items
282+
.Where(parameter => parameter.IsArgument)
283+
.Select(argument => argument.Value)
284+
.ToArray();
285+
286+
var result = BenchmarkCase.Descriptor.WorkloadMethod.Invoke(Instance, arguments);
287+
288+
if (TryAwaitTask(result, out var taskResult))
289+
result = taskResult;
290+
291+
return result;
292+
}
293+
}
262294
}
263295
}

0 commit comments

Comments
 (0)