diff --git a/Cargo.lock b/Cargo.lock index de36be2b53..74e1913efd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6111,14 +6111,9 @@ dependencies = [ [[package]] name = "mock-builder" version = "0.0.1" +source = "git+https://github.com/foss3/runtime-pallet-library?branch=polkadot-v0.9.38#9b315e2491f68f38a023bfd683c2615bac851910" dependencies = [ "frame-support", - "frame-system", - "parity-scale-codec 3.6.4", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c398b2bfb3..279c13c1b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ path = "src/main.rs" [workspace] members = [ - "libs/mock-builder", "libs/mocks", "libs/primitives", "libs/proofs", @@ -71,6 +70,9 @@ members = [ "runtime/integration-tests", ] +[workspace.dependencies] +mock-builder = { git = "https://github.com/foss3/runtime-pallet-library", branch = "polkadot-v0.9.38" } + [dependencies] # third-party dependencies async-trait = "0.1" diff --git a/libs/mock-builder/Cargo.toml b/libs/mock-builder/Cargo.toml deleted file mode 100644 index 50bbcf11c3..0000000000 --- a/libs/mock-builder/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -authors = ["Centrifuge "] -description = "Build mock pallets from traits" -edition = "2021" -license = "LGPL-3.0" -name = "mock-builder" -repository = "https://github.com/centrifuge/centrifuge-chain" -version = "0.0.1" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } - -[dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -scale-info = { version = "2.3.0", features = ["derive"] } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } - -[features] -default = ["std"] -std = [ - "frame-support/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] diff --git a/libs/mock-builder/src/lib.rs b/libs/mock-builder/src/lib.rs deleted file mode 100644 index 5522b1f88e..0000000000 --- a/libs/mock-builder/src/lib.rs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2023 Centrifuge Foundation (centrifuge.io). -// This file is part of Centrifuge chain project. - -// Centrifuge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version (see http://www.gnu.org/licenses). - -// Centrifuge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -//! `mock-builder` allows you to create *mock pallets*. -//! A *mock pallet* is a regular pallet that implements some traits whose -//! behavior can be implemented on the fly by closures. They are perfect for -//! testing because they allow you to customize each test case, getting -//! organized and accurate tests for your pallet. *Mock pallet* is not just a -//! trait mocked, it's a whole pallet that can implement one or more traits and -//! can be added to runtimes. -//! -//! # Motivation -//! -//! Pallets have dependencies. Programming in a -//! [loosely coupled](https://docs.substrate.io/build/pallet-coupling) -//! way is great for getting rid of those dependencies for the implementation. -//! Nevertheless, those dependencies still exist in testing because when the -//! `mock.rs` file is defined, you're forced to give some implementations for -//! the associated types of your pallet `Config`. -//! -//! Then, you are mostly forced to use other pallet configurations -//! getting a [tight coupling](https://docs.substrate.io/build/pallet-coupling/) -//! with them. It has some downsides: -//! - You need to learn how to configure other pallets. -//! - You need to know how those pallets work, because they affect directly the -//! behavior of the pallet you're testing. -//! - The way they work can give you non-completed tests. It means that some -//! paths of your pallet can not be tested because some dependency works in a -//! specific way. -//! - You need a lot of effort maintaining your tests because each time one -//! dependency changes, it can easily break your tests. -//! -//! This doesn't scale well. Frequently some pallet dependencies need in turn to -//! configure their own dependent pallets, making this problem even worse. -//! -//! This is why mocking is so important. It lets you get rid of all these -//! dependencies and related issues, obtaining **loose coupling tests**. -//! -//! There are other crates focusing on this problem, -//! such as [`mockall`](https://docs.rs/mockall/latest/mockall/), -//! but they mock traits. Instead, this crate gives you an entire pallet -//! ready to use in any runtime, implementing the number of traits you specify. -//! -//! ## *Mock pallet* usage -//! -//! Suppose that in our pallet, which we'll call it `my_pallet`, we have an -//! associated type in our `Config`, which implements traits `TraitA` and -//! `TraitB`. Those traits are defined as follows: -//! -//! ```no_run -//! trait TraitA { -//! type AssocA; -//! -//! fn foo() -> Self::AssocA; -//! } -//! -//! trait TraitB { -//! type AssocB; -//! -//! fn bar(a: u64, b: Self::AssocB) -> u32; -//! } -//! ``` -//! -//! We have a really huge pallet that implements a specific behavior for those -//! traits, but we want to get rid of such dependency so we [generate a *mock -//! pallet*](#mock-pallet-creation), we'll call it `pallet_mock_dep`. -//! -//! We can add this *mock pallet* to the runtime as usual: -//! -//! ```ignore -//! frame_support::construct_runtime!( -//! pub enum Runtime where -//! Block = Block, -//! NodeBlock = Block, -//! UncheckedExtrinsic = UncheckedExtrinsic, -//! { -//! System: frame_system, -//! MockDep: pallet_mock_dep, -//! MyPallet: my_pallet, -//! } -//! ); -//! ``` -//! -//! And we configure it as a regular pallet: -//! -//! ```ignore -//! impl pallet_mock_dep::Config for Runtime { -//! type AssocA = bool; -//! type AssocB = u8; -//! } -//! ``` -//! -//! Later in our use case, we can give a behavior for both `foo()` and `bar()` -//! methods in their analogous methods `mock_foo()` and `mock_bar()` which -//! accept a closure. -//! -//! ```ignore -//! #[test] -//! fn correct() { -//! new_test_ext().execute_with(|| { -//! MockDep::mock_foo(|| true); -//! MockDep::mock_bar(|a, b| { -//! assert_eq!(a, 42); -//! assert_eq!(b, false); -//! 23 -//! }); -//! -//! // This method will call foo() and bar() under the hood, running the -//! // closures we just have defined. -//! MyPallet::my_call(); -//! }); -//! } -//! ``` -//! -//! Take a look to the [pallet -//! tests](https://github.com/foss3/runtime-pallet-library/blob/main/mock-builder/tests/pallet.rs) -//! to have a user view of how to use a *mock pallet*. -//! It supports any kind of trait, with reference -//! parameters and generics at trait level and method level. -//! -//! ## Mock pallet creation -//! -//! **NOTE: There is a working progress on this part to generate *mock pallets* -//! automatically using procedural macros. Once done, all this part can be -//! auto-generated.** -//! -//! This crate exports two macros [`register_call!()`] and [`execute_call!()`] -//! that allow you to build a *mock pallet*. -//! -//! - [`register_call!()`] registers a closure where you can define the -//! mock behavior for that method. The method which registers the closure must -//! have the name of the trait method you want to mock prefixed with `mock_`. -//! -//! - [`execute_call!()`] is placed in the trait method implementation and will -//! call the closure previously registered by [`register_call!()`] -//! -//! The only condition to use these macros is to have the following storage in -//! the pallet (it's safe to just copy and paste this snippet in your pallet): -//! -//! ```no_run -//! # #[frame_support::pallet] -//! # mod pallet { -//! # use frame_support::pallet_prelude::*; -//! # #[pallet::config] -//! # pub trait Config: frame_system::Config { } -//! # #[pallet::pallet] -//! # #[pallet::generate_store(pub(super) trait Store)] -//! # pub struct Pallet(_); -//! -//! #[pallet::storage] -//! pub(super) type CallIds = StorageMap< -//! _, -//! Blake2_128Concat, -//! ::Output, -//! mock_builder::CallId, -//! >; -//! -//! # } -//! ``` -//! -//! Following the above example, generating a *mock pallet* for both `TraitA` -//! and `TraitB` is done as follows: -//! -//! ```no_run -//! #[frame_support::pallet] -//! pub mod pallet { -//! # trait TraitA { -//! # type AssocA; -//! # -//! # fn foo() -> Self::AssocA; -//! # } -//! # -//! # trait TraitB { -//! # type AssocB; -//! # -//! # fn bar(a: u64, b: Self::AssocB) -> u32; -//! # } -//! -//! use frame_support::pallet_prelude::*; -//! use mock_builder::{execute_call, register_call}; -//! -//! #[pallet::config] -//! pub trait Config: frame_system::Config { -//! type AssocA; -//! type AssocB; -//! } -//! -//! #[pallet::pallet] -//! #[pallet::generate_store(pub(super) trait Store)] -//! pub struct Pallet(_); -//! -//! #[pallet::storage] -//! pub(super) type CallIds = StorageMap< -//! _, -//! Blake2_128Concat, -//! ::Output, -//! mock_builder::CallId, -//! >; -//! -//! impl Pallet { -//! fn mock_foo(f: impl Fn() -> T::AssocA + 'static) { -//! register_call!(move |()| f()) -//! } -//! -//! fn mock_bar(f: impl Fn(u64, T::AssocB) -> u32 + 'static) { -//! register_call!(move |(a, b)| f(a, b)) -//! } -//! } -//! -//! impl TraitA for Pallet { -//! type AssocA = T::AssocA; -//! -//! fn foo() -> Self::AssocA { -//! execute_call!(()) -//! } -//! } -//! -//! impl TraitB for Pallet { -//! type AssocB = T::AssocB; -//! -//! fn bar(a: u64, b: Self::AssocB) -> u32 { -//! execute_call!((a, b)) -//! } -//! } -//! } -//! ``` -//! -//! If types for the closure of `mock_*` method and trait method don't match, -//! you will obtain a runtime error in your tests. -//! -//! ## Mock Patterns -//! -//! ### Storage pattern -//! In some cases it's pretty common making a mock that returns a value that was -//! set previously by another mock. For this case you can define your "getter" -//! mock inside the definition of the "setter" mock, as follows: -//! -//! ```ignore -//! MyMock::mock_set(|value| MyMock::mock_get(move || value)); -//! ``` -//! -//! Any call to `get()` will return the last value given to `set()`. -//! -//! ### Check internal calls are ordered -//! If you want to test some mocks method are calle in some order, you can -//! define them nested, in the expected order they must be called -//! -//! ```ignore -//! MyMock::mock_first(|| { -//! MyMock::mock_second(|| { -//! MyMock::mock_third(|| { -//! //... -//! }) -//! }) -//! }); -//! -//! -//! // The next method only will be succesful -//! // if it makes the internal calls in order -//! MyPallet::calls_first_second_third(); -//! ``` - -/// Provide functions for register/execute calls -pub mod storage; - -/// Provide functions for handle fuction locations -pub mod location; - -mod util; - -use frame_support::{Blake2_128, StorageHasher, StorageMap}; -use location::FunctionLocation; -pub use storage::CallId; - -/// Prefix that the register functions should have. -pub const MOCK_FN_PREFIX: &str = "mock_"; - -/// Register a mock function into the mock function storage. -/// This function should be called with a locator used as a function -/// identification. -pub fn register(locator: L, f: F) -where - Map: StorageMap<::Output, CallId>, - L: Fn(), - F: Fn(I) -> O + 'static, -{ - let location = FunctionLocation::from(locator) - .normalize() - .strip_name_prefix(MOCK_FN_PREFIX) - .append_type_signature::(); - - Map::insert(location.hash::(), storage::register_call(f)); -} - -/// Execute a function from the function storage. -/// This function should be called with a locator used as a function -/// identification. -pub fn execute(locator: L, input: I) -> O -where - Map: StorageMap<::Output, CallId>, - L: Fn(), -{ - let location = FunctionLocation::from(locator) - .normalize() - .append_type_signature::(); - - let call_id = Map::try_get(location.hash::()) - .unwrap_or_else(|_| panic!("Mock was not found. Location: {location:?}")); - - storage::execute_call(call_id, input).unwrap_or_else(|err| { - panic!("{err}. Location: {location:?}"); - }) -} - -/// Register a mock function into the mock function storage. -/// Same as `register()` but it uses as locator who calls this macro. -#[macro_export] -macro_rules! register_call { - ($f:expr) => {{ - $crate::register::, _, _, _, _>(|| (), $f) - }}; -} - -/// Register a mock function into the mock function storage for a pallet with -/// instances. Same as `register()` but it uses as locator who calls this macro. -#[macro_export] -macro_rules! register_call_instance { - ($f:expr) => {{ - $crate::register::, _, _, _, _>(|| (), $f) - }}; -} - -/// Execute a function from the function storage. -/// Same as `execute()` but it uses as locator who calls this macro. -#[macro_export] -macro_rules! execute_call { - ($input:expr) => {{ - $crate::execute::, _, _, _>(|| (), $input) - }}; -} - -/// Execute a function from the function storage for a pallet with instances. -/// Same as `execute()` but it uses as locator who calls this macro. -#[macro_export] -macro_rules! execute_call_instance { - ($input:expr) => {{ - $crate::execute::, _, _, _>(|| (), $input) - }}; -} diff --git a/libs/mock-builder/src/location.rs b/libs/mock-builder/src/location.rs deleted file mode 100644 index 9cff4abab2..0000000000 --- a/libs/mock-builder/src/location.rs +++ /dev/null @@ -1,158 +0,0 @@ -use frame_support::StorageHasher; - -use super::util::TypeSignature; - -/// Absolute string identification of function. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct FunctionLocation(String); - -impl FunctionLocation { - /// Creates a location for the function which created the given closure used - /// as a locator - pub fn from(_: F) -> Self { - let location = std::any::type_name::(); - let location = &location[..location.len() - "::{{closure}}".len()]; - - // Remove generic attributes from signature if it has any - let location = location - .ends_with('>') - .then(|| { - let mut count = 0; - for (i, c) in location.chars().rev().enumerate() { - if c == '>' { - count += 1; - } else if c == '<' { - count -= 1; - if count == 0 { - return location.split_at(location.len() - i - 1).0; - } - } - } - panic!("Expected '<' symbol to close '>'"); - }) - .unwrap_or(location); - - Self(location.into()) - } - - /// Normalize the location, allowing to identify the function - /// no matter if it belongs to a trait or not. - pub fn normalize(self) -> Self { - let (path, name) = self.0.rsplit_once("::").expect("always ::"); - let path = path - .strip_prefix('<') - .map(|trait_path| trait_path.split_once(" as").expect("always ' as'").0) - .unwrap_or(path); - - Self(format!("{path}::{name}")) - } - - /// Remove the prefix from the function name. - pub fn strip_name_prefix(self, prefix: &str) -> Self { - let (path, name) = self.0.rsplit_once("::").expect("always ::"); - let name = name.strip_prefix(prefix).unwrap_or_else(|| { - panic!( - "Function '{name}' should have a '{prefix}' prefix. Location: {}", - self.0 - ) - }); - - Self(format!("{path}::{name}")) - } - - /// Add a representation of the function input and output types - pub fn append_type_signature(self) -> Self { - Self(format!("{}:{}", self.0, TypeSignature::new::())) - } - - /// Generate a hash of the location - pub fn hash(&self) -> Hasher::Output { - Hasher::hash(self.0.as_bytes()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const PREFIX: &str = "mock_builder::location::tests"; - - trait TraitExample { - fn method() -> FunctionLocation; - fn generic_method>(_: impl Into) -> FunctionLocation; - } - - struct Example; - - impl Example { - fn mock_method() -> FunctionLocation { - FunctionLocation::from(|| ()) - } - - fn mock_generic_method>(_: impl Into) -> FunctionLocation { - FunctionLocation::from(|| ()) - } - } - - impl TraitExample for Example { - fn method() -> FunctionLocation { - FunctionLocation::from(|| ()) - } - - fn generic_method>(_: impl Into) -> FunctionLocation { - FunctionLocation::from(|| ()) - } - } - - #[test] - fn function_location() { - assert_eq!( - Example::mock_method().0, - format!("{PREFIX}::Example::mock_method") - ); - - assert_eq!( - Example::mock_generic_method::(0u8).0, - format!("{PREFIX}::Example::mock_generic_method") - ); - - assert_eq!( - Example::method().0, - format!("<{PREFIX}::Example as {PREFIX}::TraitExample>::method") - ); - - assert_eq!( - Example::generic_method::(0u8).0, - format!("<{PREFIX}::Example as {PREFIX}::TraitExample>::generic_method") - ); - } - - #[test] - fn normalized_function_location() { - assert_eq!( - Example::mock_method().normalize().0, - format!("{PREFIX}::Example::mock_method") - ); - - assert_eq!( - Example::method().normalize().0, - format!("{PREFIX}::Example::method") - ); - } - - #[test] - fn striped_function_location() { - assert_eq!( - Example::mock_method().strip_name_prefix("mock_").0, - format!("{PREFIX}::Example::method") - ); - } - - #[test] - fn appended_type_signature() { - assert_eq!( - Example::mock_method().append_type_signature::().0, - format!("{PREFIX}::Example::mock_method:i8->u8") - ); - } -} diff --git a/libs/mock-builder/src/storage.rs b/libs/mock-builder/src/storage.rs deleted file mode 100644 index 03b764a2e3..0000000000 --- a/libs/mock-builder/src/storage.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! This module is in change of storing closures with the type `Fn(I) -> O` -//! in a static lifetime storage, supporting mixing differents `I` and `O` -//! types. Because we need to merge different closures with different types in -//! the same storage, we use an `u128` as closure identification (composed by -//! the closure function pointer (`u64`) and the pointer to the closure metadata -//! (`u64`). - -use std::{ - cell::RefCell, - collections::HashMap, - fmt, - sync::{Arc, Mutex}, -}; - -use super::util::TypeSignature; - -/// Identify a call in the call storage -pub type CallId = u64; - -struct CallInfo { - /// Closure identification - ptr: u128, - - /// Runtime representation of the closure type. - /// This field is needed to ensure we are getting the correct closure type, - /// since the type at compiler time is lost in the `u128` representation of - /// the closure. - type_signature: TypeSignature, -} - -type Registry = HashMap>>; - -thread_local! { - static CALLS: RefCell = RefCell::new(HashMap::default()); -} - -#[derive(Debug, PartialEq)] -pub enum Error { - CallNotFound, - TypeNotMatch { - expected: TypeSignature, - found: TypeSignature, - }, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::CallNotFound => write!(f, "Trying to call a function that is not registered"), - Error::TypeNotMatch { expected, found } => write!( - f, - "The function is registered but the type mismatches. Expected {expected}, found: {found}", - ), - } - } -} - -/// Register a call into the call storage. -/// The registered call can be uniquely identified by the returned `CallId`. -pub fn register_call O + 'static, I, O>(f: F) -> CallId { - // We box the closure in order to store it in a fixed place of memory, - // and handle it in a more generic way without knowing the specific closure - // implementation. - let f = Box::new(f) as Box O>; - - // We're only interested in the memory address of the closure. - // Box is never dropped after this call. - let ptr: *const dyn Fn(I) -> O = Box::into_raw(f); - - let call = CallInfo { - // We need the transmutation to forget about the type of the closure at compile time, - // and then store closures with different types together. - // SAFETY: transforming a wide pointer (*const dyn) to an u128 is always safe - // because the memory representation is the same. - ptr: unsafe { std::mem::transmute(ptr) }, - // Since we've lost the type representation at compile time, we need to store the type - // representation at runtime, in order to recover later the correct closure - type_signature: TypeSignature::new::(), - }; - - CALLS.with(|state| { - let registry = &mut *state.borrow_mut(); - let call_id = registry.len() as u64; - registry.insert(call_id, Arc::new(Mutex::new(call))); - call_id - }) -} - -/// Execute a call from the call storage identified by a `call_id`. -pub fn execute_call(call_id: CallId, input: I) -> Result { - let expected_type_signature = TypeSignature::new::(); - - let call = CALLS.with(|state| { - let registry = &*state.borrow(); - let call = registry.get(&call_id).ok_or(Error::CallNotFound)?; - Ok(call.clone()) - })?; - - let call = call.lock().unwrap(); - - // We need the runtime type check since we lost the type at compile time. - if expected_type_signature != call.type_signature { - return Err(Error::TypeNotMatch { - expected: expected_type_signature, - found: call.type_signature.clone(), - }); - } - - // SAFETY: - // 1. The existence of this closure ptr in consequent calls is ensured - // thanks to Box::into_raw() at register_call(), - // which takes the Box ownership without dropping it. So, ptr exists forever. - // 2. The type of the transmuted call is ensured in runtime by the above type - // signature check. - // 3. The pointer is correctly aligned because it was allocated by a Box. - // 4. The closure is called once at the same time thanks to the mutex. - let f: &dyn Fn(I) -> O = unsafe { - #[allow(clippy::useless_transmute)] // Clippy hints something erroneous - let ptr: *const dyn Fn(I) -> O = std::mem::transmute(call.ptr); - &*ptr - }; - - Ok(f(input)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn correct_type() { - let func_1 = |n: u8| -> usize { 23 * n as usize }; - let call_id_1 = register_call(func_1); - let result = execute_call::<_, usize>(call_id_1, 2u8); - - assert_eq!(result, Ok(46)); - } - - #[test] - fn different_input_type() { - let func_1 = |n: u8| -> usize { 23 * n as usize }; - let call_id_1 = register_call(func_1); - let result = execute_call::<_, usize>(call_id_1, 'a'); - - assert_eq!( - result, - Err(Error::TypeNotMatch { - expected: TypeSignature::new::(), - found: TypeSignature::new::() - }) - ); - } - - #[test] - fn different_output_type() { - let func_1 = |n: u8| -> usize { 23 * n as usize }; - let call_id_1 = register_call(func_1); - let result = execute_call::<_, char>(call_id_1, 2u8); - - assert_eq!( - result, - Err(Error::TypeNotMatch { - expected: TypeSignature::new::(), - found: TypeSignature::new::() - }) - ); - } - - #[test] - fn no_registered() { - let call_id_1 = 42; - - assert_eq!( - execute_call::<_, usize>(call_id_1, 2u8), - Err(Error::CallNotFound) - ); - } -} diff --git a/libs/mock-builder/src/util.rs b/libs/mock-builder/src/util.rs deleted file mode 100644 index 5b438e8f2c..0000000000 --- a/libs/mock-builder/src/util.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::fmt; - -#[derive(Debug, PartialEq, Clone)] -pub struct TypeSignature(String); - -impl TypeSignature { - pub fn new() -> TypeSignature { - Self(format!( - "{}->{}", - std::any::type_name::(), - std::any::type_name::(), - )) - } -} - -impl fmt::Display for TypeSignature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/libs/mock-builder/tests/pallet.rs b/libs/mock-builder/tests/pallet.rs deleted file mode 100644 index 3dbcae0287..0000000000 --- a/libs/mock-builder/tests/pallet.rs +++ /dev/null @@ -1,309 +0,0 @@ -pub trait TraitA { - fn foo(p1: String, p2: Option); - fn bar(p1: u64, p2: bool) -> Result<(), String>; -} - -pub trait TraitB { - fn qux(p1: String) -> bool; - fn generic_input>(a: A, b: impl Into) -> usize; - fn generic_output>() -> A; - fn reference(a: &i32) -> &i32; -} - -pub trait Storage { - fn set(value: i32); - fn get() -> i32; -} - -#[frame_support::pallet] -pub mod pallet_mock_test { - use frame_support::pallet_prelude::*; - use mock_builder::{execute_call, register_call}; - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::storage] - pub(super) type CallIds = StorageMap< - _, - Blake2_128Concat, - ::Output, - mock_builder::CallId, - >; - - impl Pallet { - pub fn mock_foo(f: impl Fn(String, Option) + 'static) { - register_call!(move |(a, b)| f(a, b)); - } - - pub fn mock_bar(f: impl Fn(u64, bool) -> Result<(), String> + 'static) { - register_call!(move |(a, b)| f(a, b)); - } - - pub fn mock_qux(f: impl Fn(String) -> bool + 'static) { - register_call!(f); - } - - pub fn mock_generic_input, B: Into>(f: impl Fn(A, B) -> usize + 'static) { - register_call!(move |(a, b)| f(a, b)); - } - - pub fn mock_generic_output>(f: impl Fn() -> A + 'static) { - register_call!(move |()| f()); - } - - pub fn mock_reference(f: impl Fn(&i32) -> &i32 + 'static) { - register_call!(f); - } - - pub fn mock_set(f: impl Fn(i32) + 'static) { - register_call!(f); - } - - pub fn mock_get(f: impl Fn() -> i32 + 'static) { - register_call!(move |()| f()); - } - } - - impl super::TraitA for Pallet { - fn foo(a: String, b: Option) { - execute_call!((a, b)) - } - - fn bar(a: u64, b: bool) -> Result<(), String> { - execute_call!((a, b)) - } - } - - impl super::TraitB for Pallet { - fn qux(a: String) -> bool { - execute_call!(a) - } - - fn generic_input>(a: A, b: impl Into) -> usize { - execute_call!((a, b)) - } - - fn generic_output>() -> A { - execute_call!(()) - } - - fn reference(a: &i32) -> &i32 { - execute_call!(a) - } - } - - impl super::Storage for Pallet { - fn set(a: i32) { - execute_call!(a) - } - - fn get() -> i32 { - execute_call!(()) - } - } -} - -#[frame_support::pallet] -pub mod my_pallet { - use super::{TraitA, TraitB}; - - #[pallet::config] - pub trait Config: frame_system::Config { - type ActionAB: TraitA + TraitB; - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - impl Pallet { - pub fn my_call(name: &str, value: u64) -> Result<(), String> { - T::ActionAB::foo(name.into(), Some(value)); - let answer = T::ActionAB::qux(name.into()); - T::ActionAB::bar(value, answer) - } - } -} - -mod mock { - use frame_support::traits::{ConstU16, ConstU32, ConstU64}; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - }; - - use super::{my_pallet, pallet_mock_test}; - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - MockTest: pallet_mock_test, - MyPallet: my_pallet, - } - ); - - impl frame_system::Config for Runtime { - type AccountData = (); - type AccountId = u64; - type BaseCallFilter = frame_support::traits::Everything; - type BlockHashCount = ConstU64<250>; - type BlockLength = (); - type BlockNumber = u64; - type BlockWeights = (); - type DbWeight = (); - type Hash = H256; - type Hashing = BlakeTwo256; - type Header = Header; - type Index = u64; - type Lookup = IdentityLookup; - type MaxConsumers = ConstU32<16>; - type OnKilledAccount = (); - type OnNewAccount = (); - type OnSetCode = (); - type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type SS58Prefix = ConstU16<42>; - type SystemWeightInfo = (); - type Version = (); - } - - impl pallet_mock_test::Config for Runtime {} - - impl my_pallet::Config for Runtime { - type ActionAB = pallet_mock_test::Pallet; - } - - pub fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() - } -} - -mod test { - use frame_support::assert_ok; - - use super::{mock::*, Storage, TraitB}; - - #[test] - fn correct() { - new_test_ext().execute_with(|| { - MockTest::mock_foo(|p1, _| assert_eq!("hello", &p1)); - MockTest::mock_qux(|p1| &p1 == "hello"); - MockTest::mock_bar(|_, p2| match p2 { - true => Ok(()), - false => Err("err".into()), - }); - - assert_ok!(MyPallet::my_call("hello".into(), 42)); - }); - } - - #[test] - #[should_panic] - fn wrong() { - new_test_ext().execute_with(|| { - MockTest::mock_foo(|p1, _| assert_eq!("hello", &p1)); - - assert_ok!(MyPallet::my_call("bye".into(), 42)); - }); - } - - #[test] - #[should_panic] - fn mock_not_configured() { - new_test_ext().execute_with(|| { - assert_ok!(MyPallet::my_call("hello".into(), 42)); - }); - } - - #[test] - #[should_panic] - fn previous_mock_data_is_destroyed() { - correct(); - // The storage is dropped at this time. Mocks no longer found from here. - mock_not_configured(); - } - - #[test] - fn generic_input() { - new_test_ext().execute_with(|| { - MockTest::mock_generic_input(|p1: i8, p2: u8| { - assert_eq!(p1, 1); - assert_eq!(p2, 2); - 8 - }); - MockTest::mock_generic_input(|p1: i16, p2: u16| { - assert_eq!(p1, 3); - assert_eq!(p2, 4); - 16 - }); - - assert_eq!(MockTest::generic_input(1i8, 2u8), 8); - assert_eq!(MockTest::generic_input(3i16, 4u16), 16); - }); - } - - #[test] - #[should_panic] - fn generic_input_not_found() { - new_test_ext().execute_with(|| { - MockTest::mock_generic_input(|p1: i8, p2: u8| { - assert_eq!(p1, 3); - assert_eq!(p2, 4); - 8 - }); - - MockTest::generic_input(3i16, 4u16); - }); - } - - #[test] - fn generic_output() { - new_test_ext().execute_with(|| { - MockTest::mock_generic_output(|| 8i8); - MockTest::mock_generic_output(|| 16i16); - - assert_eq!(MockTest::generic_output::(), 8); - assert_eq!(MockTest::generic_output::(), 16); - }); - } - - #[test] - fn reference() { - new_test_ext().execute_with(|| { - MockTest::mock_reference(|a| a); - - assert_eq!(MockTest::reference(&42), &42); - }); - } - - #[test] - fn get_last_set() { - new_test_ext().execute_with(|| { - MockTest::mock_set(|v| MockTest::mock_get(move || v)); - - MockTest::set(23); - assert_eq!(MockTest::get(), 23); - - MockTest::set(42); - assert_eq!(MockTest::get(), 42); - }); - } -} diff --git a/libs/mocks/Cargo.toml b/libs/mocks/Cargo.toml index baf6bb8a15..ac619b1b9b 100644 --- a/libs/mocks/Cargo.toml +++ b/libs/mocks/Cargo.toml @@ -25,7 +25,7 @@ cfg-traits = { path = "../traits", default-features = false } cfg-types = { path = "../types", default-features = false } orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.38" } -mock-builder = { path = "../../libs/mock-builder" } +mock-builder = { workspace = true } [features] default = ["std"] @@ -51,7 +51,6 @@ runtime-benchmarks = [ "cfg-traits/runtime-benchmarks", "cfg-types/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "mock-builder/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", @@ -60,5 +59,4 @@ try-runtime = [ "cfg-primitives/try-runtime", "cfg-traits/try-runtime", "sp-runtime/try-runtime", - "mock-builder/try-runtime", ] diff --git a/libs/traits/Cargo.toml b/libs/traits/Cargo.toml index a675829fff..5d7867228c 100644 --- a/libs/traits/Cargo.toml +++ b/libs/traits/Cargo.toml @@ -23,7 +23,7 @@ sp-std = { git = "https://github.com/paritytech/substrate", default-features = f [dev-dependencies] cfg-mocks = { path = "../mocks" } cfg-types = { path = "../types" } -mock-builder = { path = "../../libs/mock-builder" } +mock-builder = { workspace = true } [features] default = ["std"] @@ -33,7 +33,6 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "cfg-mocks/runtime-benchmarks", "cfg-types/runtime-benchmarks", - "mock-builder/runtime-benchmarks", ] std = [ "codec/std", @@ -48,5 +47,4 @@ try-runtime = [ "frame-support/try-runtime", "cfg-primitives/try-runtime", "sp-runtime/try-runtime", - "mock-builder/try-runtime", ]