-
Notifications
You must be signed in to change notification settings - Fork 248
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
Factor substrate node runner into separate crate #913
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
07ad2e5
factor out node starting thing to separate crate to use in test-runti…
jsdw 56588de
remove subxt dep on test-runtime build, and clippy tidyup
jsdw 6767908
Merge branch 'master' into jsdw-substrate-runner
jsdw c112941
remove now-unneeded dep
jsdw 423318e
Merge branch 'master' into jsdw-substrate-runner
jsdw a08069a
Update testing/substrate-runner/Cargo.toml
jsdw ce52146
Update testing/integration-tests/Cargo.toml
jsdw 06933b1
remove unneeded port things for backward compat
jsdw 83e35bb
Merge branch 'master' into jsdw-substrate-runner
jsdw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "substrate-runner" | ||
version = "0.28.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# substrate-runner | ||
|
||
A small crate whose sole purpose is starting up a substrate node on some free port and handing back a handle to it with the port that it started on. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2019-2023 Parity Technologies (UK) Ltd. | ||
// This file is dual-licensed as Apache-2.0 or GPL-3.0. | ||
// see LICENSE for license details. | ||
|
||
#[derive(Debug)] | ||
pub enum Error { | ||
Io(std::io::Error), | ||
CouldNotExtractPort, | ||
} | ||
|
||
impl std::fmt::Display for Error { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Error::Io(err) => write!(f, "IO error: {err}"), | ||
Error::CouldNotExtractPort => write!( | ||
f, | ||
"could not extract port from running substrate node's stdout" | ||
), | ||
} | ||
} | ||
} | ||
|
||
impl std::error::Error for Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Copyright 2019-2023 Parity Technologies (UK) Ltd. | ||
// This file is dual-licensed as Apache-2.0 or GPL-3.0. | ||
// see LICENSE for license details. | ||
|
||
mod error; | ||
|
||
use std::borrow::Cow; | ||
use std::collections::HashMap; | ||
use std::ffi::OsString; | ||
use std::io::{BufRead, BufReader, Read}; | ||
use std::process::{self, Command}; | ||
|
||
pub use error::Error; | ||
|
||
type CowStr = Cow<'static, str>; | ||
|
||
pub struct SubstrateNodeBuilder { | ||
binary_path: OsString, | ||
custom_flags: HashMap<CowStr, Option<CowStr>>, | ||
} | ||
|
||
impl Default for SubstrateNodeBuilder { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
impl SubstrateNodeBuilder { | ||
/// Configure a new Substrate node. | ||
pub fn new() -> Self { | ||
SubstrateNodeBuilder { | ||
binary_path: "substrate".into(), | ||
custom_flags: Default::default(), | ||
} | ||
} | ||
|
||
/// Set the path to the `substrate` binary; defaults to "substrate". | ||
pub fn binary_path(&mut self, path: impl Into<OsString>) -> &mut Self { | ||
self.binary_path = path.into(); | ||
self | ||
} | ||
|
||
/// Provide a boolean argument like `--alice` | ||
pub fn arg(&mut self, s: impl Into<CowStr>) -> &mut Self { | ||
self.custom_flags.insert(s.into(), None); | ||
self | ||
} | ||
|
||
/// Provide an argument with a value. | ||
pub fn arg_val(&mut self, key: impl Into<CowStr>, val: impl Into<CowStr>) -> &mut Self { | ||
self.custom_flags.insert(key.into(), Some(val.into())); | ||
self | ||
} | ||
|
||
/// Spawn the node, handing back an object which, when dropped, will stop it. | ||
pub fn spawn(self) -> Result<SubstrateNode, Error> { | ||
let mut cmd = Command::new(self.binary_path); | ||
|
||
cmd.env("RUST_LOG", "info") | ||
.stdout(process::Stdio::piped()) | ||
.stderr(process::Stdio::piped()) | ||
.arg("--dev") | ||
.arg("--port=0"); | ||
|
||
for (key, val) in self.custom_flags { | ||
let arg = match val { | ||
Some(val) => format!("--{key}={val}"), | ||
None => format!("--{key}"), | ||
}; | ||
cmd.arg(arg); | ||
} | ||
|
||
let mut proc = cmd.spawn().map_err(Error::Io)?; | ||
|
||
// Wait for RPC port to be logged (it's logged to stderr). | ||
let stderr = proc.stderr.take().unwrap(); | ||
let ws_port = | ||
try_find_substrate_port_from_output(stderr).ok_or(Error::CouldNotExtractPort)?; | ||
|
||
Ok(SubstrateNode { proc, ws_port }) | ||
} | ||
} | ||
|
||
pub struct SubstrateNode { | ||
proc: process::Child, | ||
ws_port: u16, | ||
} | ||
|
||
impl SubstrateNode { | ||
/// Configure and spawn a new [`SubstrateNode`]. | ||
pub fn builder() -> SubstrateNodeBuilder { | ||
SubstrateNodeBuilder::new() | ||
} | ||
|
||
/// Return the ID of the running process. | ||
pub fn id(&self) -> u32 { | ||
self.proc.id() | ||
} | ||
|
||
/// Return the port that WS connections are accepted on. | ||
pub fn ws_port(&self) -> u16 { | ||
self.ws_port | ||
} | ||
|
||
/// Kill the process. | ||
pub fn kill(&mut self) -> std::io::Result<()> { | ||
self.proc.kill() | ||
} | ||
} | ||
|
||
impl Drop for SubstrateNode { | ||
fn drop(&mut self) { | ||
let _ = self.kill(); | ||
} | ||
} | ||
|
||
// Consume a stderr reader from a spawned substrate command and | ||
// locate the port number that is logged out to it. | ||
fn try_find_substrate_port_from_output(r: impl Read + Send + 'static) -> Option<u16> { | ||
BufReader::new(r).lines().take(50).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 | ||
// oldest message: | ||
.rsplit_once("Listening for new connections on 127.0.0.1:") | ||
// slightly newer message: | ||
.or_else(|| line.rsplit_once("Running JSON-RPC WS server: addr=127.0.0.1:")) | ||
// newest message (jsonrpsee merging http and ws servers): | ||
.or_else(|| line.rsplit_once("Running JSON-RPC 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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
maybe increase the number of a lines a bit to future proof?
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.
I guess I wanted it to fail reasonably quickly if for some reason it wasn't picking up the port; I started at 100 but I think I counted right now that it's something like 25 lines to get the port, so 50 was my "safe" number; maybe 100 to compromise a bit? It should never take that long for the port to be logged I'd hope :)