diff --git a/.lock b/.lock new file mode 100644 index 0000000..e69de29 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/crates.js b/crates.js new file mode 100644 index 0000000..3aa3b54 --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["mock_builder"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..b84c484 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
pub const MOCK_FN_PREFIX: &str = "mock_";
Prefix that the register functions should have.
+pub fn execute<Locator, I, O, Get>(locator: Locator, input: I, get: Get) -> Owhere
+ Locator: Fn(),
+ Get: Fn(String) -> Option<CallId>,
Execute a function from the function storage. +This function should be called with a locator used as a function +identification.
+pub fn register<Locator, F, I, O, Insert>(
+ locator: Locator,
+ f: F,
+ insert: Insert
+)where
+ Locator: Fn(),
+ F: Fn(I) -> O + 'static,
+ Insert: Fn(String, CallId),
Register a mock function into the mock function storage. +This function should be called with a locator used as a function +identification.
+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.
Pallets have dependencies. Programming in a
+loosely coupled
+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 +with them. It has some downsides:
+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
,
+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.
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:
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, we’ll call it pallet_mock_dep
.
We can add this mock pallet to the runtime as usual:
+ +frame_support::construct_runtime!(
+ pub struct Runtime {
+ System: frame_system,
+ MockDep: pallet_mock_dep,
+ MyPallet: my_pallet,
+ }
+);
And we configure it as a regular pallet:
+ +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.
#[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 +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.
+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):
+ +
+#[pallet::storage]
+type CallIds<T: Config> = StorageMap<_, _, String, mock_builder::CallId>;
+
Following the above example, generating a mock pallet for both TraitA
+and TraitB
is done as follows:
#[frame_support::pallet(dev_mode)]
+pub mod pallet {
+
+ 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]
+ pub struct Pallet<T>(_);
+
+ #[pallet::storage]
+ type CallIds<T: Config> = StorageMap<_, _, String, mock_builder::CallId>;
+
+ impl<T: Config> Pallet<T> {
+ 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<T: Config> TraitA for Pallet<T> {
+ type AssocA = T::AssocA;
+
+ fn foo() -> Self::AssocA {
+ execute_call!(())
+ }
+ }
+
+ impl<T: Config> TraitB for Pallet<T> {
+ 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.
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:
+ +MyMock::mock_set(|value| MyMock::mock_get(move || value));
Any call to get()
will return the last value given to set()
.
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
+ +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();
pub use storage::CallId;
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
).execute()
but it uses as locator who calls this macro.execute()
but it uses as locator who calls this macro.register()
but it uses as locator who calls this macro.register()
but it uses as locator who calls this macro.pub enum TraitInfo {
+ Yes,
+ No,
+ Whatever,
+}
Indicate how to perform the localtion hash
+See FunctionLocation::hash()
Create hash with trait info, panics if it has not.
+Create hash with no trait info
+Create the hash with the trait info if it has trait info +or not if it has none.
+Provide functions for handle fuction locations
+FunctionLocation::hash()
pub struct FunctionLocation { /* private fields */ }
Absolute string identification of function.
+Creates a location for the function which created the given closure used +as a locator
+Normalize the location, allowing to identify the function +no matter if it belongs to a trait or not.
+Remove the prefix from the function name.
+Remove the trait name from the function name and add such information to
+the location. The location is expected to have the following structure:
+<path>::<TraitInfo>_<name>
Add a representation of the function input and output types
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.Redirecting to macro.execute_call.html...
+ + + \ No newline at end of file diff --git a/mock_builder/macro.execute_call.html b/mock_builder/macro.execute_call.html new file mode 100644 index 0000000..7e44272 --- /dev/null +++ b/mock_builder/macro.execute_call.html @@ -0,0 +1,5 @@ +macro_rules! execute_call { + ($input:expr) => { ... }; +}
Execute a function from the function storage.
+Same as execute()
but it uses as locator who calls this macro.
Redirecting to macro.execute_call_instance.html...
+ + + \ No newline at end of file diff --git a/mock_builder/macro.execute_call_instance.html b/mock_builder/macro.execute_call_instance.html new file mode 100644 index 0000000..099d884 --- /dev/null +++ b/mock_builder/macro.execute_call_instance.html @@ -0,0 +1,5 @@ +macro_rules! execute_call_instance { + ($input:expr) => { ... }; +}
Execute a function from the function storage for a pallet with instances.
+Same as execute()
but it uses as locator who calls this macro.
Redirecting to macro.register_call.html...
+ + + \ No newline at end of file diff --git a/mock_builder/macro.register_call.html b/mock_builder/macro.register_call.html new file mode 100644 index 0000000..f5e2250 --- /dev/null +++ b/mock_builder/macro.register_call.html @@ -0,0 +1,5 @@ +macro_rules! register_call { + ($f:expr) => { ... }; +}
Register a mock function into the mock function storage.
+Same as register()
but it uses as locator who calls this macro.
Redirecting to macro.register_call_instance.html...
+ + + \ No newline at end of file diff --git a/mock_builder/macro.register_call_instance.html b/mock_builder/macro.register_call_instance.html new file mode 100644 index 0000000..949eb25 --- /dev/null +++ b/mock_builder/macro.register_call_instance.html @@ -0,0 +1,5 @@ +macro_rules! register_call_instance { + ($f:expr) => { ... }; +}
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.
pub enum Error {
+ CallNotFound,
+ TypeNotMatch {
+ expected: TypeSignature,
+ found: TypeSignature,
+ },
+}
pub fn execute_call<I, O>(call_id: CallId, input: I) -> Result<O, Error>
Execute a call from the call storage identified by a call_id
.
pub fn register_call<F: Fn(I) -> O + 'static, I, O>(f: F) -> CallId
Register a call into the call storage.
+The registered call can be uniquely identified by the returned CallId
.
Provide functions for register/execute calls
+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
).
call_id
.CallId
.pub type CallId = u64;
Identify a call in the call storage
+mock-builder
allows you to create mock pallets. A mock …","t":"CRFOOAFOOADNENNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGNENLLLFLLLLFLLLLMM","n":["CallId","MOCK_FN_PREFIX","execute","execute_call","execute_call_instance","location","register","register_call","register_call_instance","storage","FunctionLocation","No","TraitInfo","Whatever","Yes","append_type_signature","assimilate_trait_prefix","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","equivalent","fmt","fmt","from","from","from","get","into","into","normalize","strip_name_prefix","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","CallId","CallNotFound","Error","TypeNotMatch","borrow","borrow_mut","eq","execute_call","fmt","fmt","from","into","register_call","to_string","try_from","try_into","type_id","expected","found"],"q":[[0,"mock_builder"],[10,"mock_builder::location"],[45,"mock_builder::storage"],[62,"mock_builder::storage::Error"]],"d":["","Prefix that the register functions should have.","Execute a function from the function storage. This …","Execute a function from the function storage. Same as …","Execute a function from the function storage for a pallet …","Provide functions for handle fuction locations","Register a mock function into the mock function storage. …","Register a mock function into the mock function storage. …","Register a mock function into the mock function storage …","Provide functions for register/execute calls This module …","Absolute string identification of function.","Create hash with no trait info","Indicate how to perform the localtion hash See …","Create the hash with the trait info if it has trait info …","Create hash with trait info, panics if it has not.","Add a representation of the function input and output types","Remove the trait name from the function name and add such …","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Creates a location for the function which created the …","Generate a hash of the location","Calls U::from(self)
.","Calls U::from(self)
.","Normalize the location, allowing to identify the function …","Remove the prefix from the function name.","","","","","","","","","Identify a call in the call storage","","","","","","","Execute a call from the call storage identified by a …","","","Returns the argument unchanged.","Calls U::from(self)
.","Register a call into the call storage. The registered call …","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,3,0,3,3,2,2,3,2,3,2,3,2,3,2,2,2,3,2,3,2,2,2,3,2,2,2,3,2,3,2,3,2,3,2,0,11,0,11,11,11,11,0,11,11,11,11,0,11,11,11,11,13,13],"f":[0,0,[[1,1]],0,0,0,[[1,1,1]],0,0,0,0,0,0,0,0,[2,2],[2,2],[[]],[[]],[[]],[[]],[3,3],[2,2],[[]],[[]],[[2,2],4],[[],4],[[3,5],6],[[2,5],6],[[]],[[]],[1,2],[[2,3],7],[[]],[[]],[2,2],[[2,8],2],[[]],[[]],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],0,0,0,0,[[]],[[]],[[11,11],4],[12,[[9,[11]]]],[[11,5],6],[[11,5],6],[[]],[[]],[1,12],[[],7],[[],9],[[],9],[[],10],0,0],"c":[],"p":[[8,"Fn"],[3,"FunctionLocation"],[4,"TraitInfo"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"String"],[15,"str"],[4,"Result"],[3,"TypeId"],[4,"Error"],[6,"CallId"],[13,"TypeNotMatch"]]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..8fd69d4
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +
// 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:
+//!
+//! ```
+//! 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 struct Runtime {
+//! 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):
+//!
+//! ```
+//! # #[frame_support::pallet(dev_mode)]
+//! # mod pallet {
+//! # use frame_support::pallet_prelude::*;
+//! # #[pallet::config]
+//! # pub trait Config: frame_system::Config { }
+//! # #[pallet::pallet]
+//! # pub struct Pallet<T>(_);
+//!
+//! #[pallet::storage]
+//! type CallIds<T: Config> = StorageMap<_, _, String, mock_builder::CallId>;
+//!
+//! # }
+//! ```
+//!
+//! Following the above example, generating a *mock pallet* for both `TraitA`
+//! and `TraitB` is done as follows:
+//!
+//! ```
+//! #[frame_support::pallet(dev_mode)]
+//! 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]
+//! pub struct Pallet<T>(_);
+//!
+//! #[pallet::storage]
+//! type CallIds<T: Config> = StorageMap<_, _, String, mock_builder::CallId>;
+//!
+//! impl<T: Config> Pallet<T> {
+//! 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<T: Config> TraitA for Pallet<T> {
+//! type AssocA = T::AssocA;
+//!
+//! fn foo() -> Self::AssocA {
+//! execute_call!(())
+//! }
+//! }
+//!
+//! impl<T: Config> TraitB for Pallet<T> {
+//! 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 location::{FunctionLocation, TraitInfo};
+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, F, I, O, Insert>(locator: Locator, f: F, insert: Insert)
+where
+ Locator: Fn(),
+ F: Fn(I) -> O + 'static,
+ Insert: Fn(String, CallId),
+{
+ let location = FunctionLocation::from(locator)
+ .normalize()
+ .strip_name_prefix(MOCK_FN_PREFIX)
+ .assimilate_trait_prefix()
+ .append_type_signature::<I, O>();
+
+ insert(location.get(TraitInfo::Whatever), 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, I, O, Get>(locator: Locator, input: I, get: Get) -> O
+where
+ Locator: Fn(),
+ Get: Fn(String) -> Option<CallId>,
+{
+ let location = FunctionLocation::from(locator)
+ .normalize()
+ .append_type_signature::<I, O>();
+
+ let call_id = get(location.get(TraitInfo::Yes))
+ .or_else(|| get(location.get(TraitInfo::No)))
+ .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, CallIds::<T>::insert);
+ }};
+}
+
+/// 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, CallIds::<T, I>::insert);
+ }};
+}
+
+/// 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, CallIds::<T>::get)
+ }};
+}
+
+/// 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, CallIds::<T, I>::get)
+ }};
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +
use super::util::TypeSignature;
+
+/// Indicate how to perform the localtion hash
+/// See `FunctionLocation::hash()`
+#[derive(Clone, Copy, Debug)]
+pub enum TraitInfo {
+ /// Create hash with trait info, panics if it has not.
+ Yes,
+
+ /// Create hash with no trait info
+ No,
+
+ /// Create the hash with the trait info if it has trait info
+ /// or not if it has none.
+ Whatever,
+}
+
+/// Absolute string identification of function.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct FunctionLocation {
+ location: String,
+ trait_info: Option<String>,
+}
+
+impl FunctionLocation {
+ /// Creates a location for the function which created the given closure used
+ /// as a locator
+ pub fn from<F: Fn()>(_: F) -> Self {
+ let location = std::any::type_name::<F>();
+ 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: location.into(),
+ trait_info: Default::default(),
+ }
+ }
+
+ /// 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.location.rsplit_once("::").expect("always '::'");
+ let (path, trait_info) = match path.strip_prefix('<') {
+ Some(struct_as_trait_path) => {
+ let (struct_path, trait_path) = struct_as_trait_path
+ .split_once(" as")
+ .expect("always ' as'");
+
+ let trait_name = trait_path
+ .split_once('<')
+ .map(|(name, _generics)| name)
+ .unwrap_or(trait_path.strip_suffix('>').unwrap())
+ .rsplit_once("::")
+ .expect("Always '::'")
+ .1;
+
+ (struct_path, Some(trait_name.to_owned()))
+ }
+ None => (path, None),
+ };
+
+ Self {
+ location: format!("{path}::{name}"),
+ trait_info,
+ }
+ }
+
+ /// Remove the prefix from the function name.
+ pub fn strip_name_prefix(self, prefix: &str) -> Self {
+ let (path, name) = self.location.rsplit_once("::").expect("always ::");
+ let name = name.strip_prefix(prefix).unwrap_or_else(|| {
+ panic!(
+ "Function '{name}' should have a '{prefix}' prefix. Location: {}",
+ self.location
+ )
+ });
+
+ Self {
+ location: format!("{path}::{name}"),
+ trait_info: self.trait_info,
+ }
+ }
+
+ /// Remove the trait name from the function name and add such information to
+ /// the location. The location is expected to have the following structure:
+ /// `<path>::<TraitInfo>_<name>`
+ pub fn assimilate_trait_prefix(self) -> Self {
+ let (path, name) = self.location.rsplit_once("::").expect("always ::");
+ let (trait_info, name) = match name.chars().next().unwrap().is_uppercase() {
+ true => {
+ let (trait_info, name) = name.split_once('_').expect("always '_' after trait name");
+ (Some(trait_info.to_owned()), name)
+ }
+ false => (None, name),
+ };
+
+ Self {
+ location: format!("{path}::{name}"),
+ trait_info,
+ }
+ }
+
+ /// Add a representation of the function input and output types
+ pub fn append_type_signature<I, O>(self) -> Self {
+ Self {
+ location: format!("{}:{}", self.location, TypeSignature::new::<I, O>()),
+ trait_info: self.trait_info,
+ }
+ }
+
+ /// Generate a hash of the location
+ pub fn get(&self, trait_info: TraitInfo) -> String {
+ let trait_info = match trait_info {
+ TraitInfo::Yes => self.trait_info.clone().unwrap(),
+ TraitInfo::No => String::default(),
+ TraitInfo::Whatever => self.trait_info.clone().unwrap_or_default(),
+ };
+
+ format!("{},trait={}", self.location, trait_info)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const PREFIX: &str = "mock_builder::location::tests";
+
+ trait TraitExample {
+ fn method() -> FunctionLocation;
+ fn generic_method<A: Into<i32>>(_: impl Into<u32>) -> FunctionLocation;
+ }
+
+ trait TraitExampleGen<G1, G2> {
+ fn generic() -> FunctionLocation;
+ }
+
+ trait Config {
+ type Assoc;
+ }
+
+ struct Example<T>(core::marker::PhantomData<T>);
+
+ impl<T> Example<T> {
+ fn mock_method() -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+
+ #[allow(non_snake_case)]
+ fn mock_TraitExample_method() -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+
+ #[allow(non_snake_case)]
+ fn mock_TraitExampleGen_generic() -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+
+ fn mock_generic_method<A: Into<i32>>(_: impl Into<u32>) -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+ }
+
+ impl<T> TraitExample for Example<T> {
+ fn method() -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+
+ fn generic_method<A: Into<i32>>(_: impl Into<u32>) -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+ }
+
+ impl<T: Config> TraitExampleGen<T::Assoc, bool> for Example<T> {
+ fn generic() -> FunctionLocation {
+ FunctionLocation::from(|| ())
+ }
+ }
+
+ struct TestConfig;
+ impl Config for TestConfig {
+ type Assoc = u32;
+ }
+
+ #[test]
+ fn function_location() {
+ assert_eq!(
+ Example::<TestConfig>::mock_method(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::mock_method"),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::mock_TraitExample_method(),
+ FunctionLocation {
+ location: format!(
+ "{PREFIX}::Example<{PREFIX}::TestConfig>::mock_TraitExample_method"
+ ),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::mock_generic_method::<i8>(0u8),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::mock_generic_method"),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::method(),
+ FunctionLocation {
+ location: format!(
+ "<{PREFIX}::Example<{PREFIX}::TestConfig> as {PREFIX}::TraitExample>::method"
+ ),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::generic_method::<i8>(0u8),
+ FunctionLocation {
+ location: format!("<{PREFIX}::Example<{PREFIX}::TestConfig> as {PREFIX}::TraitExample>::generic_method"),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::generic(),
+ FunctionLocation {
+ location: format!(
+ "<{PREFIX}::Example<{PREFIX}::TestConfig> as {PREFIX}::TraitExampleGen<<{PREFIX}::TestConfig as {PREFIX}::Config>::Assoc, bool>>::generic"
+ ),
+ trait_info: None,
+ }
+ );
+ }
+
+ #[test]
+ fn normalized() {
+ assert_eq!(
+ Example::<TestConfig>::mock_method().normalize(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::mock_method"),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::mock_TraitExample_method().normalize(),
+ FunctionLocation {
+ location: format!(
+ "{PREFIX}::Example<{PREFIX}::TestConfig>::mock_TraitExample_method"
+ ),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::method().normalize(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::method"),
+ trait_info: Some("TraitExample".into()),
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::generic().normalize(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::generic"),
+ trait_info: Some("TraitExampleGen".into()),
+ }
+ );
+ }
+
+ #[test]
+ fn striped_name_prefix() {
+ assert_eq!(
+ Example::<TestConfig>::mock_method().strip_name_prefix("mock_"),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::method"),
+ trait_info: None,
+ }
+ );
+ }
+
+ #[test]
+ fn assimilated_trait_prefix() {
+ assert_eq!(
+ Example::<TestConfig>::mock_method()
+ .strip_name_prefix("mock_")
+ .assimilate_trait_prefix(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::method"),
+ trait_info: None,
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::mock_TraitExample_method()
+ .strip_name_prefix("mock_")
+ .assimilate_trait_prefix(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::method"),
+ trait_info: Some("TraitExample".into()),
+ }
+ );
+
+ assert_eq!(
+ Example::<TestConfig>::mock_TraitExampleGen_generic()
+ .strip_name_prefix("mock_")
+ .assimilate_trait_prefix(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::generic"),
+ trait_info: Some("TraitExampleGen".into()),
+ }
+ );
+ }
+
+ #[test]
+ fn appended_type_signature() {
+ assert_eq!(
+ Example::<TestConfig>::mock_method().append_type_signature::<i8, u8>(),
+ FunctionLocation {
+ location: format!("{PREFIX}::Example<{PREFIX}::TestConfig>::mock_method:i8->u8"),
+ trait_info: None,
+ }
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +
//! 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<CallId, Arc<Mutex<CallInfo>>>;
+
+thread_local! {
+ static CALLS: RefCell<Registry> = 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<F: Fn(I) -> 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<dyn Fn(I) -> 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::<I, O>(),
+ };
+
+ 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<I, O>(call_id: CallId, input: I) -> Result<O, Error> {
+ let expected_type_signature = TypeSignature::new::<I, O>();
+
+ 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::<char, usize>(),
+ found: TypeSignature::new::<u8, usize>()
+ })
+ );
+ }
+
+ #[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::<u8, char>(),
+ found: TypeSignature::new::<u8, usize>()
+ })
+ );
+ }
+
+ #[test]
+ fn no_registered() {
+ let call_id_1 = 42;
+
+ assert_eq!(
+ execute_call::<_, usize>(call_id_1, 2u8),
+ Err(Error::CallNotFound)
+ );
+ }
+}
+
use std::fmt;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct TypeSignature(String);
+
+impl TypeSignature {
+ pub fn new<I, O>() -> TypeSignature {
+ Self(format!(
+ "{}->{}",
+ std::any::type_name::<I>(),
+ std::any::type_name::<O>(),
+ ))
+ }
+}
+
+impl fmt::Display for TypeSignature {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value}
`}else{error[index]=value}});output+=`