cli_assert/
command.rs

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