1use std::ffi::OsString;
2use std::io::ErrorKind;
3use std::os::unix::ffi::OsStringExt;
4use std::pin::Pin;
5use std::process::Stdio;
6use std::sync::Arc;
7use std::{env::set_current_dir, path::Path};
8
9use anyhow::bail;
10use fs_extra::dir::CopyOptions;
11use tempdir::TempDir;
14use tokio::io::{AsyncRead, AsyncReadExt};
15use tokio::process::Command;
16use tokio::sync::Mutex;
17
18pub fn temp_dir_from_template(source_dir: &Path) -> Result<TempDir, Box<dyn std::error::Error>> {
20 let temp_dir = TempDir::new("test")?;
21 let options = CopyOptions::new().content_only(true); fs_extra::dir::copy(source_dir, temp_dir.path(), &options)?;
23 set_current_dir(temp_dir.path())?;
24 Ok(temp_dir)
25}
26
27pub struct TemporaryChild {
29 }
31
32pub struct Capture {
33 pub stdout: Option<Arc<Mutex<String>>>,
34 pub stderr: Option<Arc<Mutex<String>>>,
35}
36
37impl TemporaryChild {
38 pub async fn spawn(cmd: &mut Command, capture: Capture) -> std::io::Result<Self> {
40 if capture.stdout.is_some() {
41 cmd.stdout(Stdio::piped());
42 }
43
44 if capture.stderr.is_some() {
45 cmd.stderr(Stdio::piped());
46 }
47
48 let mut child = cmd.kill_on_drop(true).spawn()?;
56
57 if let Some(capture_stdout) = capture.stdout {
60 let stdout = child.stdout.take().unwrap();
61 spawn_dump_to_string(Box::pin(stdout), capture_stdout).await;
62 }
63
64 if let Some(capture_stderr) = capture.stderr {
65 let stderr = child.stderr.take().unwrap();
66 spawn_dump_to_string(Box::pin(stderr), capture_stderr).await;
67 }
68
69 Ok(TemporaryChild { })
70 }
71}
72
73async fn spawn_dump_to_string(
74 mut stream: Pin<Box<dyn AsyncRead + Send + Sync>>, string: Arc<Mutex<String>>,
76) {
77 tokio::spawn(async move {
78 let mut buf = [0; 4096];
79 loop {
80 let r = stream.read(&mut buf).await;
81 match r {
82 Err(err) => {
83 if err.kind() == ErrorKind::UnexpectedEof {
84 return; } else {
86 panic!("Error in reading child output: {}", err);
87 }
88 }
89 Ok(r) => {
90 let s = OsString::from_vec(Vec::from(&buf[..r]));
91 string
92 .lock()
93 .await
94 .push_str(s.to_str().expect("Wrong text encoding in child output"));
95 }
96 }
97 }
98 });
99}
100
101pub async fn run_successful_command(cmd: &mut Command) -> anyhow::Result<()> {
124 let status = cmd.status().await?;
125 if !status.success() {
126 match status.code() {
127 Some(code) => bail!("Command failed with exit code: {}.", code),
128 None => bail!("Process terminated by a signal."),
129 }
130 }
131 Ok(())
132}
133
134pub async fn run_failed_command(cmd: &mut Command) -> anyhow::Result<()> {
135 let status = cmd.status().await?;
136 if status.success() {
137 bail!("Command succeeded though should have failed.");
138 }
139 Ok(())
140}