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 program: OsString,
11 program_args: Vec<OsString>,
13 current_dir: PathBuf,
15 child: Option<Child>,
17 stdin: Option<Vec<u8>>,
19 stdout: Vec<u8>,
21 stderr: Vec<u8>,
23 status: ExitStatus,
25 expected_success: Option<bool>,
27 expected_failure: Option<bool>,
29 expected_status: Option<i32>,
31 expected_stdout: Option<Vec<u8>>,
33 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 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}