// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see .
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use nix::{
sys::signal::{kill, Signal, Signal::SIGINT},
unistd::Pid,
};
use node_primitives::{Hash, Header};
use regex::Regex;
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
use std::{
io::{BufRead, BufReader, Read},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
process::{self, Child, Command},
time::Duration,
};
use tokio::io::{AsyncBufReadExt, AsyncRead};
/// Similar to [`crate::start_node`] spawns a node, but works in environments where the substrate
/// binary is not accessible with `cargo_bin("substrate-node")`, and allows customising the args
/// passed in.
///
/// Helpful if you need a Substrate dev node running in the background of a project external to
/// `substrate`.
///
/// The downside compared to using [`crate::start_node`] is that this method is blocking rather than
/// returning a [`Child`]. Therefore, you may want to call this method inside a new thread.
///
/// # Example
/// ```ignore
/// // Spawn a dev node.
/// let _ = std::thread::spawn(move || {
/// match common::start_node_inline(vec!["--dev", "--rpc-port=12345"]) {
/// Ok(_) => {}
/// Err(e) => {
/// panic!("Node exited with error: {}", e);
/// }
/// }
/// });
/// ```
pub fn start_node_inline(args: Vec<&str>) -> Result<(), sc_service::error::Error> {
use sc_cli::SubstrateCli;
// Prepend the args with some dummy value because the first arg is skipped.
let cli_call = std::iter::once("node-template").chain(args);
let cli = node_cli::Cli::from_iter(cli_call);
let runner = cli.create_runner(&cli.run).unwrap();
runner.run_node_until_exit(|config| async move { node_cli::service::new_full(config, cli) })
}
/// Starts a new Substrate node in development mode with a temporary chain.
///
/// This function creates a new Substrate node using the `substrate` binary.
/// It configures the node to run in development mode (`--dev`) with a temporary chain (`--tmp`),
/// sets the WebSocket port to 45789 (`--ws-port=45789`).
///
/// # Returns
///
/// A [`Child`] process representing the spawned Substrate node.
///
/// # Panics
///
/// This function will panic if the `substrate` binary is not found or if the node fails to start.
///
/// # Examples
///
/// ```ignore
/// use my_crate::start_node;
///
/// let child = start_node();
/// // Interact with the Substrate node using the WebSocket port 45789.
/// // When done, the node will be killed when the `child` is dropped.
/// ```
///
/// [`Child`]: std::process::Child
pub fn start_node() -> Child {
Command::new(cargo_bin("substrate-node"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(&["--dev", "--tmp", "--rpc-port=45789", "--no-hardware-benchmarks"])
.spawn()
.unwrap()
}
/// Builds the Substrate project using the provided arguments.
///
/// This function reads the CARGO_MANIFEST_DIR environment variable to find the root workspace
/// directory. It then runs the `cargo b` command in the root directory with the specified
/// arguments.
///
/// This can be useful for building the Substrate binary with a desired set of features prior
/// to using the binary in a CLI test.
///
/// # Arguments
///
/// * `args: &[&str]` - A slice of string references representing the arguments to pass to the
/// `cargo b` command.
///
/// # Panics
///
/// This function will panic if:
///
/// * The CARGO_MANIFEST_DIR environment variable is not set.
/// * The root workspace directory cannot be determined.
/// * The 'cargo b' command fails to execute.
/// * The 'cargo b' command returns a non-successful status.
///
/// # Examples
///
/// ```ignore
/// build_substrate(&["--features=try-runtime"]);
/// ```
pub fn build_substrate(args: &[&str]) {
let is_release_build = !cfg!(build_type = "debug");
// Get the root workspace directory from the CARGO_MANIFEST_DIR environment variable
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("-p=node-cli");
if is_release_build {
cmd.arg("--release");
}
let output = cmd
.args(args)
.output()
.expect(format!("Failed to execute 'cargo b' with args {:?}'", args).as_str());
if !output.status.success() {
panic!(
"Failed to execute 'cargo b' with args {:?}': \n{}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
}
/// Takes a readable tokio stream (e.g. from a child process `ChildStderr` or `ChildStdout`) and
/// a `Regex` pattern, and checks each line against the given pattern as it is produced.
/// The function returns OK(()) as soon as a line matching the pattern is found, or an Err if
/// the stream ends without any lines matching the pattern.
///
/// # Arguments
///
/// * `child_stream` - An async tokio stream, e.g. from a child process `ChildStderr` or
/// `ChildStdout`.
/// * `re` - A `Regex` pattern to search for in the stream.
///
/// # Returns
///
/// * `Ok(())` if a line matching the pattern is found.
/// * `Err(String)` if the stream ends without any lines matching the pattern.
///
/// # Examples
///
/// ```ignore
/// use regex::Regex;
/// use tokio::process::Command;
/// use tokio::io::AsyncRead;
///
/// # async fn run() {
/// let child = Command::new("some-command").stderr(std::process::Stdio::piped()).spawn().unwrap();
/// let stderr = child.stderr.unwrap();
/// let re = Regex::new("error:").unwrap();
///
/// match wait_for_pattern_match_in_stream(stderr, re).await {
/// Ok(()) => println!("Error found in stderr"),
/// Err(e) => println!("Error: {}", e),
/// }
/// # }
/// ```
pub async fn wait_for_stream_pattern_match(stream: R, re: Regex) -> Result<(), String>
where
R: AsyncRead + Unpin,
{
let mut stdio_reader = tokio::io::BufReader::new(stream).lines();
while let Ok(Some(line)) = stdio_reader.next_line().await {
match re.find(line.as_str()) {
Some(_) => return Ok(()),
None => (),
}
}
Err(String::from("Stream closed without any lines matching the regex."))
}
/// Run the given `future` and panic if the `timeout` is hit.
pub async fn run_with_timeout(timeout: Duration, future: impl futures::Future