Skip to main content

cli_assert/
command.rs

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