diff --git a/frame/balances/src/benchmarking.rs b/frame/balances/src/benchmarking.rs index c2ed0ace54100..b36fe1e341de3 100644 --- a/frame/balances/src/benchmarking.rs +++ b/frame/balances/src/benchmarking.rs @@ -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::::reserved_balance(&user).is_zero()); assert_eq!(Balances::::free_balance(&user), balance); + + Ok(()) } impl_benchmark_test_suite! { diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index f5dc513650e21..7110c378d581e 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -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 valid syntax for defining a [Linear](`v2::Linear`)is `Linear` where `A`, and `B` +/// 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` 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::::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 diff --git a/frame/examples/basic/src/benchmarking.rs b/frame/examples/basic/src/benchmarking.rs index 0d2e9c5b3b1b3..4b2ebb41fbda1 100644 --- a/frame/examples/basic/src/benchmarking.rs +++ b/frame/examples/basic/src/benchmarking.rs @@ -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::::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. diff --git a/frame/support/procedural/src/benchmark.rs b/frame/support/procedural/src/benchmark.rs index 6cf1d7ecc23af..1aca2f87b1d1d 100644 --- a/frame/support/procedural/src/benchmark.rs +++ b/frame/support/procedural/src/benchmark.rs @@ -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, call_def: BenchmarkCallDef, verify_stmts: Vec, + last_stmt: Option, extra: bool, skip_meta: bool, + fn_sig: Signature, + fn_vis: Visibility, + fn_attrs: Vec, } -impl BenchmarkDef { - /// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node. - pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result { - let mut params: Vec = Vec::new(); +/// used to parse something compatible with `Result` +#[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>`.", )) +/// 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 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::(seg.to_token_stream())?; + } + Ok(()) +} - let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) }; - let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) }; +/// Parses params such as `x: Linear<0, 1>` +fn parse_params(item_fn: &ItemFn) -> Result> { + let mut params: Vec = 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>`.", + )) + }; - // check param name - let var_span = ident.span(); - let invalid_param_name = || { - return Err(Error::new( + 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() - } + }; + 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::(args) else { return invalid_param(typ.span()) }; - let Ok(start) = args.start.base10_parse::() else { return invalid_param(args.start.span()) }; - let Ok(end) = args.end.base10_parse::() else { return invalid_param(args.end.span()) }; + // 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::(args) else { return invalid_param(typ.span()) }; + let Ok(start) = args.start.base10_parse::() else { return invalid_param(args.start.span()) }; + let Ok(end) = args.end.base10_parse::() 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", + )) + } - 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) +} - params.push(ParamDef { name, typ: typ.clone(), start, end }); - } +/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing +fn missing_call(item_fn: &ItemFn) -> Result { + return Err(Error::new( + item_fn.block.brace_token.span, + "No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body." + )) +} - // #[extrinsic_call] / #[block] handling - let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| { +/// 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::>>()?; - 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 { + 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: '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 = as #codec::Decode> + ::decode(&mut &__benchmarked_call_encoded[..]) + .expect("call is encoded above, encoding must be correct"); + let __origin = #origin.into(); + as #traits::UnfilteredDispatchable>::dispatch_bypass_filter( + __call_decoded, + __origin, + ) + }; ( - // (pre_call, post_call): - quote! { - let __call = Call::<#type_use_generics>::#expr_call; - let __benchmarked_call_encoded = #codec::Encode::encode(&__call); - }, + // (pre_call, post_call, fn_call_body): + pre_call.clone(), + quote!(#post_call?;), quote! { - let __call_decoded = as #codec::Decode> - ::decode(&mut &__benchmarked_call_encoded[..]) - .expect("call is encoded above, encoding must be correct"); - let __origin = #origin.into(); - 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::(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 , 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 = + 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 })) } } diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs new file mode 100644 index 0000000000000..5e332801df830 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr new file mode 100644 index 0000000000000..ab0bff54a8a03 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr @@ -0,0 +1,5 @@ +error: expected `BenchmarkError` + --> tests/benchmark_ui/bad_return_non_benchmark_err.rs:10:27 + | +10 | fn bench() -> Result<(), BenchmarkException> { + | ^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs new file mode 100644 index 0000000000000..a4b0d007eeecb --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr new file mode 100644 index 0000000000000..69d61b4229155 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr @@ -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) { + | ^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs new file mode 100644 index 0000000000000..15289c298aec1 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs @@ -0,0 +1,15 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benchmarks { + #[benchmark] + fn bench() -> Result { + #[block] + {} + Ok(10) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr new file mode 100644 index 0000000000000..4181ea099a14f --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr @@ -0,0 +1,5 @@ +error: expected `()` + --> tests/benchmark_ui/bad_return_non_unit_t.rs:8:23 + | +8 | fn bench() -> Result { + | ^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs new file mode 100644 index 0000000000000..a6a2c61127fa2 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr new file mode 100644 index 0000000000000..1f8c9f1e171eb --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr @@ -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>` is not implemented for `()` diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs new file mode 100644 index 0000000000000..76f1299005309 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr new file mode 100644 index 0000000000000..ff501a620fe33 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr @@ -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 | | } + | |_____^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs new file mode 100644 index 0000000000000..c206ec36a151e --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr new file mode 100644 index 0000000000000..b830b8eb59c63 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr @@ -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 { + | ^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs new file mode 100644 index 0000000000000..4b55885939747 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs @@ -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 { + #[block] + {} + assert_eq!(2 + 2, 4); + None + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr new file mode 100644 index 0000000000000..050da1676735a --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr @@ -0,0 +1,5 @@ +error: expected `Result` + --> tests/benchmark_ui/bad_return_type_option.rs:10:16 + | +10 | fn bench() -> Option { + | ^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/empty_function.rs b/frame/support/test/tests/benchmark_ui/empty_function.rs new file mode 100644 index 0000000000000..bc04101dd384a --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/empty_function.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/empty_function.stderr b/frame/support/test/tests/benchmark_ui/empty_function.stderr new file mode 100644 index 0000000000000..69d75303613d9 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/empty_function.stderr @@ -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() {} + | ^^ diff --git a/frame/support/test/tests/benchmark_ui/missing_call.rs b/frame/support/test/tests/benchmark_ui/missing_call.rs index bc04101dd384a..f39e74286b5cb 100644 --- a/frame/support/test/tests/benchmark_ui/missing_call.rs +++ b/frame/support/test/tests/benchmark_ui/missing_call.rs @@ -7,7 +7,9 @@ mod benches { use super::*; #[benchmark] - fn bench() {} + fn bench() { + assert_eq!(2 + 2, 4); + } } fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/missing_call.stderr b/frame/support/test/tests/benchmark_ui/missing_call.stderr index 3b55f77d42562..908d970439227 100644 --- a/frame/support/test/tests/benchmark_ui/missing_call.stderr +++ b/frame/support/test/tests/benchmark_ui/missing_call.stderr @@ -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 | | } + | |_____^ diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs b/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs new file mode 100644 index 0000000000000..4930aedd6011e --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs b/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs new file mode 100644 index 0000000000000..ce09b437a83bd --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs b/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs new file mode 100644 index 0000000000000..4930aedd6011e --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs @@ -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() {} diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_result.rs b/frame/support/test/tests/benchmark_ui/pass/valid_result.rs new file mode 100644 index 0000000000000..33d71ece4a018 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_result.rs @@ -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() {}