1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! Extension trait to check the output of a command

use crate::error::CommandExtError;
use std::process::{Command, Output};

/// Extension trait for [`std::process::Command`] to check the output of a command
pub trait CommandExtCheck {
    /// The error type for the result of checking for an error status
    type Error;

    /// Check the result of a command, returning an error containing the output and
    /// error stream content if the status is not success
    fn check(&mut self) -> Result<Output, Self::Error>;
}

impl CommandExtCheck for Command {
    type Error = CommandExtError;

    /// Check the result of a command, returning an error containing the status, output
    /// and error stream content if the status is not success
    fn check(&mut self) -> Result<Output, Self::Error> {
        self.output().map_err(CommandExtError::from).and_then(|r| {
            r.status
                .success()
                .then_some(r.clone())
                .ok_or_else(|| CommandExtError::Check {
                    status: r.status,
                    stdout: String::from_utf8_lossy(&r.stdout).to_string(),
                    stderr: String::from_utf8_lossy(&r.stderr).to_string(),
                })
        })
    }
}

#[cfg(test)]
mod test {
    use std::process::Command;

    use crate::{CommandExtCheck, CommandExtError};

    #[test]
    #[cfg_attr(miri, ignore)]
    /// Check that a successful command returns a success output
    fn test_success() {
        let output = Command::new("echo").arg("x").check();
        match output {
            Ok(output) => assert_eq!(
                String::from_utf8_lossy(&output.stdout),
                "x\n",
                "Output mismatch"
            ),
            Err(e) => panic!("Unexpected error from command: {}", e),
        };
    }

    #[test]
    #[cfg_attr(miri, ignore)]
    /// Test that a command that doesn't exist returns a wrapped IO error
    fn test_nocmd() {
        let output = Command::new("asdfasdfasdfasdfjkljkljkl").check();

        match output {
            Ok(output) => panic!("Unexpected success from command: {:?}", output),
            Err(e) => assert!(matches!(e, CommandExtError::StdIoError(_))),
        }
    }

    #[test]
    #[cfg_attr(miri, ignore)]
    /// Test that a command which fails by returning a nonzero status code returns a check error
    fn test_failure() {
        let output = Command::new("false").check();

        match output {
            Ok(output) => panic!("Unexpected success from command: {:?}", output),
            Err(e) => assert!(matches!(
                e,
                CommandExtError::Check {
                    status: _,
                    stdout: _,
                    stderr: _
                }
            )),
        }
    }
}