-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integration test: Generic runtime and environment #1583
Changes from all commits
45168fa
200d1b4
25d054e
d05c5f5
18aeee6
320945a
7eb6778
99e6892
dbf5ce9
5a44c3b
b4f4cb1
649381b
0a7fa0b
0d514bf
b65d350
b3e52fd
e8fedd5
8ef7fe8
ef9955d
d562ace
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
use cfg_primitives::{AuraId, Balance, CFG}; | ||
use frame_support::traits::Get; | ||
|
||
use crate::{ | ||
generic::{ | ||
environment::{Blocks, Env}, | ||
envs::runtime_env::RuntimeEnv, | ||
runtime::Runtime, | ||
utils::genesis::Genesis, | ||
}, | ||
utils::accounts::Keyring, | ||
}; | ||
|
||
fn transfer_balance<T: Runtime>() { | ||
const TRANSFER: Balance = 1000 * CFG; | ||
const FOR_FEES: Balance = 1 * CFG; | ||
|
||
// Set up all GenesisConfig for your initial state | ||
let mut env = RuntimeEnv::<T>::from_storage( | ||
Genesis::default() | ||
.add(pallet_aura::GenesisConfig::<T> { | ||
authorities: vec![AuraId::from(Keyring::Charlie.public())], | ||
}) | ||
.add(pallet_balances::GenesisConfig::<T> { | ||
balances: vec![( | ||
Keyring::Alice.to_account_id(), | ||
T::ExistentialDeposit::get() + FOR_FEES + TRANSFER, | ||
)], | ||
}) | ||
.storage(), | ||
); | ||
|
||
// Call an extrinsic that would be processed immediately | ||
env.submit( | ||
Keyring::Alice, | ||
pallet_balances::Call::transfer { | ||
dest: Keyring::Bob.into(), | ||
value: TRANSFER, | ||
}, | ||
) | ||
.unwrap(); | ||
|
||
// Check for an even occurred in this block | ||
env.check_event(pallet_balances::Event::Transfer { | ||
from: Keyring::Alice.to_account_id(), | ||
to: Keyring::Bob.to_account_id(), | ||
amount: TRANSFER, | ||
}) | ||
.unwrap(); | ||
|
||
// Pass blocks to evolve the system | ||
env.pass(Blocks::ByNumber(1)); | ||
|
||
// Check the state | ||
env.state(|| { | ||
assert_eq!( | ||
pallet_balances::Pallet::<T>::free_balance(Keyring::Bob.to_account_id()), | ||
TRANSFER | ||
); | ||
}); | ||
} | ||
|
||
fn call_api<T: Runtime>() { | ||
let env = RuntimeEnv::<T>::from_storage( | ||
Genesis::default() | ||
.add(pallet_aura::GenesisConfig::<T> { | ||
authorities: vec![AuraId::from(Keyring::Charlie.public())], | ||
}) | ||
.storage(), | ||
); | ||
|
||
env.state(|| { | ||
// Call to Core::version() API. | ||
// It's automatically implemented by the runtime T, so you can easily do: | ||
// T::version() | ||
assert_eq!(T::version(), <T as frame_system::Config>::Version::get()); | ||
}) | ||
} | ||
|
||
fn check_fee<T: Runtime>() { | ||
let mut env = RuntimeEnv::<T>::from_storage( | ||
Genesis::default() | ||
.add(pallet_aura::GenesisConfig::<T> { | ||
authorities: vec![AuraId::from(Keyring::Charlie.public())], | ||
}) | ||
.add(pallet_balances::GenesisConfig::<T> { | ||
balances: vec![(Keyring::Alice.to_account_id(), 1 * CFG)], | ||
}) | ||
.storage(), | ||
); | ||
|
||
env.submit( | ||
Keyring::Alice, | ||
frame_system::Call::remark { remark: vec![] }, | ||
) | ||
.unwrap(); | ||
|
||
// Get the fee of the last submitted extrinsic | ||
let fee = env.last_fee(); | ||
|
||
env.state(|| { | ||
assert_eq!( | ||
pallet_balances::Pallet::<T>::free_balance(Keyring::Alice.to_account_id()), | ||
1 * CFG - fee | ||
); | ||
}); | ||
} | ||
|
||
// Generate tests for all runtimes | ||
crate::test_for_runtimes!((development, altair, centrifuge), transfer_balance); | ||
crate::test_for_all_runtimes!(call_api); | ||
crate::test_for_all_runtimes!(check_fee); | ||
|
||
// Output: for `cargo test -p runtime-integration-tests transfer_balance` | ||
// running 6 tests | ||
// test generic::cases::example::transfer_balance::altair ... ok | ||
// test generic::cases::example::transfer_balance::development ... ok | ||
// test generic::cases::example::transfer_balance::centrifuge ... ok |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use cfg_primitives::{Balance, BlockNumber, Moment}; | ||
use sp_runtime::{DispatchResult, Storage}; | ||
|
||
use crate::{generic::runtime::Runtime, utils::accounts::Keyring}; | ||
|
||
/// Used by Env::pass() to determine how many blocks should be passed | ||
#[derive(Clone)] | ||
pub enum Blocks<T: Runtime> { | ||
/// Pass X blocks | ||
ByNumber(BlockNumber), | ||
|
||
/// Pass a number of blocks enough to emulate the given passage of time. | ||
/// i.e. choosing 1 sec would pass 1 block to emulate such change in the | ||
/// time. | ||
BySeconds(Moment), | ||
|
||
/// Pass a number of block until find an event or reach the limit | ||
UntilEvent { | ||
event: T::RuntimeEventExt, | ||
limit: BlockNumber, | ||
}, | ||
} | ||
|
||
/// Define an environment behavior | ||
pub trait Env<T: Runtime> { | ||
/// Load the environment from a storage | ||
fn from_storage(storage: Storage) -> Self; | ||
|
||
/// Submit an extrinsic mutating the state | ||
fn submit(&mut self, who: Keyring, call: impl Into<T::RuntimeCall>) -> DispatchResult; | ||
|
||
/// Pass any number of blocks | ||
fn pass(&mut self, blocks: Blocks<T>); | ||
|
||
/// Allows to mutate the storage state through the closure | ||
fn state_mut<R>(&mut self, f: impl FnOnce() -> R) -> R; | ||
|
||
/// Allows to read the storage state through the closure | ||
/// If storage is modified, it would not be applied. | ||
fn state<R>(&self, f: impl FnOnce() -> R) -> R; | ||
|
||
/// Check for an exact event introduced in the current block. | ||
/// Starting from last event introduced | ||
/// Returns an Option to unwrap it from the tests and have good panic | ||
/// message with the error test line | ||
fn check_event(&self, event: impl Into<T::RuntimeEventExt>) -> Option<()> { | ||
self.state(|| { | ||
let event = event.into(); | ||
frame_system::Pallet::<T>::events() | ||
.into_iter() | ||
.rev() | ||
.find(|record| record.event == event) | ||
.map(|_| ()) | ||
}) | ||
} | ||
|
||
/// Find an event introduced in the current block | ||
/// Starting from last event introduced | ||
/// Returns an Option to unwrap it from the tests and have good panic | ||
/// message with the error test line | ||
fn find_event<E, R>(&self, f: impl Fn(E) -> Option<R>) -> Option<R> | ||
where | ||
T::RuntimeEventExt: TryInto<E>, | ||
{ | ||
self.state(|| { | ||
frame_system::Pallet::<T>::events() | ||
.into_iter() | ||
.rev() | ||
.map(|record| record.event.try_into().ok()) | ||
.find_map(|event| event.map(|e| f(e))) | ||
.flatten() | ||
}) | ||
} | ||
|
||
/// Retrieve the fees used in the last submit call | ||
fn last_fee(&self) -> Balance { | ||
self.find_event(|e| match e { | ||
pallet_transaction_payment::Event::TransactionFeePaid { actual_fee, .. } => { | ||
Some(actual_fee) | ||
} | ||
_ => None, | ||
}) | ||
.expect("Expected transaction") | ||
} | ||
Comment on lines
+75
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems pretty detailed for the trait itself... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is, and I've removed it in #1588 Now, Thanks for your post-morten review! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
// TODO: implement generic::env::Env for an environment using fudge | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still want to do this in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this will be a future PR not to block this. That file is just a placeholder. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should create an issue for this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker: The authority pattern is and will be heavily used. IMO, we should add setter functions for commonly executed calls such as this one, i.e.
with_authorities(vec![AuraId::from(Keyring::Charlie.public())]
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah! The idea is to add this kind of thing. By now, in #1588 such genesis are moved to the environment because it's mandatory needed to make aura works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still need a lot of utilities for common stuff as creating assets, etc