generation of real benchmark functions for benchmarking v2 (#13224)

* function generation with _name working, need to modify signature

* WIP

* support custom BenchmarkResult<T> type

* full support for BenchmarkResult<T> on benchmark function defs

* support () return type for benchmark function defs that don't use ?

* uncomment

* fix where clause handling

* fix benchmark function call bodies

* proper parsing of return type

* add UI tests for bad return type

* fix detection of missing last_stmt with defined return type

* UI tests covering missing last_stmt

* properly detect and complain about empty benchmark function defs

* fix missing Comma in Result<T, BenchmarkError> parsing + test

* add additional UI test

* allow complex path for BenchmarkResult and BenchmarkError in fn defs

* add UI tests covering complex path for BenchmarkResult, BenchmarkError

* retain doc comments and attributes

* also add attributes to struct

* add docs for benchmark function definition support

* fix imports on benchmark example

* fix issue with unused variables in extrinsic call fn def

* fix up docs

* remove support for v2::BenchmarkResult because it was confusing

* fix typo

* remove ability to use custom T for Result<T, BenchmarkError> in v2

* use missing call error instead of empty_fn()

* remove unneeded match statement

* Add a proper QED

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* fix other QED

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* cargo fmt

* add an explicit error for non TypePath as return type

* tweak error warning and add a UI test for non TypePath return

* remove comment

* add docs about T and I generic params

* improve docs referring to section "below"

* pull out return type checking logic into its own function

* pull out params parsing into its own function

* pull out call_def parsing into its own function

* add doc comment for missing_call()

* replace spaces with tabs

* add a result-based example to the benchmarking examples

---------

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
Sam Johnson
2023-02-22 09:09:11 -05:00
committed by GitHub
parent af25310eb0
commit 55ff791d80
26 changed files with 604 additions and 98 deletions
+3 -1
View File
@@ -225,7 +225,7 @@ mod benchmarks {
}
#[benchmark]
fn force_unreserve() {
fn force_unreserve() -> Result<(), BenchmarkError> {
let user: T::AccountId = account("user", 0, SEED);
let user_lookup = T::Lookup::unlookup(user.clone());
@@ -244,6 +244,8 @@ mod benchmarks {
assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
assert_eq!(Balances::<T, I>::free_balance(&user), balance);
Ok(())
}
impl_benchmark_test_suite! {
+90 -16
View File
@@ -114,21 +114,22 @@ pub use v1::*;
/// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual
/// benchmarks using the `#[benchmark]` attribute, as shown in the example above.
///
/// The `#[benchmark]` attribute expects a function definition with a blank return type and
/// zero or more arguments whose names are valid
/// [BenchmarkParameter](`crate::BenchmarkParameter`) parameters, such as `x`, `y`, `a`, `b`,
/// etc., and whose param types must implement [ParamRange](`v2::ParamRange`). At the moment
/// the only valid type that implements [ParamRange](`v2::ParamRange`) is
/// [Linear](`v2::Linear`).
/// The `#[benchmark]` attribute expects a function definition with a blank return type (or a
/// return type compatible with `Result<(), BenchmarkError>`, as discussed below) and zero or
/// more arguments whose names are valid [BenchmarkParameter](`crate::BenchmarkParameter`)
/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement
/// [ParamRange](`v2::ParamRange`). At the moment the only valid type that implements
/// [ParamRange](`v2::ParamRange`) is [Linear](`v2::Linear`).
///
/// The valid syntax for defining a [Linear](`v2::Linear`)is `Linear<A, B>` where `A`, and `B`
/// The valid syntax for defining a [Linear](`v2::Linear`) is `Linear<A, B>` where `A`, and `B`
/// are valid integer literals (that fit in a `u32`), such that `B` >= `A`.
///
/// Note that the benchmark function definition does not actually expand as a function
/// definition, but rather is used to automatically create a number of impls and structs
/// required by the benchmarking engine. For this reason, the visibility of the function
/// definition as well as the return type are not used for any purpose and are discarded by the
/// expansion code.
/// Anywhere within a benchmark function you may use the generic `T: Config` parameter as well
/// as `I` in the case of an `#[instance_benchmarks]` module. You should not add these to the
/// function signature as this will be handled automatically for you based on whether this is a
/// `#[benchmarks]` or `#[instance_benchmarks]` module and whatever [where clause](#where-clause)
/// you have defined for the the module. You should not manually add any generics to the
/// signature of your benchmark function.
///
/// Also note that the `// setup code` and `// verification code` comments shown above are not
/// required and are included simply for demonstration purposes.
@@ -189,10 +190,10 @@ pub use v1::*;
///
/// #### `skip_meta`
///
/// Specifies that the benchmarking framework should not analyze the storage keys that
/// Specifies that the benchmarking framework should not analyze the storage keys that the
/// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown
/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis
/// of all accesses, not just ones without metadata.
/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis of
/// all accesses, not just ones without metadata.
///
/// ## Where Clause
///
@@ -231,6 +232,79 @@ pub use v1::*;
/// );
/// }
/// ```
///
/// ## Benchmark Function Generation
///
/// The benchmark function definition that you provide is used to automatically create a number
/// of impls and structs required by the benchmarking engine. Additionally, a benchmark
/// function is also generated that resembles the function definition you provide, with a few
/// modifications:
/// 1. The function name is transformed from i.e. `original_name` to `_original_name` so as not
/// to collide with the struct `original_name` that is created for some of the benchmarking
/// engine impls.
/// 2. Appropriate `T: Config` and `I` (if this is an instance benchmark) generics are added to
/// the function automatically during expansion, so you should not add these manually on
/// your function definition (but you may make use of `T` and `I` anywhere within your
/// benchmark function, in any of the three sections (setup, call, verification).
/// 3. Arguments such as `u: Linear<10, 100>` are converted to `u: u32` to make the function
/// directly callable.
/// 4. A `verify: bool` param is added as the last argument. Specifying `true` will result in
/// the verification section of your function executing, while a value of `false` will skip
/// verification.
/// 5. If you specify a return type on the function definition, it must conform to the [rules
/// below](#support-for-result-benchmarkerror-and-the--operator), and the last statement of
/// the function definition must resolve to something compatible with `Result<(),
/// BenchmarkError>`.
///
/// The reason we generate an actual function as part of the expansion is to allow the compiler
/// to enforce several constraints that would otherwise be difficult to enforce and to reduce
/// developer confusion (especially regarding the use of the `?` operator, as covered below).
///
/// Note that any attributes, comments, and doc comments attached to your benchmark function
/// definition are also carried over onto the resulting benchmark function and the struct for
/// that benchmark. As a result you should be careful about what attributes you attach here as
/// they will be replicated in multiple places.
///
/// ### Support for `Result<(), BenchmarkError>` and the `?` operator
///
/// You may optionally specify `Result<(), BenchmarkError>` as the return type of your
/// benchmark function definition. If you do so, you must return a compatible `Result<(),
/// BenchmarkError>` as the *last statement* of your benchmark function definition. You may
/// also use the `?` operator throughout your benchmark function definition if you choose to
/// follow this route. See the example below:
///
/// ```ignore
/// #![cfg(feature = "runtime-benchmarks")]
///
/// use super::{mock_helpers::*, Pallet as MyPallet};
/// use frame_benchmarking::v2::*;
///
/// #[benchmarks]
/// mod benchmarks {
/// use super::*;
///
/// #[benchmark]
/// fn bench_name(x: Linear<5, 25>) -> Result<(), BenchmarkError> {
/// // setup code
/// let z = x + 4;
/// let caller = whitelisted_caller();
///
/// // note we can make use of the ? operator here because of the return type
/// something(z)?;
///
/// #[extrinsic_call]
/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments);
///
/// // verification code
/// assert_eq!(MyPallet::<T>::my_var(), z);
///
/// // we must return a valid `Result<(), BenchmarkError>` as the last line of our benchmark
/// // function definition. This line is not included as part of the verification code that
/// // appears above it.
/// Ok(())
/// }
/// }
/// ```
pub mod v2 {
pub use super::*;
pub use frame_support_procedural::{
@@ -240,7 +314,7 @@ pub mod v2 {
// Used in #[benchmark] implementation to ensure that benchmark function arguments
// implement [`ParamRange`].
#[doc(hidden)]
pub use static_assertions::assert_impl_all;
pub use static_assertions::{assert_impl_all, assert_type_eq_all};
/// Used by the new benchmarking code to specify that a benchmarking variable is linear
/// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable
@@ -21,11 +21,7 @@
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use frame_benchmarking::v1::{
impl_benchmark_test_suite,
v2::{benchmarks, Linear},
whitelisted_caller,
};
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
@@ -55,19 +51,31 @@ mod benchmarks {
assert_eq!(Pallet::<T>::dummy(), Some(value))
}
// An example method that returns a Result that can be called within a benchmark
fn example_result_method() -> Result<(), BenchmarkError> {
Ok(())
}
// This will measure the execution time of `accumulate_dummy`.
// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
// The benchmark verification phase is omitted.
#[benchmark]
fn accumulate_dummy() {
fn accumulate_dummy() -> Result<(), BenchmarkError> {
let value = 1000u32.into();
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
let caller: T::AccountId = whitelisted_caller();
// an example of calling something result-based within a benchmark using the ? operator
// this necessitates specifying the `Result<(), BenchmarkError>` return type
example_result_method()?;
// You can use `_` if the name of the Call matches the benchmark name.
#[extrinsic_call]
_(RawOrigin::Signed(caller), value);
// need this to be compatible with the return type
Ok(())
}
/// You can write helper functions in here since its a normal Rust module.
@@ -25,11 +25,13 @@ use quote::{quote, quote_spanned, ToTokens};
use syn::{
parenthesized,
parse::{Nothing, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::{Colon2, Comma, Gt, Lt, Paren},
Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, LitInt,
Pat, Path, PathArguments, PathSegment, Result, Stmt, Token, Type, WhereClause,
Pat, Path, PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type,
TypePath, Visibility, WhereClause,
};
mod keywords {
@@ -41,6 +43,8 @@ mod keywords {
custom_keyword!(extra);
custom_keyword!(extrinsic_call);
custom_keyword!(skip_meta);
custom_keyword!(BenchmarkError);
custom_keyword!(Result);
}
/// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`.
@@ -145,62 +149,121 @@ struct BenchmarkDef {
setup_stmts: Vec<Stmt>,
call_def: BenchmarkCallDef,
verify_stmts: Vec<Stmt>,
last_stmt: Option<Stmt>,
extra: bool,
skip_meta: bool,
fn_sig: Signature,
fn_vis: Visibility,
fn_attrs: Vec<Attribute>,
}
impl BenchmarkDef {
/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
let mut params: Vec<ParamDef> = Vec::new();
/// used to parse something compatible with `Result<T, E>`
#[derive(Parse)]
struct ResultDef {
_result_kw: keywords::Result,
_lt: Token![<],
unit: Type,
_comma: Comma,
e_type: TypePath,
_gt: Token![>],
}
// parse params such as "x: Linear<0, 1>"
for arg in &item_fn.sig.inputs {
let invalid_param = |span| {
return Err(Error::new(span, "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", ))
};
let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
// check param name
let var_span = ident.span();
let invalid_param_name = || {
/// Ensures that `ReturnType` is a `Result<(), BenchmarkError>`, if specified
fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> {
if let ReturnType::Type(_, typ) = &item_fn.sig.output {
let non_unit = |span| return Err(Error::new(span, "expected `()`"));
let Type::Path(TypePath { path, qself: _ }) = &**typ else {
return Err(Error::new(
typ.span(),
"Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions",
))
};
let seg = path
.segments
.last()
.expect("to be parsed as a TypePath, it must have at least one segment; qed");
let res: ResultDef = syn::parse2(seg.to_token_stream())?;
// ensure T in Result<T, E> is ()
let Type::Tuple(tup) = res.unit else { return non_unit(res.unit.span()) };
if !tup.elems.is_empty() {
return non_unit(tup.span())
}
let TypePath { path, qself: _ } = res.e_type;
let seg = path
.segments
.last()
.expect("to be parsed as a TypePath, it must have at least one segment; qed");
syn::parse2::<keywords::BenchmarkError>(seg.to_token_stream())?;
}
Ok(())
}
/// Parses params such as `x: Linear<0, 1>`
fn parse_params(item_fn: &ItemFn) -> Result<Vec<ParamDef>> {
let mut params: Vec<ParamDef> = Vec::new();
for arg in &item_fn.sig.inputs {
let invalid_param = |span| {
return Err(Error::new(
span,
"Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.",
))
};
let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
// check param name
let var_span = ident.span();
let invalid_param_name = || {
return Err(Error::new(
var_span,
"Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.",
))
};
let name = ident.ident.to_token_stream().to_string();
if name.len() > 1 {
return invalid_param_name()
};
let Some(name_char) = name.chars().next() else { return invalid_param_name() };
if !name_char.is_alphabetic() || !name_char.is_lowercase() {
return invalid_param_name()
}
// parse type
let typ = &*arg.ty;
let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
let args = segment.arguments.to_token_stream().into();
let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
if end < start {
return Err(Error::new(
args.start.span(),
"The start of a `ParamRange` must be less than or equal to the end",
))
}
params.push(ParamDef { name, typ: typ.clone(), start, end });
};
let name = ident.ident.to_token_stream().to_string();
if name.len() > 1 {
return invalid_param_name()
};
let Some(name_char) = name.chars().next() else { return invalid_param_name() };
if !name_char.is_alphabetic() || !name_char.is_lowercase() {
return invalid_param_name()
}
// #[extrinsic_call] / #[block] handling
let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
// parse type
let typ = &*arg.ty;
let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
let args = segment.arguments.to_token_stream().into();
let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
if end < start {
return Err(Error::new(
args.start.span(),
"The start of a `ParamRange` must be less than or equal to the end",
))
}
params.push(ParamDef { name, typ: typ.clone(), start, end });
}
Ok(params)
}
/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing
fn missing_call<T>(item_fn: &ItemFn) -> Result<T> {
return Err(Error::new(
item_fn.block.brace_token.span,
"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
))
}
/// Finds the `BenchmarkCallDef` and its index (within the list of stmts for the fn) and
/// returns them. Also handles parsing errors for invalid / extra call defs. AKA this is
/// general handling for `#[extrinsic_call]` and `#[block]`
fn parse_call_def(item_fn: &ItemFn) -> Result<(usize, BenchmarkCallDef)> {
// #[extrinsic_call] / #[block] handling
let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
if let Stmt::Semi(Expr::Call(expr_call), _semi) = child {
// #[extrinsic_call] case
expr_call.attrs.iter().enumerate().find_map(|(k, attr)| {
@@ -234,25 +297,60 @@ impl BenchmarkDef {
None
}
}).collect::<Result<Vec<_>>>()?;
let (i, call_def) = match &call_defs[..] {
[(i, call_def)] => (*i, call_def.clone()), // = 1
[] => return Err(Error::new( // = 0
item_fn.block.brace_token.span,
"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
)),
_ => return Err(Error::new( // > 1
Ok(match &call_defs[..] {
[(i, call_def)] => (*i, call_def.clone()), // = 1
[] => return missing_call(item_fn),
_ =>
return Err(Error::new(
call_defs[1].1.attr_span(),
"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark."
"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.",
)),
})
}
impl BenchmarkDef {
/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
let params = parse_params(item_fn)?;
ensure_valid_return_type(item_fn)?;
let (i, call_def) = parse_call_def(&item_fn)?;
let (verify_stmts, last_stmt) = match item_fn.sig.output {
ReturnType::Default =>
// no return type, last_stmt should be None
(Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), None),
ReturnType::Type(_, _) => {
// defined return type, last_stmt should be Result<(), BenchmarkError>
// compatible and should not be included in verify_stmts
if i + 1 >= item_fn.block.stmts.len() {
return Err(Error::new(
item_fn.block.span(),
"Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the \
last statement of your benchmark function definition if you have \
defined a return type. You should return something compatible \
with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement \
or change your signature to a blank return type.",
))
}
let Some(stmt) = item_fn.block.stmts.last() else { return missing_call(item_fn) };
(
Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len() - 1]),
Some(stmt.clone()),
)
},
};
Ok(BenchmarkDef {
params,
setup_stmts: Vec::from(&item_fn.block.stmts[0..i]),
call_def,
verify_stmts: Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]),
verify_stmts,
last_stmt,
extra,
skip_meta,
fn_sig: item_fn.sig.clone(),
fn_vis: item_fn.vis.clone(),
fn_attrs: item_fn.attrs.clone(),
})
}
}
@@ -643,6 +741,7 @@ fn expand_benchmark(
let traits = quote!(#krate::frame_support::traits);
let setup_stmts = benchmark_def.setup_stmts;
let verify_stmts = benchmark_def.verify_stmts;
let last_stmt = benchmark_def.last_stmt;
let test_ident = Ident::new(format!("test_{}", name.to_string()).as_str(), Span::call_site());
// unroll params (prepare for quoting)
@@ -661,7 +760,8 @@ fn expand_benchmark(
true => quote!(T: Config<I>, I: 'static),
};
let (pre_call, post_call) = match benchmark_def.call_def {
// used in the benchmarking impls
let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def {
BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
let mut expr_call = expr_call.clone();
@@ -705,36 +805,97 @@ fn expand_benchmark(
qself: None,
path: Path { leading_colon: None, segments: punct },
});
let pre_call = quote! {
let __call = Call::<#type_use_generics>::#expr_call;
let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
};
let post_call = quote! {
let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
::decode(&mut &__benchmarked_call_encoded[..])
.expect("call is encoded above, encoding must be correct");
let __origin = #origin.into();
<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
__call_decoded,
__origin,
)
};
(
// (pre_call, post_call):
// (pre_call, post_call, fn_call_body):
pre_call.clone(),
quote!(#post_call?;),
quote! {
let __call = Call::<#type_use_generics>::#expr_call;
let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
},
quote! {
let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
::decode(&mut &__benchmarked_call_encoded[..])
.expect("call is encoded above, encoding must be correct");
let __origin = #origin.into();
<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
__call_decoded,
__origin,
)?;
#pre_call
#post_call.unwrap();
},
)
},
BenchmarkCallDef::Block { block, attr_span: _ } => (quote!(), quote!(#block)),
BenchmarkCallDef::Block { block, attr_span: _ } =>
(quote!(), quote!(#block), quote!(#block)),
};
let vis = benchmark_def.fn_vis;
// remove #[benchmark] attribute
let fn_attrs: Vec<&Attribute> = benchmark_def
.fn_attrs
.iter()
.filter(|attr| !syn::parse2::<keywords::benchmark>(attr.path.to_token_stream()).is_ok())
.collect();
// modify signature generics, ident, and inputs, e.g:
// before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>`
// after: `fn _bench <T: Config<I>, I: 'static>(u: u32, verify: bool) -> Result<(),
// BenchmarkError>`
let mut sig = benchmark_def.fn_sig;
sig.generics = parse_quote!(<#type_impl_generics>);
if !where_clause.is_empty() {
sig.generics.where_clause = parse_quote!(where #where_clause);
}
sig.ident =
Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site());
let mut fn_param_inputs: Vec<TokenStream2> =
param_names.iter().map(|name| quote!(#name: u32)).collect();
fn_param_inputs.push(quote!(verify: bool));
sig.inputs = parse_quote!(#(#fn_param_inputs),*);
// used in instance() impl
let impl_last_stmt = match &last_stmt {
Some(stmt) => quote!(#stmt),
None => quote!(Ok(())),
};
let fn_def = quote! {
#(
#fn_attrs
)*
#vis #sig {
#(
#setup_stmts
)*
#fn_call_body
if verify {
#(
#verify_stmts
)*
}
#last_stmt
}
};
// generate final quoted tokens
let res = quote! {
// benchmark function definition
#fn_def
// compile-time assertions that each referenced param type implements ParamRange
#(
#home::assert_impl_all!(#param_types: #home::ParamRange);
)*
#[allow(non_camel_case_types)]
#(
#fn_attrs
)*
struct #name;
#[allow(unused_variables)]
@@ -773,7 +934,7 @@ fn expand_benchmark(
#verify_stmts
)*
}
Ok(())
#impl_last_stmt
}))
}
}
@@ -0,0 +1,19 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Result<(), BenchmarkException> {
let a = 2 + 2;
#[block]
{}
assert_eq!(a, 4);
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: expected `BenchmarkError`
--> tests/benchmark_ui/bad_return_non_benchmark_err.rs:10:27
|
10 | fn bench() -> Result<(), BenchmarkException> {
| ^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,17 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn bench() -> (String, u32) {
#[block]
{}
(String::from("hey"), 23)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions
--> tests/benchmark_ui/bad_return_non_type_path.rs:10:16
|
10 | fn bench() -> (String, u32) {
| ^^^^^^^^^^^^^
@@ -0,0 +1,15 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benchmarks {
#[benchmark]
fn bench() -> Result<u32, BenchmarkError> {
#[block]
{}
Ok(10)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: expected `()`
--> tests/benchmark_ui/bad_return_non_unit_t.rs:8:23
|
8 | fn bench() -> Result<u32, BenchmarkError> {
| ^^^
@@ -0,0 +1,22 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
fn something() -> Result<(), BenchmarkError> {
Ok(())
}
#[benchmark]
fn bench() {
something()?;
#[block]
{}
assert_eq!(2 + 2, 4);
}
}
fn main() {}
@@ -0,0 +1,10 @@
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> tests/benchmark_ui/bad_return_type_blank_with_question.rs:15:14
|
5 | #[benchmarks]
| ------------- this function should return `Result` or `Option` to accept `?`
...
15 | something()?;
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<std::convert::Infallible, frame_benchmarking::BenchmarkError>>` is not implemented for `()`
@@ -0,0 +1,16 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Result<(), BenchmarkError> {
#[block]
{}
}
}
fn main() {}
@@ -0,0 +1,9 @@
error: Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the last statement of your benchmark function definition if you have defined a return type. You should return something compatible with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement or change your signature to a blank return type.
--> tests/benchmark_ui/bad_return_type_no_last_stmt.rs:10:43
|
10 | fn bench() -> Result<(), BenchmarkError> {
| ______________________________________________^
11 | | #[block]
12 | | {}
13 | | }
| |_____^
@@ -0,0 +1,19 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench(y: Linear<1, 2>) -> String {
let a = 2 + 2;
#[block]
{}
assert_eq!(a, 4);
String::from("test")
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: expected `Result`
--> tests/benchmark_ui/bad_return_type_non_result.rs:10:31
|
10 | fn bench(y: Linear<1, 2>) -> String {
| ^^^^^^
@@ -0,0 +1,18 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Option<BenchmarkError> {
#[block]
{}
assert_eq!(2 + 2, 4);
None
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: expected `Result`
--> tests/benchmark_ui/bad_return_type_option.rs:10:16
|
10 | fn bench() -> Option<BenchmarkError> {
| ^^^^^^
@@ -0,0 +1,13 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() {}
}
fn main() {}
@@ -0,0 +1,5 @@
error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
--> tests/benchmark_ui/empty_function.rs:10:13
|
10 | fn bench() {}
| ^^
@@ -7,7 +7,9 @@ mod benches {
use super::*;
#[benchmark]
fn bench() {}
fn bench() {
assert_eq!(2 + 2, 4);
}
}
fn main() {}
@@ -1,5 +1,8 @@
error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
--> tests/benchmark_ui/missing_call.rs:10:13
|
10 | fn bench() {}
| ^^
10 | fn bench() {
| ________________^
11 | | assert_eq!(2 + 2, 4);
12 | | }
| |_____^
@@ -0,0 +1,17 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
#[block]
{}
Ok(())
}
}
fn main() {}
@@ -0,0 +1,16 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() {
#[block]
{}
}
}
fn main() {}
@@ -0,0 +1,17 @@
use frame_benchmarking::v2::*;
#[allow(unused_imports)]
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
#[block]
{}
Ok(())
}
}
fn main() {}
@@ -0,0 +1,18 @@
use frame_benchmarking::v2::*;
use frame_support_test::Config;
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn bench() -> Result<(), BenchmarkError> {
let a = 2 + 2;
#[block]
{}
assert_eq!(a, 4);
Ok(())
}
}
fn main() {}