git_mob_tool/
helpers.rs

1use std::{error::Error, process::Command};
2
3pub struct CmdOutput {
4    pub stdout: Vec<u8>,
5    pub stderr: Vec<u8>,
6    pub status_code: Option<i32>,
7}
8
9#[cfg(test)]
10use mockall::{automock, predicate::*};
11#[cfg_attr(test, automock)]
12pub trait CommandRunner {
13    #[allow(clippy::needless_lifetimes)] // explicit lifetime is needed for automock
14    fn execute<'a>(&self, program: &str, args: &[&'a str]) -> Result<CmdOutput, Box<dyn Error>>;
15}
16
17pub struct StdCommandRunner;
18
19impl CommandRunner for StdCommandRunner {
20    fn execute(&self, program: &str, args: &[&str]) -> Result<CmdOutput, Box<dyn Error>> {
21        let output = Command::new(program).args(args).output()?;
22
23        Ok(CmdOutput {
24            stdout: output.stdout,
25            stderr: output.stderr,
26            status_code: output.status.code(),
27        })
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn test_execute_success() -> Result<(), Box<dyn Error>> {
37        let result = StdCommandRunner.execute("git", &["--version"])?;
38
39        assert_eq!(result.status_code, Some(0));
40        assert!(result.stdout.starts_with(b"git version "));
41        assert!(result.stderr.is_empty());
42
43        Ok(())
44    }
45
46    #[test]
47    fn test_execute_failure() -> Result<(), Box<dyn Error>> {
48        let result = StdCommandRunner.execute("git", &["--invalid_option"])?;
49
50        assert!(result.status_code.is_some_and(|x| x != 0));
51        assert!(result.stdout.is_empty());
52        assert!(result
53            .stderr
54            .starts_with(b"unknown option: --invalid_option"));
55
56        Ok(())
57    }
58
59    #[test]
60    fn test_execute_error() -> Result<(), Box<dyn Error>> {
61        let result = StdCommandRunner.execute("non_existent_program", &[]);
62
63        assert!(result.is_err());
64
65        Ok(())
66    }
67}