https://github.com/paritytech/substrate-test-runner
An experimental project to enable writing Substrate integration tests easily.
https://github.com/paritytech/substrate-test-runner
Last synced: about 1 year ago
JSON representation
An experimental project to enable writing Substrate integration tests easily.
- Host: GitHub
- URL: https://github.com/paritytech/substrate-test-runner
- Owner: paritytech
- Created: 2020-06-03T10:41:50.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-03-25T00:17:57.000Z (about 3 years ago)
- Last Synced: 2023-04-09T19:14:17.007Z (about 3 years ago)
- Language: Rust
- Size: 1.39 MB
- Stars: 13
- Watchers: 8
- Forks: 4
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Substrate Test Runner
Allows you to test
- Migrations
- Runtime Upgrades
- Pallets and general runtime functionality.
This works by running a full node with a ManualSeal-BABE™ hybrid consensus for block authoring.
The test runner provides two apis of note
- `seal_blocks(count: u32)`
This tells manual seal authorship task running on the node to author `count` number of blocks, including any transactions in the transaction pool in those blocks.
- `submit_extrinsic(call: Impl Into, from: T::AccountId)`
Providing a `Call` and an `AccountId`, creates an `UncheckedExtrinsic` with an empty signature and sends to the node to be included in future block.
Note
The running node has no signature verification, which allows us author extrinsics for any account on chain.
How do I Use this?
```rust
/// tons of ignored imports
use substrate_test_runner::{TestRequirements, Node};
struct Requirements;
impl TestRequirements for Requirements {
/// Provide a Block type with an OpaqueExtrinsic
type Block = polkadot_core_primitives::Block;
/// Provide an Executor type for the runtime
type Executor = polkadot_service::PolkadotExecutor;
/// Provide the runtime itself
type Runtime = polkadot_runtime::Runtime;
/// A touch of runtime api
type RuntimeApi = polkadot_runtime::RuntimeApi;
/// A pinch of SelectChain implementation
type SelectChain = sc_consensus::LongestChain, Self::Block>;
/// A slice of concrete BlockImport type
type BlockImport = BlockImport<
Self::Block,
TFullBackend,
TFullClient,
Self::SelectChain,
>;
/// and a dash of SignedExtensions
type SignedExtension = SignedExtra;
/// Load the chain spec for your runtime here.
fn load_spec() -> Result, String> {
let wasm_binary = polkadot_runtime::WASM_BINARY.ok_or("Polkadot development wasm not available")?;
Ok(Box::new(PolkadotChainSpec::from_genesis(
"Development",
"polkadot",
ChainType::Development,
move || polkadot_development_config_genesis(wasm_binary),
vec![],
None,
Some("dot"),
None,
Default::default(),
)))
}
/// Optionally provide the base path if you want to fork an existing chain.
// fn base_path() -> Option<&'static str> {
// Some("/home/seun/.local/share/polkadot")
// }
/// Create your signed extras here.
fn signed_extras(
from: ::AccountId,
) -> Self::SignedExtension
where
S: StateProvider
{
let nonce = frame_system::Module::::account_nonce(from);
(
frame_system::CheckSpecVersion::::new(),
frame_system::CheckTxVersion::::new(),
frame_system::CheckGenesis::::new(),
frame_system::CheckMortality::::from(Era::Immortal),
frame_system::CheckNonce::::from(nonce),
frame_system::CheckWeight::::new(),
pallet_transaction_payment::ChargeTransactionPayment::::from(0),
polkadot_runtime_common::claims::PrevalidateAttests::::new(),
)
}
/// The function signature tells you all you need to know. ;)
fn create_client_parts(config: &Configuration) -> Result<
(
Arc>,
Arc>,
KeyStorePtr,
TaskManager,
InherentDataProviders,
Option,
Self::Block
>,
>
>>,
Self::SelectChain,
Self::BlockImport
),
sc_service::Error
> {
let (
client,
backend,
keystore,
task_manager,
) = new_full_parts::(config)?;
let client = Arc::new(client);
let inherent_providers = InherentDataProviders::new();
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let (grandpa_block_import, ..) =
sc_finality_grandpa::block_import(client.clone(), &(client.clone() as Arc<_>), select_chain.clone())?;
let (block_import, babe_link) = sc_consensus_babe::block_import(
sc_consensus_babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
)?;
let consensus_data_provider = BabeConsensusDataProvider::new(
client.clone(),
keystore.clone(),
&inherent_providers,
babe_link.epoch_changes().clone(),
vec![(AuthorityId::from(Alice.public()), 1000)]
)
.expect("failed to create ConsensusDataProvider");
Ok((
client,
backend,
keystore,
task_manager,
inherent_providers,
Some(Box::new(consensus_data_provider)),
select_chain,
block_import
))
}
}
/// And now for the most basic test
#[test]
fn simple_balances_test() {
// given
let mut node = Node::::new();
type Balances = pallet_balances::Module;
let (alice, bob) = (Sr25519Keyring::Alice.pair(), Sr25519Keyring::Bob.pair());
let (alice_account_id, bob_acount_id) = (
MultiSigner::from(alice.public()).into_account(),
MultiSigner::from(bob.public()).into_account()
);
/// the function with_state allows us to read state, pretty cool right? :D
let old_balance = node.with_state(|| Balances::free_balance(alice_account_id.clone()));
// 70 dots
let amount = 70_000_000_000_000;
/// Send extrinsic in action.
node.submit_extrinsic(BalancesCall::transfer(bob_acount_id.clone(), amount), alice_account_id.clone());
/// Produce blocks in action, Powered by manual-seal™.
node.seal_blocks(1);
/// we can check the new state :D
let new_balance = node.with_state(|| Balances::free_balance(alice_account_id));
/// we can now make assertions on how state has changed.
assert_eq!(old_balance + amount, new_balance);
}
```