Skip to main content

double_o/
exec.rs

1use std::process::Command;
2
3use crate::error::Error;
4
5pub struct CommandOutput {
6    pub stdout: Vec<u8>,
7    pub stderr: Vec<u8>,
8    pub exit_code: i32,
9}
10
11impl CommandOutput {
12    /// Merged output: stdout followed by stderr.
13    pub fn merged(&self) -> Vec<u8> {
14        let mut out = self.stdout.clone();
15        out.extend_from_slice(&self.stderr);
16        out
17    }
18
19    /// Merged output as a lossy UTF-8 string.
20    pub fn merged_lossy(&self) -> String {
21        String::from_utf8_lossy(&self.merged()).into_owned()
22    }
23}
24
25pub fn run(args: &[String]) -> Result<CommandOutput, Error> {
26    let output = Command::new(&args[0]).args(&args[1..]).output()?;
27
28    let exit_code = output.status.code().unwrap_or(128);
29
30    Ok(CommandOutput {
31        stdout: output.stdout,
32        stderr: output.stderr,
33        exit_code,
34    })
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn test_successful_command() {
43        let result = run(&["echo".into(), "hello".into()]).unwrap();
44        assert_eq!(result.exit_code, 0);
45        assert_eq!(String::from_utf8_lossy(&result.stdout), "hello\n");
46    }
47
48    #[test]
49    fn test_failing_command() {
50        let result = run(&["false".into()]).unwrap();
51        assert_ne!(result.exit_code, 0);
52    }
53
54    #[test]
55    fn test_stderr_captured() {
56        let result = run(&["sh".into(), "-c".into(), "echo err >&2".into()]).unwrap();
57        assert_eq!(result.exit_code, 0);
58        assert_eq!(String::from_utf8_lossy(&result.stderr), "err\n");
59    }
60
61    #[test]
62    fn test_exit_code_preserved() {
63        let result = run(&["sh".into(), "-c".into(), "exit 42".into()]).unwrap();
64        assert_eq!(result.exit_code, 42);
65    }
66
67    #[test]
68    fn test_merged_output() {
69        let result = run(&["sh".into(), "-c".into(), "echo out; echo err >&2".into()]).unwrap();
70        let merged = result.merged_lossy();
71        assert!(merged.contains("out"));
72        assert!(merged.contains("err"));
73    }
74}