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_file: impl AsRef<str>, manifest_dir: impl AsRef<str>) -> Self {
39 let manifest_path = Path::new(manifest_dir.as_ref());
40 let caller_path = Path::new(caller_file.as_ref())
41 .parent()
42 .expect("failed to retrieve parent directory for caller file");
43 let current_dir = match manifest_path.rem(caller_path) {
44 None => caller_path.into(),
45 Some(path_buf) => {
46 if path_buf.components().count() == 0 {
47 PathBuf::from(".")
48 } else {
49 path_buf
50 }
51 }
52 };
53 Self {
54 program: program.as_ref().into(),
55 program_args: vec![],
56 current_dir,
57 child: None,
58 stdin: None,
59 stdout: vec![],
60 stderr: vec![],
61 status: ExitStatus::default(),
62 expected_success: None,
63 expected_failure: None,
64 expected_status: None,
65 expected_stdout: None,
66 expected_stderr: None,
67 }
68 }
69
70 pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
71 self.program_args.push(arg.as_ref().into());
72 self
73 }
74
75 pub fn success(mut self) -> Self {
76 self.expected_success = Some(true);
77 self.expected_failure = None;
78 self
79 }
80
81 pub fn failure(mut self) -> Self {
82 self.expected_failure = Some(true);
83 self.expected_success = None;
84 self
85 }
86
87 pub fn code(mut self, code: i32) -> Self {
88 self.expected_status = Some(code);
89 self
90 }
91
92 pub fn stdin(mut self, bytes: impl AsRef<[u8]>) -> Self {
93 self.stdin = Some(bytes.as_ref().to_vec());
94 self
95 }
96
97 pub fn stdout(mut self, bytes: impl AsRef<[u8]>) -> Self {
98 self.expected_stdout = Some(bytes.as_ref().to_vec());
99 self
100 }
101
102 pub fn stderr(mut self, bytes: impl AsRef<[u8]>) -> Self {
103 self.expected_stderr = Some(bytes.as_ref().to_vec());
104 self
105 }
106
107 pub fn spawn(&mut self) {
108 if self.child.is_some() {
109 panic!("command is already spawned");
110 }
111 let mut command = std::process::Command::new(self.program.clone());
112 let mut child = command
113 .args(self.program_args.clone())
114 .current_dir(self.current_dir.clone())
115 .stdin(std::process::Stdio::piped())
116 .stdout(std::process::Stdio::piped())
117 .stderr(std::process::Stdio::piped())
118 .spawn()
119 .expect("failed to spawn requested command");
120 if let Some(bytes) = &self.stdin {
121 let mut stdin = child.stdin.take().expect("failed to obtain the stdin handle");
122 stdin.write_all(bytes).expect("failed to write stdin");
123 }
124 self.child = Some(child);
125 }
126
127 pub fn wait(&mut self) {
128 if let Some(child) = self.child.take() {
129 let output = child
130 .wait_with_output()
131 .expect("failed during waiting for a child process");
132 self.stdout = output.stdout;
133 self.stderr = output.stderr;
134 self.status = output.status;
135 self.assert();
136 } else {
137 panic!("command is not spawned");
138 }
139 }
140
141 pub fn execute(&mut self) {
142 self.spawn();
143 self.wait();
144 }
145
146 pub fn stop(&mut self) {
147 if let Some(child) = &mut self.child {
148 child.kill().expect("failed to force a child process to stop");
149 } else {
150 panic!("command is not spawned");
151 }
152 }
153
154 pub fn get_program(&self) -> &OsStr {
155 &self.program
156 }
157
158 pub fn get_current_dir(&self) -> &Path {
159 &self.current_dir
160 }
161
162 pub fn get_stdout(&'_ self) -> Cow<'_, str> {
163 String::from_utf8_lossy(&self.stdout)
164 }
165
166 pub fn get_stderr(&'_ self) -> Cow<'_, str> {
167 String::from_utf8_lossy(&self.stderr)
168 }
169
170 pub fn get_stdout_raw(&self) -> &[u8] {
171 &self.stdout
172 }
173
174 pub fn get_stderr_raw(&self) -> &[u8] {
175 &self.stderr
176 }
177
178 pub fn get_status(&self) -> ExitStatus {
179 self.status
180 }
181
182 fn assert(&self) {
184 if let Some(true) = self.expected_success {
185 assert!(self.status.success(), "expected success");
186 }
187 if let Some(true) = self.expected_failure {
188 assert!(!self.status.success(), "expected failure");
189 }
190 if let Some(expected) = self.expected_status {
191 let actual = self.status.code().expect("failed to retrieve status code");
192 assert_eq!(
193 expected, actual,
194 "\nexpected status code: {}\n actual status code: {}",
195 expected, actual
196 );
197 }
198 if let Some(expected) = &self.expected_stdout {
199 let actual = self.get_stdout_raw();
200 assert_eq!(
201 expected, actual,
202 "\nexpected stdout: {:?}\n actual stdout: {:?}",
203 expected, actual
204 )
205 }
206 if let Some(expected) = &self.expected_stderr {
207 let actual = self.get_stderr_raw();
208 assert_eq!(
209 expected, actual,
210 "\nexpected stderr: {:?}\n actual stderr: {:?}",
211 expected, actual
212 )
213 }
214 }
215}