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 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 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}