cli_assert/
command.rs

1use crate::utils::PathExt;
2use std::borrow::Cow;
3use std::ffi::{OsStr, OsString};
4use std::path::{Path, PathBuf};
5use std::process::{Child, ExitStatus};
6
7pub struct Command {
8  /// Path to the program to be executed.
9  program: OsString,
10  /// Arguments passed to spawned program.
11  program_args: Vec<OsString>,
12  /// Path to the directory this command is executed in.
13  current_dir: PathBuf,
14  /// Child process.
15  child: Option<Child>,
16  /// The raw content of the stdout of the child process.
17  stdout: Vec<u8>,
18  /// The raw content of the stderr of the child process.
19  stderr: Vec<u8>,
20  /// Execution status of the child process.
21  status: ExitStatus,
22  /// Flag indicating if the command is expected to complete successfully.
23  expected_success: Option<bool>,
24  /// Flag indicating if the command is expected to fail.
25  expected_failure: Option<bool>,
26  /// Expected status code.
27  expected_status: Option<i32>,
28  /// Expected stdout.
29  expected_stdout: Option<Vec<u8>>,
30  /// Expected stderr.
31  expected_stderr: Option<Vec<u8>>,
32}
33
34impl Command {
35  pub fn new(program: impl AsRef<OsStr>, caller: &str, dir: &str) -> Self {
36    let dir = Path::new(dir);
37    let caller = Path::new(caller).parent().expect("failed to retrieve parent directory");
38    let current_dir = match dir.rem(caller) {
39      None => caller.into(),
40      Some(path_buf) => {
41        if path_buf.components().count() == 0 {
42          PathBuf::from(".")
43        } else {
44          path_buf
45        }
46      }
47    };
48    Self {
49      program: program.as_ref().into(),
50      program_args: vec![],
51      current_dir,
52      child: None,
53      stdout: vec![],
54      stderr: vec![],
55      status: ExitStatus::default(),
56      expected_success: None,
57      expected_failure: None,
58      expected_status: None,
59      expected_stdout: None,
60      expected_stderr: None,
61    }
62  }
63
64  pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
65    self.program_args.push(arg.as_ref().into());
66    self
67  }
68
69  pub fn success(mut self) -> Self {
70    self.expected_success = Some(true);
71    self.expected_failure = None;
72    self
73  }
74
75  pub fn failure(mut self) -> Self {
76    self.expected_failure = Some(true);
77    self.expected_success = None;
78    self
79  }
80
81  pub fn code(mut self, code: i32) -> Self {
82    self.expected_status = Some(code);
83    self
84  }
85
86  pub fn stdout(mut self, bytes: impl AsRef<[u8]>) -> Self {
87    self.expected_stdout = Some(bytes.as_ref().to_vec());
88    self
89  }
90
91  pub fn stderr(mut self, bytes: impl AsRef<[u8]>) -> Self {
92    self.expected_stderr = Some(bytes.as_ref().to_vec());
93    self
94  }
95
96  pub fn spawn(&mut self) {
97    let mut command = std::process::Command::new(self.program.clone());
98    self.child = Some(
99      command
100        .args(self.program_args.clone())
101        .current_dir(self.current_dir.clone())
102        .stdout(std::process::Stdio::piped())
103        .stderr(std::process::Stdio::piped())
104        .spawn()
105        .expect("failed to spawn requested command"),
106    );
107  }
108
109  pub fn wait(&mut self) {
110    if let Some(child) = self.child.take() {
111      let output = child
112        .wait_with_output()
113        .expect("failed during waiting for a child process");
114      self.stdout = output.stdout;
115      self.stderr = output.stderr;
116      self.status = output.status;
117      self.assert();
118    }
119  }
120
121  pub fn execute(&mut self) {
122    self.spawn();
123    self.wait();
124  }
125
126  pub fn stop(&mut self) {
127    if let Some(child) = &mut self.child {
128      child.kill().expect("failed to force a child process to stop");
129    }
130  }
131
132  pub fn get_program(&self) -> &OsStr {
133    &self.program
134  }
135
136  pub fn get_current_dir(&self) -> &Path {
137    &self.current_dir
138  }
139
140  pub fn get_stdout(&'_ self) -> Cow<'_, str> {
141    String::from_utf8_lossy(&self.stdout)
142  }
143
144  pub fn get_stderr(&'_ self) -> Cow<'_, str> {
145    String::from_utf8_lossy(&self.stderr)
146  }
147
148  pub fn get_stdout_raw(&self) -> &[u8] {
149    &self.stdout
150  }
151
152  pub fn get_stderr_raw(&self) -> &[u8] {
153    &self.stderr
154  }
155
156  pub fn get_status(&self) -> ExitStatus {
157    self.status
158  }
159
160  /// Checks all assertions.
161  fn assert(&self) {
162    if let Some(true) = self.expected_success {
163      assert!(self.status.success(), "expected success");
164    }
165    if let Some(true) = self.expected_failure {
166      assert!(!self.status.success(), "expected failure");
167    }
168    if let Some(expected) = self.expected_status {
169      let actual = self.status.code().expect("failed to retrieve status code");
170      assert_eq!(
171        expected, actual,
172        "\nexpected status code: {}\n  actual status code: {}",
173        expected, actual
174      );
175    }
176    if let Some(expected) = self.expected_stdout.as_ref() {
177      let actual = self.get_stdout_raw();
178      assert_eq!(
179        expected, actual,
180        "\nexpected stdout: {:?}\n  actual stdout: {:?}",
181        expected, actual
182      )
183    }
184    if let Some(expected) = self.expected_stderr.as_ref() {
185      let actual = self.get_stderr_raw();
186      assert_eq!(
187        expected, actual,
188        "\nexpected stderr: {:?}\n  actual stderr: {:?}",
189        expected, actual
190      )
191    }
192  }
193}