mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Spawn test node process for integration tests (#252)
* Move tests to module in own file * Add test-node-proc crate * Move test node to main lib, fix compilation errors * Fmt * Attempted port scanning * Use global static port to allocate open ports * Register missing type sizes * Fix port scanning and balances test * Don't wait for blocks on insert_jey test * Fmt * Remove unused dependencies * Download v3.0.0 rust binary * Move substrate bin to home dir * Add test instructions to README * Figure out the $PATH * Remove path echo * Add check for missing substrate binary on the PATH * @niklasad1 review: don't include client feature for tests
This commit is contained in:
+14
-8
@@ -163,7 +163,7 @@ mod tests {
|
||||
subscription::EventSubscription,
|
||||
system::AccountStoreExt,
|
||||
tests::{
|
||||
test_client,
|
||||
test_node_process,
|
||||
TestRuntime,
|
||||
},
|
||||
};
|
||||
@@ -179,7 +179,8 @@ mod tests {
|
||||
let alice = PairSigner::<TestRuntime, _>::new(AccountKeyring::Alice.pair());
|
||||
let bob = PairSigner::<TestRuntime, _>::new(AccountKeyring::Bob.pair());
|
||||
let bob_address = bob.account_id().clone().into();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
|
||||
let alice_pre = client.account(alice.account_id(), None).await.unwrap();
|
||||
let bob_pre = client.account(bob.account_id(), None).await.unwrap();
|
||||
@@ -208,7 +209,8 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_state_total_issuance() {
|
||||
env_logger::try_init().ok();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
let total_issuance = client.total_issuance(None).await.unwrap();
|
||||
assert_ne!(total_issuance, 0);
|
||||
}
|
||||
@@ -216,7 +218,8 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_state_read_free_balance() {
|
||||
env_logger::try_init().ok();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
let account = AccountKeyring::Alice.to_account_id();
|
||||
let info = client.account(&account, None).await.unwrap();
|
||||
assert_ne!(info.data.free, 0);
|
||||
@@ -270,14 +273,16 @@ mod tests {
|
||||
let alice_addr = alice.account_id().clone().into();
|
||||
let hans = PairSigner::<TestRuntime, _>::new(Pair::generate().0);
|
||||
let hans_address = hans.account_id().clone().into();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
client
|
||||
.transfer_and_watch(&alice, &hans_address, 100_000_000_000)
|
||||
.transfer_and_watch(&alice, &hans_address, 100_000_000_000_000_000)
|
||||
.await
|
||||
.unwrap();
|
||||
let res = client
|
||||
.transfer_and_watch(&hans, &alice_addr, 100_000_000_000)
|
||||
.transfer_and_watch(&hans, &alice_addr, 100_000_000_000_000_000)
|
||||
.await;
|
||||
|
||||
if let Err(Error::Runtime(RuntimeError::Module(error))) = res {
|
||||
let error2 = ModuleError {
|
||||
module: "Balances".into(),
|
||||
@@ -295,7 +300,8 @@ mod tests {
|
||||
let alice = PairSigner::<TestRuntime, _>::new(AccountKeyring::Alice.pair());
|
||||
let bob = AccountKeyring::Bob.to_account_id();
|
||||
let bob_addr = bob.clone().into();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
let sub = client.subscribe_events().await.unwrap();
|
||||
let decoder = client.events_decoder();
|
||||
let mut sub = EventSubscription::<TestRuntime>::new(sub, &decoder);
|
||||
|
||||
+5
-3
@@ -62,7 +62,7 @@ mod tests {
|
||||
extrinsic::PairSigner,
|
||||
frame::balances::TransferCall,
|
||||
tests::{
|
||||
test_client,
|
||||
test_node_process,
|
||||
TestRuntime,
|
||||
},
|
||||
};
|
||||
@@ -73,7 +73,8 @@ mod tests {
|
||||
env_logger::try_init().ok();
|
||||
let alice = PairSigner::<TestRuntime, _>::new(AccountKeyring::Alice.pair());
|
||||
let bob = AccountKeyring::Bob.to_account_id().clone().into();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
|
||||
let call = client
|
||||
.encode(TransferCall {
|
||||
@@ -97,7 +98,8 @@ mod tests {
|
||||
env_logger::try_init().ok();
|
||||
let alice = PairSigner::<TestRuntime, _>::new(AccountKeyring::Alice.pair());
|
||||
let bob = AccountKeyring::Bob.to_account_id().into();
|
||||
let (client, _) = test_client().await;
|
||||
let test_node_proc = test_node_process().await;
|
||||
let client = test_node_proc.client();
|
||||
|
||||
let call = client
|
||||
.encode(TransferCall {
|
||||
|
||||
+2
-183
@@ -83,6 +83,8 @@ mod metadata;
|
||||
mod rpc;
|
||||
mod runtimes;
|
||||
mod subscription;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use crate::{
|
||||
error::{
|
||||
@@ -656,186 +658,3 @@ impl codec::Encode for Encoded {
|
||||
self.0.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::storage::{
|
||||
well_known_keys,
|
||||
StorageKey,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use substrate_subxt_client::{
|
||||
DatabaseConfig,
|
||||
KeystoreConfig,
|
||||
Role,
|
||||
SubxtClient,
|
||||
SubxtClientConfig,
|
||||
};
|
||||
use tempdir::TempDir;
|
||||
|
||||
pub(crate) type TestRuntime = crate::NodeTemplateRuntime;
|
||||
|
||||
pub(crate) async fn test_client_with(
|
||||
key: AccountKeyring,
|
||||
) -> (Client<TestRuntime>, TempDir) {
|
||||
env_logger::try_init().ok();
|
||||
let tmp = TempDir::new("subxt-").expect("failed to create tempdir");
|
||||
let config = SubxtClientConfig {
|
||||
impl_name: "substrate-subxt-full-client",
|
||||
impl_version: "0.0.1",
|
||||
author: "substrate subxt",
|
||||
copyright_start_year: 2020,
|
||||
db: DatabaseConfig::RocksDb {
|
||||
path: tmp.path().join("db"),
|
||||
cache_size: 128,
|
||||
},
|
||||
keystore: KeystoreConfig::Path {
|
||||
path: tmp.path().join("keystore"),
|
||||
password: None,
|
||||
},
|
||||
chain_spec: test_node::chain_spec::development_config().unwrap(),
|
||||
role: Role::Authority(key),
|
||||
telemetry: None,
|
||||
wasm_method: Default::default(),
|
||||
};
|
||||
let client = ClientBuilder::new()
|
||||
.set_client(
|
||||
SubxtClient::from_config(config, test_node::service::new_full)
|
||||
.expect("Error creating subxt client"),
|
||||
)
|
||||
.set_page_size(3)
|
||||
.build()
|
||||
.await
|
||||
.expect("Error creating client");
|
||||
(client, tmp)
|
||||
}
|
||||
pub(crate) async fn test_client() -> (Client<TestRuntime>, TempDir) {
|
||||
test_client_with(AccountKeyring::Alice).await
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_insert_key() {
|
||||
// Bob is not an authority, so block production should be disabled.
|
||||
let (client, _tmp) = test_client_with(AccountKeyring::Bob).await;
|
||||
let mut blocks = client.subscribe_blocks().await.unwrap();
|
||||
// get the genesis block.
|
||||
assert_eq!(blocks.next().await.unwrap().number, 0);
|
||||
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
|
||||
client
|
||||
.insert_key(
|
||||
"aura".to_string(),
|
||||
"//Alice".to_string(),
|
||||
public.clone().into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(client
|
||||
.has_key(public.clone().into(), "aura".to_string())
|
||||
.await
|
||||
.unwrap());
|
||||
// Alice is an authority, so blocks should be produced.
|
||||
assert_eq!(blocks.next().await.unwrap().number, 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_tx_transfer_balance() {
|
||||
let mut signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let (client, _) = test_client().await;
|
||||
let nonce = client
|
||||
.account(&AccountKeyring::Alice.to_account_id(), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.nonce;
|
||||
signer.set_nonce(nonce);
|
||||
client
|
||||
.submit(
|
||||
balances::TransferCall {
|
||||
to: &dest,
|
||||
amount: 10_000,
|
||||
},
|
||||
&signer,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// check that nonce is handled correctly
|
||||
signer.increment_nonce();
|
||||
client
|
||||
.submit(
|
||||
balances::TransferCall {
|
||||
to: &dest,
|
||||
amount: 10_000,
|
||||
},
|
||||
&signer,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_hash() {
|
||||
let (client, _) = test_client().await;
|
||||
client.block_hash(None).await.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_block() {
|
||||
let (client, _) = test_client().await;
|
||||
let block_hash = client.block_hash(None).await.unwrap();
|
||||
client.block(block_hash).await.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_read_proof() {
|
||||
let (client, _) = test_client().await;
|
||||
let block_hash = client.block_hash(None).await.unwrap();
|
||||
client
|
||||
.read_proof(
|
||||
vec![
|
||||
StorageKey(well_known_keys::HEAP_PAGES.to_vec()),
|
||||
StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()),
|
||||
],
|
||||
block_hash,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_chain_subscribe_blocks() {
|
||||
let (client, _) = test_client().await;
|
||||
let mut blocks = client.subscribe_blocks().await.unwrap();
|
||||
blocks.next().await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_chain_subscribe_finalized_blocks() {
|
||||
let (client, _) = test_client().await;
|
||||
let mut blocks = client.subscribe_finalized_blocks().await.unwrap();
|
||||
blocks.next().await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_fetch_keys() {
|
||||
let (client, _) = test_client().await;
|
||||
let keys = client
|
||||
.fetch_keys::<system::AccountStore<_>>(4, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(keys.len(), 4)
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_iter() {
|
||||
let (client, _) = test_client().await;
|
||||
let mut iter = client.iter::<system::AccountStore<_>>(None).await.unwrap();
|
||||
let mut i = 0;
|
||||
while let Some(_) = iter.next().await.unwrap() {
|
||||
i += 1;
|
||||
}
|
||||
assert_eq!(i, 4);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -157,7 +157,7 @@ pub enum TransactionStatus<Hash, BlockHash> {
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "client", test))]
|
||||
#[cfg(feature = "client")]
|
||||
use substrate_subxt_client::SubxtClient;
|
||||
|
||||
/// Rpc client wrapper.
|
||||
@@ -169,7 +169,7 @@ pub enum RpcClient {
|
||||
/// JSONRPC client HTTP transport.
|
||||
// NOTE: Arc because `HttpClient` is not clone.
|
||||
Http(Arc<HttpClient>),
|
||||
#[cfg(any(feature = "client", test))]
|
||||
#[cfg(feature = "client")]
|
||||
/// Embedded substrate node.
|
||||
Subxt(SubxtClient),
|
||||
}
|
||||
@@ -186,7 +186,7 @@ impl RpcClient {
|
||||
inner.request(method, params).await.map_err(Into::into)
|
||||
}
|
||||
Self::Http(inner) => inner.request(method, params).await.map_err(Into::into),
|
||||
#[cfg(any(feature = "client", test))]
|
||||
#[cfg(feature = "client")]
|
||||
Self::Subxt(inner) => inner.request(method, params).await.map_err(Into::into),
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ impl RpcClient {
|
||||
)
|
||||
.into())
|
||||
}
|
||||
#[cfg(any(feature = "client", test))]
|
||||
#[cfg(feature = "client")]
|
||||
Self::Subxt(inner) => {
|
||||
inner
|
||||
.subscribe(subscribe_method, params, unsubscribe_method)
|
||||
@@ -234,7 +234,7 @@ impl From<HttpClient> for RpcClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "client", test))]
|
||||
#[cfg(feature = "client")]
|
||||
impl From<SubxtClient> for RpcClient {
|
||||
fn from(client: SubxtClient) -> Self {
|
||||
RpcClient::Subxt(client)
|
||||
|
||||
@@ -203,6 +203,7 @@ impl Runtime for DefaultNodeRuntime {
|
||||
event_type_registry.with_system();
|
||||
event_type_registry.with_balances();
|
||||
event_type_registry.with_session();
|
||||
event_type_registry.with_staking();
|
||||
event_type_registry.with_contracts();
|
||||
event_type_registry.with_sudo();
|
||||
register_default_type_sizes(event_type_registry);
|
||||
@@ -376,14 +377,20 @@ pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>;
|
||||
pub fn register_default_type_sizes<T: Runtime>(
|
||||
event_type_registry: &mut EventTypeRegistry<T>,
|
||||
) {
|
||||
// for types which have all variants with no data, the size is just the index byte.
|
||||
type CLikeEnum = u8;
|
||||
|
||||
// primitives
|
||||
event_type_registry.register_type_size::<bool>("bool");
|
||||
event_type_registry.register_type_size::<u8>("u8");
|
||||
event_type_registry.register_type_size::<u16>("u16");
|
||||
event_type_registry.register_type_size::<u32>("u32");
|
||||
event_type_registry.register_type_size::<u64>("u64");
|
||||
event_type_registry.register_type_size::<u128>("u128");
|
||||
|
||||
event_type_registry.register_type_size::<()>("PhantomData");
|
||||
event_type_registry
|
||||
.register_type_size::<()>("sp_std::marker::PhantomData<(AccountId, Event)>");
|
||||
|
||||
// frame_support types
|
||||
event_type_registry
|
||||
@@ -400,14 +407,22 @@ pub fn register_default_type_sizes<T: Runtime>(
|
||||
event_type_registry.register_type_size::<[u8; 16]>("Kind");
|
||||
|
||||
event_type_registry.register_type_size::<u32>("AccountIndex");
|
||||
event_type_registry.register_type_size::<u32>("AssetId");
|
||||
event_type_registry.register_type_size::<u32>("BountyIndex");
|
||||
event_type_registry.register_type_size::<(u8, u8)>("CallIndex");
|
||||
event_type_registry.register_type_size::<[u8; 32]>("CallHash");
|
||||
event_type_registry.register_type_size::<u32>("PropIndex");
|
||||
event_type_registry.register_type_size::<u32>("ProposalIndex");
|
||||
event_type_registry.register_type_size::<CLikeEnum>("ProxyType");
|
||||
event_type_registry.register_type_size::<u32>("AuthorityIndex");
|
||||
event_type_registry.register_type_size::<u32>("MemberCount");
|
||||
event_type_registry.register_type_size::<u32>("RegistrarIndex");
|
||||
|
||||
event_type_registry.register_type_size::<u8>("VoteThreshold");
|
||||
event_type_registry
|
||||
.register_type_size::<(T::BlockNumber, u32)>("TaskAddress<BlockNumber>");
|
||||
event_type_registry
|
||||
.register_type_size::<(T::BlockNumber, u32)>("Timepoint<BlockNumber>");
|
||||
|
||||
event_type_registry.register_type_size::<AuthorityId>("AuthorityId");
|
||||
event_type_registry.register_type_size::<AuthorityWeight>("AuthorityWeight");
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of substrate-subxt.
|
||||
//
|
||||
// subxt 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.
|
||||
//
|
||||
// subxt 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod node_proc;
|
||||
|
||||
use super::*;
|
||||
use node_proc::TestNodeProcess;
|
||||
use sp_core::storage::{
|
||||
well_known_keys,
|
||||
StorageKey,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
|
||||
/// substrate node should be installed on the $PATH
|
||||
const SUBSTRATE_NODE_PATH: &str = "substrate";
|
||||
|
||||
pub(crate) type TestRuntime = crate::DefaultNodeRuntime;
|
||||
|
||||
pub(crate) async fn test_node_process_with(
|
||||
key: AccountKeyring,
|
||||
) -> TestNodeProcess<TestRuntime> {
|
||||
if which::which(SUBSTRATE_NODE_PATH).is_err() {
|
||||
panic!("A substrate binary should be installed on your path for integration tests. See https://github.com/paritytech/substrate-subxt/tree/master#integration-testing")
|
||||
}
|
||||
|
||||
let proc = TestNodeProcess::<TestRuntime>::build(SUBSTRATE_NODE_PATH)
|
||||
.with_authority(key)
|
||||
.scan_for_open_ports()
|
||||
.spawn::<TestRuntime>()
|
||||
.await;
|
||||
proc.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn test_node_process() -> TestNodeProcess<TestRuntime> {
|
||||
test_node_process_with(AccountKeyring::Alice).await
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_insert_key() {
|
||||
let test_node_process = test_node_process_with(AccountKeyring::Bob).await;
|
||||
let client = test_node_process.client();
|
||||
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
|
||||
client
|
||||
.insert_key(
|
||||
"aura".to_string(),
|
||||
"//Alice".to_string(),
|
||||
public.clone().into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(client
|
||||
.has_key(public.clone().into(), "aura".to_string())
|
||||
.await
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_tx_transfer_balance() {
|
||||
let mut signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let nonce = client
|
||||
.account(&AccountKeyring::Alice.to_account_id(), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.nonce;
|
||||
signer.set_nonce(nonce);
|
||||
client
|
||||
.submit(
|
||||
balances::TransferCall {
|
||||
to: &dest,
|
||||
amount: 10_000,
|
||||
},
|
||||
&signer,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// check that nonce is handled correctly
|
||||
signer.increment_nonce();
|
||||
client
|
||||
.submit(
|
||||
balances::TransferCall {
|
||||
to: &dest,
|
||||
amount: 10_000,
|
||||
},
|
||||
&signer,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_hash() {
|
||||
let node_process = test_node_process().await;
|
||||
node_process.client().block_hash(None).await.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_block() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let block_hash = client.block_hash(None).await.unwrap();
|
||||
client.block(block_hash).await.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_getting_read_proof() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let block_hash = client.block_hash(None).await.unwrap();
|
||||
client
|
||||
.read_proof(
|
||||
vec![
|
||||
StorageKey(well_known_keys::HEAP_PAGES.to_vec()),
|
||||
StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()),
|
||||
],
|
||||
block_hash,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_chain_subscribe_blocks() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let mut blocks = client.subscribe_blocks().await.unwrap();
|
||||
blocks.next().await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_chain_subscribe_finalized_blocks() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let mut blocks = client.subscribe_finalized_blocks().await.unwrap();
|
||||
blocks.next().await;
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_fetch_keys() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let keys = client
|
||||
.fetch_keys::<system::AccountStore<_>>(4, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(keys.len(), 4)
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_iter() {
|
||||
let node_process = test_node_process().await;
|
||||
let client = node_process.client();
|
||||
let mut iter = client.iter::<system::AccountStore<_>>(None).await.unwrap();
|
||||
let mut i = 0;
|
||||
while let Some(_) = iter.next().await.unwrap() {
|
||||
i += 1;
|
||||
}
|
||||
assert_eq!(i, 13);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of substrate-subxt.
|
||||
//
|
||||
// subxt 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.
|
||||
//
|
||||
// subxt 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
Client,
|
||||
ClientBuilder,
|
||||
Runtime,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use std::{
|
||||
ffi::{
|
||||
OsStr,
|
||||
OsString,
|
||||
},
|
||||
net::TcpListener,
|
||||
process,
|
||||
sync::atomic::{
|
||||
AtomicU16,
|
||||
Ordering,
|
||||
},
|
||||
thread,
|
||||
time,
|
||||
};
|
||||
|
||||
/// Spawn a local substrate node for testing subxt.
|
||||
pub struct TestNodeProcess<R: Runtime> {
|
||||
proc: process::Child,
|
||||
client: Client<R>,
|
||||
}
|
||||
|
||||
impl<R> Drop for TestNodeProcess<R>
|
||||
where
|
||||
R: Runtime,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
let _ = self.kill();
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> TestNodeProcess<R>
|
||||
where
|
||||
R: Runtime,
|
||||
{
|
||||
/// Construct a builder for spawning a test node process.
|
||||
pub fn build<S>(program: S) -> TestNodeProcessBuilder
|
||||
where
|
||||
S: AsRef<OsStr> + Clone,
|
||||
{
|
||||
TestNodeProcessBuilder::new(program)
|
||||
}
|
||||
|
||||
/// Attempt to kill the running substrate process.
|
||||
pub fn kill(&mut self) -> Result<(), String> {
|
||||
log::info!("Killing contracts node process {}", self.proc.id());
|
||||
if let Err(err) = self.proc.kill() {
|
||||
let err = format!(
|
||||
"Error killing contracts node process {}: {}",
|
||||
self.proc.id(),
|
||||
err
|
||||
);
|
||||
log::error!("{}", err);
|
||||
return Err(err.into())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the subxt client connected to the running node.
|
||||
pub fn client(&self) -> &Client<R> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a test node process.
|
||||
pub struct TestNodeProcessBuilder {
|
||||
node_path: OsString,
|
||||
authority: Option<AccountKeyring>,
|
||||
scan_port_range: bool,
|
||||
}
|
||||
|
||||
impl TestNodeProcessBuilder {
|
||||
pub fn new<P>(node_path: P) -> TestNodeProcessBuilder
|
||||
where
|
||||
P: AsRef<OsStr>,
|
||||
{
|
||||
Self {
|
||||
node_path: node_path.as_ref().into(),
|
||||
authority: None,
|
||||
scan_port_range: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the authority dev account for a node in validator mode e.g. --alice.
|
||||
pub fn with_authority(&mut self, account: AccountKeyring) -> &mut Self {
|
||||
self.authority = Some(account);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable port scanning to scan for open ports.
|
||||
///
|
||||
/// Allows spawning multiple node instances for tests to run in parallel.
|
||||
pub fn scan_for_open_ports(&mut self) -> &mut Self {
|
||||
self.scan_port_range = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawn the substrate node at the given path, and wait for rpc to be initialized.
|
||||
pub async fn spawn<R>(&self) -> Result<TestNodeProcess<R>, String>
|
||||
where
|
||||
R: Runtime,
|
||||
{
|
||||
let mut cmd = process::Command::new(&self.node_path);
|
||||
cmd.env("RUST_LOG", "error").arg("--dev").arg("--tmp");
|
||||
|
||||
if let Some(authority) = self.authority {
|
||||
let authority = format!("{:?}", authority);
|
||||
let arg = format!("--{}", authority.as_str().to_lowercase());
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let ws_port = if self.scan_port_range {
|
||||
let (p2p_port, http_port, ws_port) = next_open_port()
|
||||
.ok_or("No available ports in the given port range".to_owned())?;
|
||||
|
||||
cmd.arg(format!("--port={}", p2p_port));
|
||||
cmd.arg(format!("--rpc-port={}", http_port));
|
||||
cmd.arg(format!("--ws-port={}", ws_port));
|
||||
ws_port
|
||||
} else {
|
||||
// the default Websockets port
|
||||
9944
|
||||
};
|
||||
|
||||
let ws_url = format!("ws://127.0.0.1:{}", ws_port);
|
||||
|
||||
let mut proc = cmd.spawn().map_err(|e| {
|
||||
format!(
|
||||
"Error spawning substrate node '{}': {}",
|
||||
self.node_path.to_string_lossy(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
// wait for rpc to be initialized
|
||||
const MAX_ATTEMPTS: u32 = 10;
|
||||
let mut attempts = 1;
|
||||
let client = loop {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
log::info!(
|
||||
"Connecting to contracts enabled node, attempt {}/{}",
|
||||
attempts,
|
||||
MAX_ATTEMPTS
|
||||
);
|
||||
let result = ClientBuilder::<R>::new()
|
||||
.set_url(ws_url.clone())
|
||||
.build()
|
||||
.await;
|
||||
match result {
|
||||
Ok(client) => break Ok(client),
|
||||
Err(crate::Error::MissingTypeSizes(e)) => {
|
||||
break Err(crate::Error::MissingTypeSizes(e))
|
||||
}
|
||||
Err(err) => {
|
||||
if attempts < MAX_ATTEMPTS {
|
||||
attempts += 1;
|
||||
continue
|
||||
}
|
||||
break Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
match client {
|
||||
Ok(client) => Ok(TestNodeProcess { proc, client }),
|
||||
Err(err) => {
|
||||
let err = format!(
|
||||
"Failed to connect to node rpc at {} after {} attempts: {}",
|
||||
ws_url, attempts, err
|
||||
);
|
||||
log::error!("{}", err);
|
||||
proc.kill().map_err(|e| {
|
||||
format!("Error killing substrate process '{}': {}", proc.id(), e)
|
||||
})?;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The start of the port range to scan.
|
||||
const START_PORT: u16 = 9900;
|
||||
/// The end of the port range to scan.
|
||||
const END_PORT: u16 = 10000;
|
||||
/// The maximum number of ports to scan before giving up.
|
||||
const MAX_PORTS: u16 = 1000;
|
||||
/// Next available unclaimed port for test node endpoints.
|
||||
static PORT: AtomicU16 = AtomicU16::new(START_PORT);
|
||||
|
||||
/// Returns the next set of 3 open ports.
|
||||
///
|
||||
/// Returns None if there are not 3 open ports available.
|
||||
fn next_open_port() -> Option<(u16, u16, u16)> {
|
||||
let mut ports = Vec::new();
|
||||
let mut ports_scanned = 0u16;
|
||||
loop {
|
||||
let _ = PORT.compare_exchange(
|
||||
END_PORT,
|
||||
START_PORT,
|
||||
Ordering::SeqCst,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
let next = PORT.fetch_add(1, Ordering::SeqCst);
|
||||
match TcpListener::bind(("0.0.0.0", next)) {
|
||||
Ok(_) => {
|
||||
ports.push(next);
|
||||
if ports.len() == 3 {
|
||||
return Some((ports[0], ports[1], ports[2]))
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
ports_scanned += 1;
|
||||
if ports_scanned == MAX_PORTS {
|
||||
return None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user