66using BenchmarkDotNet . Attributes ;
77using BenchmarkDotNet . Extensions ;
88using BenchmarkDotNet . Running ;
9+ using BenchmarkDotNet . Toolchains . InProcess . NoEmit ;
910
1011namespace 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