Ensure metadata is in sync with running node during tests (#333)

* add test-runtime crate to dynamically obtain metadata/node_runtime for tests

* cargo fmt

* Download substrate prior to cargo calls (hopefully)

* add README explaining test-runtime

* Fix CI, fmt and clippy

* more clippy

* tweak test-node readme

* fmt the clippied

* A little tidy up in build.rs

* use ureq and raw string

* Don't export unnecessary metadata

* async_std/RpcClient/bytes instead of ureq/Value/hex

* newline

* document try_from_url error unwrap
This commit is contained in:
James Wilson
2021-11-29 09:39:40 +00:00
committed by GitHub
parent 7e26f9559c
commit 4b9ee133ac
11 changed files with 200 additions and 20 deletions
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of 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 subxt. If not, see <http://www.gnu.org/licenses/>.
use std::{
env,
fs,
net::TcpListener,
path::Path,
process::Command,
sync::atomic::{
AtomicU16,
Ordering,
},
thread,
time,
};
static SUBSTRATE_BIN_ENV_VAR: &str = "SUBSTRATE_NODE_PATH";
#[async_std::main]
async fn main() {
// Select substrate binary to run based on env var.
let substrate_bin =
env::var(SUBSTRATE_BIN_ENV_VAR).unwrap_or_else(|_| "substrate".to_owned());
// Run binary.
let port = next_open_port()
.expect("Cannot spawn substrate: no available ports in the given port range");
let cmd = Command::new(&substrate_bin)
.arg("--dev")
.arg("--tmp")
.arg(format!("--rpc-port={}", port))
.spawn();
let mut cmd = match cmd {
Ok(cmd) => cmd,
Err(e) => {
panic!("Cannot spawn substrate command '{}': {}", substrate_bin, e)
}
};
// Download metadata from binary; retry until successful, or a limit is hit.
let metadata_bytes: sp_core::Bytes = {
const MAX_RETRIES: usize = 20;
let mut retries = 0;
let mut wait_secs = 1;
loop {
if retries >= MAX_RETRIES {
panic!("Cannot connect to substrate node after {} retries", retries);
}
let res =
subxt::RpcClient::try_from_url(&format!("http://localhost:{}", port))
.await
.expect("should only error if malformed URL for an HTTP connection")
.request("state_getMetadata", &[])
.await;
match res {
Ok(res) => {
let _ = cmd.kill();
break res
}
_ => {
thread::sleep(time::Duration::from_secs(wait_secs));
retries += 1;
wait_secs += 1;
}
};
}
};
// Save metadata to a file:
let out_dir = env::var_os("OUT_DIR").unwrap();
let metadata_path = Path::new(&out_dir).join("metadata.scale");
fs::write(&metadata_path, &metadata_bytes.0).expect("Couldn't write metadata output");
// Write out our expression to generate the runtime API to a file. Ideally, we'd just write this code
// in lib.rs, but we must pass a string literal (and not `concat!(..)`) as an arg to runtime_metadata_path,
// and so we need to spit it out here and include it verbatim instead.
let runtime_api_contents = format!(
r#"
#[subxt::subxt(
runtime_metadata_path = "{}",
generated_type_derives = "Debug, Eq, PartialEq"
)]
pub mod node_runtime {{
#[subxt(substitute_type = "sp_arithmetic::per_things::Perbill")]
use sp_runtime::Perbill;
}}
"#,
metadata_path
.to_str()
.expect("Path to metadata should be stringifiable")
);
let runtime_path = Path::new(&out_dir).join("runtime.rs");
fs::write(&runtime_path, runtime_api_contents)
.expect("Couldn't write runtime rust output");
// Re-build if we point to a different substrate binary:
println!("cargo:rerun-if-env-changed={}", SUBSTRATE_BIN_ENV_VAR);
// Re-build if this file changes:
println!("cargo:rerun-if-changed=build.rs");
}
/// Returns the next open port, or None if no port found in range.
fn next_open_port() -> Option<u16> {
/// 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;
let next_port: AtomicU16 = AtomicU16::new(START_PORT);
let mut ports_scanned = 0u16;
loop {
// Loop back from the beginning if needed
let _ = next_port.compare_exchange(
END_PORT,
START_PORT,
Ordering::SeqCst,
Ordering::SeqCst,
);
let next = next_port.fetch_add(1, Ordering::SeqCst);
if TcpListener::bind(("0.0.0.0", next)).is_ok() {
return Some(next)
}
ports_scanned += 1;
if ports_scanned == MAX_PORTS {
return None
}
}
}