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