1use anyhow::Result;
2use log::debug;
3use std::process::Stdio;
4use tokio::io::AsyncReadExt;
5use tokio::process::Command;
6
7#[cfg(test)]
8#[path = "process_tests.rs"]
9mod tests;
10
11async fn read_stderr(handle: Option<tokio::process::ChildStderr>) -> String {
13 if let Some(stderr) = handle {
14 let mut buf = Vec::new();
15 let mut reader = tokio::io::BufReader::new(stderr);
16 let _ = reader.read_to_end(&mut buf).await;
17 String::from_utf8_lossy(&buf).trim().to_string()
18 } else {
19 String::new()
20 }
21}
22
23pub fn log_stderr_text(stderr: &str) {
25 if !stderr.is_empty() {
26 for line in stderr.lines() {
27 debug!("[STDERR] {}", line);
28 }
29 }
30}
31
32pub fn check_exit_status(
37 status: std::process::ExitStatus,
38 stderr: &str,
39 agent_name: &str,
40) -> Result<()> {
41 debug!("{} process exited with status: {}", agent_name, status);
42 if status.success() {
43 return Ok(());
44 }
45 if stderr.is_empty() {
46 anyhow::bail!("{} command failed with status: {}", agent_name, status);
47 } else {
48 anyhow::bail!("{}", stderr);
49 }
50}
51
52pub fn handle_output(output: &std::process::Output, agent_name: &str) -> Result<()> {
56 let stderr_text = String::from_utf8_lossy(&output.stderr);
57 let stderr_text = stderr_text.trim();
58 log_stderr_text(stderr_text);
59 check_exit_status(output.status, stderr_text, agent_name)
60}
61
62pub async fn run_captured(cmd: &mut Command, agent_name: &str) -> Result<String> {
66 debug!("{}: running with captured stdout/stderr", agent_name);
67 cmd.stdin(Stdio::inherit())
68 .stdout(Stdio::piped())
69 .stderr(Stdio::piped());
70
71 let output = cmd.output().await?;
72 debug!(
73 "{}: captured {} bytes stdout, {} bytes stderr",
74 agent_name,
75 output.stdout.len(),
76 output.stderr.len()
77 );
78 handle_output(&output, agent_name)?;
79 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
80}
81
82pub async fn run_with_captured_stderr(cmd: &mut Command) -> Result<()> {
90 debug!("Running command with captured stderr");
91 cmd.stderr(Stdio::piped());
92
93 let mut child = cmd.spawn()?;
94 let stderr_handle = child.stderr.take();
95 let status = child.wait().await?;
96 let stderr_text = read_stderr(stderr_handle).await;
97
98 log_stderr_text(&stderr_text);
99 check_exit_status(status, &stderr_text, "Command")
100}
101
102pub async fn spawn_with_captured_stderr(cmd: &mut Command) -> Result<tokio::process::Child> {
107 debug!("Spawning command with captured stderr");
108 cmd.stderr(Stdio::piped());
109 let child = cmd.spawn()?;
110 Ok(child)
111}
112
113pub async fn wait_with_stderr(mut child: tokio::process::Child) -> Result<()> {
118 let stderr_handle = child.stderr.take();
119 let status = child.wait().await?;
120 let stderr_text = read_stderr(stderr_handle).await;
121
122 log_stderr_text(&stderr_text);
123 check_exit_status(status, &stderr_text, "Command")
124}