use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
use anyhow::{Context, Result, bail};
use serde_json::Value;
mod docker;
mod native;
pub use docker::Docker;
pub use native::Native;
pub const SEQUENCER_FEATURES: &str = "standalone,prove";
pub const LEZ_REPO: &str = "https://github.com/logos-blockchain/logos-execution-zone.git";
const SEQUENCER_LOG: &str = "info,kameo=warn";
pub enum WalletArg {
Text(String),
File(PathBuf),
}
impl From<&str> for WalletArg {
fn from(text: &str) -> Self {
Self::Text(text.to_owned())
}
}
impl From<String> for WalletArg {
fn from(text: String) -> Self {
Self::Text(text)
}
}
pub trait Runtime {
fn describe(&self) -> String;
fn prepare(&self, rebuild: bool) -> Result<()>;
fn upstream_config(&self) -> Result<Value>;
fn chain_home(&self) -> String;
fn wallet_sequencer_url(&self) -> String;
fn start(&self) -> Result<()>;
fn stop(&self) -> Result<()>;
fn running(&self) -> bool;
fn wipe_chain(&self) -> Result<()>;
fn wallet(&self, args: Vec<WalletArg>) -> Result<String>;
fn logs(&self, follow: bool) -> Result<()>;
}
pub(crate) fn stdout_of(cmd: &mut Command) -> Result<String> {
let output = run(cmd)?;
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
pub(crate) fn all_output_of(cmd: &mut Command) -> Result<String> {
run(cmd).map(|output| combined(&output))
}
fn run(cmd: &mut Command) -> Result<Output> {
let output = cmd
.stdin(Stdio::null())
.output()
.with_context(|| format!("could not run {:?}", cmd.get_program()))?;
if !output.status.success() {
bail!(
"{:?} failed ({}):\n{}",
cmd.get_program(),
output.status,
combined(&output).trim_end()
);
}
Ok(output)
}
pub(crate) fn run_attached(cmd: &mut Command) -> Result<()> {
let status = cmd
.status()
.with_context(|| format!("could not run {:?}", cmd.get_program()))?;
if !status.success() {
bail!("{:?} failed ({status})", cmd.get_program());
}
Ok(())
}
fn combined(output: &Output) -> String {
let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&output.stderr));
text
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keeps_stdout_apart_from_stderr() {
let out = stdout_of(Command::new("sh").args(["-c", "echo out; echo warn >&2"])).unwrap();
assert_eq!(out, "out\n");
let all = all_output_of(Command::new("sh").args(["-c", "echo out; echo err >&2"])).unwrap();
assert_eq!(all, "out\nerr\n");
}
#[test]
fn a_failure_carries_both_streams() {
let err = stdout_of(Command::new("sh").args(["-c", "echo out; echo err >&2; exit 3"]))
.unwrap_err()
.to_string();
assert!(err.contains("out") && err.contains("err"), "{err}");
}
#[test]
fn the_image_and_native_builds_agree() {
let dockerfile = docker::DOCKERFILE;
assert!(dockerfile.contains(LEZ_REPO));
assert!(dockerfile.contains(&format!("FEATURES={SEQUENCER_FEATURES}")));
}
}