Factor substrate node runner into separate crate (#913)

* factor out node starting thing to separate crate to use in test-runtime and integration-tests

* remove subxt dep on test-runtime build, and clippy tidyup

* remove now-unneeded dep

* Update testing/substrate-runner/Cargo.toml

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>

* Update testing/integration-tests/Cargo.toml

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* remove unneeded port things for backward compat

---------

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
James Wilson
2023-04-17 11:42:17 +01:00
committed by GitHub
parent aec1cc52c1
commit 2f1b67b384
11 changed files with 231 additions and 179 deletions
@@ -22,11 +22,9 @@ pub async fn test_context_with(key: AccountKeyring) -> TestContext {
SUBSTRATE_NODE_PATH.to_string()
});
let proc = TestContext::build(path.as_str())
.with_authority(key)
.spawn::<SubstrateConfig>()
.await;
proc.unwrap()
let mut proc = TestContext::build(path.as_str());
proc.with_authority(key);
proc.spawn::<SubstrateConfig>().await.unwrap()
}
pub type TestContext = TestNodeProcess<SubstrateConfig>;
@@ -3,28 +3,17 @@
// see LICENSE for license details.
use sp_keyring::AccountKeyring;
use std::{
ffi::{OsStr, OsString},
io::{BufRead, BufReader, Read},
process,
};
use std::ffi::{OsStr, OsString};
use substrate_runner::SubstrateNode;
use subxt::{Config, OnlineClient};
/// Spawn a local substrate node for testing subxt.
pub struct TestNodeProcess<R: Config> {
proc: process::Child,
// Keep a handle to the node; once it's dropped the node is killed.
_proc: SubstrateNode,
client: OnlineClient<R>,
}
impl<R> Drop for TestNodeProcess<R>
where
R: Config,
{
fn drop(&mut self) {
let _ = self.kill();
}
}
impl<R> TestNodeProcess<R>
where
R: Config,
@@ -37,17 +26,6 @@ where
TestNodeProcessBuilder::new(program)
}
/// Attempt to kill the running substrate process.
pub fn kill(&mut self) -> Result<(), String> {
tracing::info!("Killing node process {}", self.proc.id());
if let Err(err) = self.proc.kill() {
let err = format!("Error killing node process {}: {}", self.proc.id(), err);
tracing::error!("{}", err);
return Err(err);
}
Ok(())
}
/// Returns the subxt client connected to the running node.
pub fn client(&self) -> OnlineClient<R> {
self.client.clone()
@@ -78,78 +56,31 @@ impl TestNodeProcessBuilder {
}
/// 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>
pub async fn spawn<R>(self) -> Result<TestNodeProcess<R>, String>
where
R: Config,
{
let mut cmd = process::Command::new(&self.node_path);
cmd.env("RUST_LOG", "info")
.arg("--dev")
.arg("--tmp")
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.arg("--port=0")
.arg("--rpc-port=0")
.arg("--ws-port=0");
let mut node_builder = SubstrateNode::builder();
node_builder.binary_path(self.node_path);
if let Some(authority) = self.authority {
let authority = format!("{authority:?}");
let arg = format!("--{}", authority.as_str().to_lowercase());
cmd.arg(arg);
node_builder.arg(authority.as_str().to_lowercase());
}
let mut proc = cmd.spawn().map_err(|e| {
format!(
"Error spawning substrate node '{}': {}",
self.node_path.to_string_lossy(),
e
)
})?;
// Wait for RPC port to be logged (it's logged to stderr):
let stderr = proc.stderr.take().unwrap();
let ws_port = find_substrate_port_from_output(stderr);
let ws_url = format!("ws://127.0.0.1:{ws_port}");
// Spawn the node and retrieve a URL to it:
let proc = node_builder.spawn().map_err(|e| e.to_string())?;
let ws_url = format!("ws://127.0.0.1:{}", proc.ws_port());
// Connect to the node with a subxt client:
let client = OnlineClient::from_url(ws_url.clone()).await;
match client {
Ok(client) => Ok(TestNodeProcess { proc, client }),
Err(err) => {
let err = format!("Failed to connect to node rpc at {ws_url}: {err}");
tracing::error!("{}", err);
proc.kill().map_err(|e| {
format!("Error killing substrate process '{}': {}", proc.id(), e)
})?;
Err(err)
}
Ok(client) => Ok(TestNodeProcess {
_proc: proc,
client,
}),
Err(err) => Err(format!("Failed to connect to node rpc at {ws_url}: {err}")),
}
}
}
// Consume a stderr reader from a spawned substrate command and
// locate the port number that is logged out to it.
fn find_substrate_port_from_output(r: impl Read + Send + 'static) -> u16 {
BufReader::new(r)
.lines()
.find_map(|line| {
let line = line.expect("failed to obtain next line from stdout for port discovery");
// does the line contain our port (we expect this specific output from substrate).
let line_end = line
.rsplit_once("Listening for new connections on 127.0.0.1:")
.or_else(|| line.rsplit_once("Running JSON-RPC WS server: addr=127.0.0.1:"))
.map(|(_, port_str)| port_str)?;
// trim non-numeric chars from the end of the port part of the line.
let port_str = line_end.trim_end_matches(|b: char| !b.is_ascii_digit());
// expect to have a number here (the chars after '127.0.0.1:') and parse them into a u16.
let port_num = port_str
.parse()
.unwrap_or_else(|_| panic!("valid port expected for log line, got '{port_str}'"));
Some(port_num)
})
.expect("We should find a port before the reader ends")
}