use super::*; pub(crate) trait ToArgs { fn to_args(&self) -> Vec; } impl ToArgs for String { fn to_args(&self) -> Vec { self.as_str().to_args() } } impl ToArgs for &str { fn to_args(&self) -> Vec { self.split_whitespace().map(str::to_string).collect() } } impl ToArgs for [&str; N] { fn to_args(&self) -> Vec { self.iter().cloned().map(str::to_string).collect() } } impl ToArgs for Vec { fn to_args(&self) -> Vec { self.clone() } } pub(crate) struct Spawn { pub(crate) child: Child, expected_exit_code: i32, expected_stderr: Expected, expected_stdout: Expected, tempdir: Arc, } impl Spawn { #[track_caller] fn run(self) -> (TempDir, String) { let output = self.child.wait_with_output().unwrap(); let stdout = str::from_utf8(&output.stdout).unwrap(); let stderr = str::from_utf8(&output.stderr).unwrap(); if output.status.code() != Some(self.expected_exit_code) { panic!( "Test failed: {}\nstdout:\n{}\nstderr:\n{}", output.status, stdout, stderr ); } self.expected_stderr.assert_match(stderr); self.expected_stdout.assert_match(stdout); (Arc::try_unwrap(self.tempdir).unwrap(), stdout.into()) } } pub(crate) struct CommandBuilder { args: Vec, core_cookie_file: Option, core_url: Option, env: BTreeMap, expected_exit_code: i32, expected_stderr: Expected, expected_stdout: Expected, integration_test: bool, ord_url: Option, stderr: bool, stdin: Vec, stdout: bool, tempdir: Arc, } impl CommandBuilder { pub(crate) fn new(args: impl ToArgs) -> Self { Self { args: args.to_args(), core_cookie_file: None, core_url: None, env: BTreeMap::new(), expected_exit_code: 0, expected_stderr: Expected::String(String::new()), expected_stdout: Expected::String(String::new()), integration_test: true, ord_url: None, stderr: true, stdin: Vec::new(), stdout: true, tempdir: Arc::new(TempDir::new().unwrap()), } } pub(crate) fn env(mut self, key: &str, value: impl AsRef) -> Self { self.env.insert(key.into(), value.as_ref().into()); self } pub(crate) fn integration_test(self, integration_test: bool) -> Self { Self { integration_test, ..self } } pub(crate) fn write(self, path: impl AsRef, contents: impl AsRef<[u8]>) -> Self { fs::write(self.tempdir.path().join(path), contents).unwrap(); self } pub(crate) fn core(self, core: &mockcore::Handle) -> Self { Self { core_url: Some(core.url()), core_cookie_file: Some(core.cookie_file()), ..self } } pub(crate) fn bitomc(self, bitomc: &TestServer) -> Self { Self { ord_url: Some(bitomc.url()), ..self } } #[allow(unused)] pub(crate) fn stderr(self, stderr: bool) -> Self { Self { stderr, ..self } } pub(crate) fn stdin(self, stdin: Vec) -> Self { Self { stdin, ..self } } #[allow(unused)] pub(crate) fn stdout(self, stdout: bool) -> Self { Self { stdout, ..self } } pub(crate) fn stdout_regex(self, expected_stdout: impl AsRef) -> Self { Self { expected_stdout: Expected::regex(expected_stdout.as_ref()), ..self } } pub(crate) fn expected_stderr(self, expected_stderr: impl AsRef) -> Self { Self { expected_stderr: Expected::String(expected_stderr.as_ref().to_owned()), ..self } } pub(crate) fn stderr_regex(self, expected_stderr: impl AsRef) -> Self { Self { expected_stderr: Expected::regex(expected_stderr.as_ref()), ..self } } pub(crate) fn expected_exit_code(self, expected_exit_code: i32) -> Self { Self { expected_exit_code, ..self } } pub(crate) fn temp_dir(self, tempdir: Arc) -> Self { Self { tempdir, ..self } } pub(crate) fn command(&self) -> Command { let mut command = Command::new(executable_path("bitomc")); if let Some(rpc_server_url) = &self.core_url { command.args([ "--bitcoin-rpc-url", rpc_server_url, "--cookie-file", self.core_cookie_file.as_ref().unwrap().to_str().unwrap(), ]); } let mut args = Vec::new(); for arg in self.args.iter() { args.push(arg.clone()); if arg == "wallet" { if let Some(ord_server_url) = &self.ord_url { args.push("--server-url".to_string()); args.push(ord_server_url.to_string()); } } } for (key, value) in &self.env { command.env(key, value); } if self.integration_test { command.env("BITOMC_INTEGRATION_TEST", "1"); } command .stdin(Stdio::piped()) .stdout(if self.stdout { Stdio::piped() } else { Stdio::inherit() }) .stderr(if self.stderr { Stdio::piped() } else { Stdio::inherit() }) .current_dir(&*self.tempdir) .arg("--datadir") .arg(self.tempdir.path()) .args(&args); command } #[track_caller] pub(crate) fn spawn(self) -> Spawn { let mut command = self.command(); let child = command.spawn().unwrap(); child .stdin .as_ref() .unwrap() .write_all(&self.stdin) .unwrap(); Spawn { child, expected_exit_code: self.expected_exit_code, expected_stderr: self.expected_stderr, expected_stdout: self.expected_stdout, tempdir: self.tempdir, } } #[track_caller] fn run(self) -> (TempDir, String) { self.spawn().run() } #[track_caller] pub(crate) fn run_and_extract_stdout(self) -> String { self.run().1 } #[track_caller] pub(crate) fn run_and_deserialize_output(self) -> T { let stdout = self.stdout_regex(".*").run_and_extract_stdout(); match serde_json::from_str(&stdout) { Ok(output) => output, Err(err) => panic!("Failed to deserialize JSON: {err}\n{stdout}"), } } }