run_make_support/
command.rs
1use std::ffi::OsStr;
2use std::io::Write;
3use std::path::Path;
4use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
5use std::{ffi, panic};
6
7use build_helper::drop_bomb::DropBomb;
8
9use crate::util::handle_failed_output;
10use crate::{
11 assert_contains, assert_contains_regex, assert_equals, assert_not_contains,
12 assert_not_contains_regex,
13};
14
15#[derive(Debug)]
36pub struct Command {
37 cmd: StdCommand,
38 stdin_buf: Option<Box<[u8]>>,
40
41 stdin: Option<Stdio>,
43 stdout: Option<Stdio>,
44 stderr: Option<Stdio>,
45
46 drop_bomb: DropBomb,
48 already_executed: bool,
49}
50
51impl Command {
52 #[track_caller]
53 pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
54 let program = program.as_ref();
55 Self {
56 cmd: StdCommand::new(program),
57 stdin_buf: None,
58 drop_bomb: DropBomb::arm(program),
59 stdin: None,
60 stdout: None,
61 stderr: None,
62 already_executed: false,
63 }
64 }
65
66 pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
68 self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
69 self
70 }
71
72 pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
76 self.stdin = Some(cfg.into());
77 self
78 }
79
80 pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
84 self.stdout = Some(cfg.into());
85 self
86 }
87
88 pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
92 self.stderr = Some(cfg.into());
93 self
94 }
95
96 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
98 where
99 K: AsRef<ffi::OsStr>,
100 V: AsRef<ffi::OsStr>,
101 {
102 self.cmd.env(key, value);
103 self
104 }
105
106 pub fn env_remove<K>(&mut self, key: K) -> &mut Self
108 where
109 K: AsRef<ffi::OsStr>,
110 {
111 self.cmd.env_remove(key);
112 self
113 }
114
115 pub fn arg<S>(&mut self, arg: S) -> &mut Self
119 where
120 S: AsRef<ffi::OsStr>,
121 {
122 self.cmd.arg(arg);
123 self
124 }
125
126 pub fn args<S, V>(&mut self, args: V) -> &mut Self
130 where
131 S: AsRef<ffi::OsStr>,
132 V: AsRef<[S]>,
133 {
134 self.cmd.args(args.as_ref());
135 self
136 }
137
138 pub fn inspect<I>(&mut self, inspector: I) -> &mut Self
141 where
142 I: FnOnce(&StdCommand),
143 {
144 inspector(&self.cmd);
145 self
146 }
147
148 pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
150 self.cmd.current_dir(path);
151 self
152 }
153
154 #[cfg(unix)]
163 pub fn set_aux_fd<F: Into<std::os::fd::OwnedFd>>(
164 &mut self,
165 new_fd: std::os::fd::RawFd,
166 fd: F,
167 ) -> &mut Self {
168 use std::mem;
169 use std::os::fd::AsRawFd;
172 use std::os::unix::process::CommandExt;
173
174 let cvt = |x| if x == -1 { Err(std::io::Error::last_os_error()) } else { Ok(()) };
175
176 let fd = mem::ManuallyDrop::new(fd.into());
178 let fd = fd.as_raw_fd();
179
180 if fd == new_fd {
181 let fd_flags = {
183 let ret = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
184 if ret < 0 {
185 panic!("failed to read fd flags: {}", std::io::Error::last_os_error());
186 }
187 ret
188 };
189 let fd_flags = fd_flags & !libc::FD_CLOEXEC;
191
192 cvt(unsafe { libc::fcntl(fd, libc::F_SETFD, fd_flags as libc::c_int) })
194 .expect("disabling CLOEXEC failed");
195 }
196 let pre_exec = move || {
197 if fd.as_raw_fd() != new_fd {
198 cvt(unsafe { libc::dup2(fd, new_fd) })?;
201 }
202 Ok(())
203 };
204 unsafe { self.cmd.pre_exec(pre_exec) };
206 self
207 }
208
209 #[track_caller]
213 pub fn run(&mut self) -> CompletedProcess {
214 let output = self.command_output();
215 if !output.status().success() {
216 handle_failed_output(&self, output, panic::Location::caller().line());
217 }
218 output
219 }
220
221 #[track_caller]
225 pub fn run_fail(&mut self) -> CompletedProcess {
226 let output = self.command_output();
227 if output.status().success() {
228 handle_failed_output(&self, output, panic::Location::caller().line());
229 }
230 output
231 }
232
233 #[track_caller]
238 pub fn run_unchecked(&mut self) -> CompletedProcess {
239 self.command_output()
240 }
241
242 #[track_caller]
243 fn command_output(&mut self) -> CompletedProcess {
244 if self.already_executed {
245 panic!("command was already executed");
246 } else {
247 self.already_executed = true;
248 }
249
250 self.drop_bomb.defuse();
251 self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
253 self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
254 self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
255
256 let output = if let Some(input) = &self.stdin_buf {
257 let mut child = self.cmd.spawn().unwrap();
258
259 {
260 let mut stdin = child.stdin.take().unwrap();
261 stdin.write_all(input.as_ref()).unwrap();
262 }
263
264 child.wait_with_output().expect("failed to get output of finished process")
265 } else {
266 self.cmd.output().expect("failed to get output of finished process")
267 };
268 output.into()
269 }
270}
271
272pub struct CompletedProcess {
276 output: Output,
277}
278
279impl CompletedProcess {
280 #[must_use]
281 #[track_caller]
282 pub fn stdout(&self) -> Vec<u8> {
283 self.output.stdout.clone()
284 }
285
286 #[must_use]
287 #[track_caller]
288 pub fn stdout_utf8(&self) -> String {
289 String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
290 }
291
292 #[must_use]
293 #[track_caller]
294 pub fn invalid_stdout_utf8(&self) -> String {
295 String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
296 }
297
298 #[must_use]
299 #[track_caller]
300 pub fn stderr(&self) -> Vec<u8> {
301 self.output.stderr.clone()
302 }
303
304 #[must_use]
305 #[track_caller]
306 pub fn stderr_utf8(&self) -> String {
307 String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
308 }
309
310 #[must_use]
311 #[track_caller]
312 pub fn invalid_stderr_utf8(&self) -> String {
313 String::from_utf8_lossy(&self.output.stderr.clone()).to_string()
314 }
315
316 #[must_use]
317 pub fn status(&self) -> ExitStatus {
318 self.output.status
319 }
320
321 #[track_caller]
323 pub fn assert_stdout_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
324 assert_equals(self.stdout_utf8().trim(), expected.as_ref().trim());
325 self
326 }
327
328 #[track_caller]
330 pub fn assert_stdout_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
331 assert_not_contains(&self.stdout_utf8(), unexpected);
332 self
333 }
334
335 #[track_caller]
337 pub fn assert_stdout_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
338 assert_not_contains_regex(&self.stdout_utf8(), unexpected);
339 self
340 }
341
342 #[track_caller]
344 pub fn assert_stdout_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
345 assert_contains(&self.stdout_utf8(), expected);
346 self
347 }
348
349 #[track_caller]
351 pub fn assert_stdout_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
352 assert_contains_regex(&self.stdout_utf8(), expected);
353 self
354 }
355
356 #[track_caller]
358 pub fn assert_stderr_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
359 assert_equals(self.stderr_utf8().trim(), expected.as_ref().trim());
360 self
361 }
362
363 #[track_caller]
365 pub fn assert_stderr_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
366 assert_contains(&self.stderr_utf8(), expected);
367 self
368 }
369
370 #[track_caller]
372 pub fn assert_stderr_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
373 assert_contains_regex(&self.stderr_utf8(), expected);
374 self
375 }
376
377 #[track_caller]
379 pub fn assert_stderr_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
380 assert_not_contains(&self.stderr_utf8(), unexpected);
381 self
382 }
383
384 #[track_caller]
386 pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
387 assert_not_contains_regex(&self.stderr_utf8(), unexpected);
388 self
389 }
390
391 #[track_caller]
396 pub fn assert_exit_code(&self, code: i32) -> &Self {
397 assert_eq!(self.output.status.code(), Some(code));
398 self
399 }
400}
401
402impl From<Output> for CompletedProcess {
403 fn from(output: Output) -> Self {
404 Self { output }
405 }
406}