run_make_support/
command.rs1use 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(crate) fn into_raw_command(mut self) -> std::process::Command {
68 self.drop_bomb.defuse();
69 self.cmd
70 }
71
72 pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
74 self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
75 self
76 }
77
78 pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
82 self.stdin = Some(cfg.into());
83 self
84 }
85
86 pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
90 self.stdout = Some(cfg.into());
91 self
92 }
93
94 pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
98 self.stderr = Some(cfg.into());
99 self
100 }
101
102 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
104 where
105 K: AsRef<ffi::OsStr>,
106 V: AsRef<ffi::OsStr>,
107 {
108 self.cmd.env(key, value);
109 self
110 }
111
112 pub fn env_remove<K>(&mut self, key: K) -> &mut Self
114 where
115 K: AsRef<ffi::OsStr>,
116 {
117 self.cmd.env_remove(key);
118 self
119 }
120
121 pub fn arg<S>(&mut self, arg: S) -> &mut Self
125 where
126 S: AsRef<ffi::OsStr>,
127 {
128 self.cmd.arg(arg);
129 self
130 }
131
132 pub fn args<S, V>(&mut self, args: V) -> &mut Self
136 where
137 S: AsRef<ffi::OsStr>,
138 V: AsRef<[S]>,
139 {
140 self.cmd.args(args.as_ref());
141 self
142 }
143
144 pub fn inspect<I>(&mut self, inspector: I) -> &mut Self
147 where
148 I: FnOnce(&StdCommand),
149 {
150 inspector(&self.cmd);
151 self
152 }
153
154 pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
156 self.cmd.current_dir(path);
157 self
158 }
159
160 #[cfg(unix)]
169 pub fn set_aux_fd<F: Into<std::os::fd::OwnedFd>>(
170 &mut self,
171 new_fd: std::os::fd::RawFd,
172 fd: F,
173 ) -> &mut Self {
174 use std::mem;
175 use std::os::fd::AsRawFd;
178 use std::os::unix::process::CommandExt;
179
180 let cvt = |x| if x == -1 { Err(std::io::Error::last_os_error()) } else { Ok(()) };
181
182 let fd = mem::ManuallyDrop::new(fd.into());
184 let fd = fd.as_raw_fd();
185
186 if fd == new_fd {
187 let fd_flags = {
189 let ret = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
190 if ret < 0 {
191 panic!("failed to read fd flags: {}", std::io::Error::last_os_error());
192 }
193 ret
194 };
195 let fd_flags = fd_flags & !libc::FD_CLOEXEC;
197
198 cvt(unsafe { libc::fcntl(fd, libc::F_SETFD, fd_flags as libc::c_int) })
200 .expect("disabling CLOEXEC failed");
201 }
202 let pre_exec = move || {
203 if fd.as_raw_fd() != new_fd {
204 cvt(unsafe { libc::dup2(fd, new_fd) })?;
207 }
208 Ok(())
209 };
210 unsafe { self.cmd.pre_exec(pre_exec) };
212 self
213 }
214
215 #[track_caller]
219 pub fn run(&mut self) -> CompletedProcess {
220 let output = self.command_output();
221 if !output.status().success() {
222 handle_failed_output(&self, output, panic::Location::caller().line());
223 }
224 output
225 }
226
227 #[track_caller]
231 pub fn run_fail(&mut self) -> CompletedProcess {
232 let output = self.command_output();
233 if output.status().success() {
234 handle_failed_output(&self, output, panic::Location::caller().line());
235 }
236 output
237 }
238
239 #[track_caller]
244 pub fn run_unchecked(&mut self) -> CompletedProcess {
245 self.command_output()
246 }
247
248 #[track_caller]
249 fn command_output(&mut self) -> CompletedProcess {
250 if self.already_executed {
251 panic!("command was already executed");
252 } else {
253 self.already_executed = true;
254 }
255
256 self.drop_bomb.defuse();
257 self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
259 self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
260 self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
261
262 let output = if let Some(input) = &self.stdin_buf {
263 let mut child = self.cmd.spawn().unwrap();
264
265 {
266 let mut stdin = child.stdin.take().unwrap();
267 stdin.write_all(input.as_ref()).unwrap();
268 }
269
270 child.wait_with_output().expect("failed to get output of finished process")
271 } else {
272 self.cmd.output().expect("failed to get output of finished process")
273 };
274 output.into()
275 }
276}
277
278pub struct CompletedProcess {
282 output: Output,
283}
284
285impl CompletedProcess {
286 #[must_use]
287 #[track_caller]
288 pub fn stdout(&self) -> Vec<u8> {
289 self.output.stdout.clone()
290 }
291
292 #[must_use]
293 #[track_caller]
294 pub fn stdout_utf8(&self) -> String {
295 String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
296 }
297
298 #[must_use]
299 #[track_caller]
300 pub fn invalid_stdout_utf8(&self) -> String {
301 String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
302 }
303
304 #[must_use]
305 #[track_caller]
306 pub fn stderr(&self) -> Vec<u8> {
307 self.output.stderr.clone()
308 }
309
310 #[must_use]
311 #[track_caller]
312 pub fn stderr_utf8(&self) -> String {
313 String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
314 }
315
316 #[must_use]
317 #[track_caller]
318 pub fn invalid_stderr_utf8(&self) -> String {
319 String::from_utf8_lossy(&self.output.stderr.clone()).to_string()
320 }
321
322 #[must_use]
323 pub fn status(&self) -> ExitStatus {
324 self.output.status
325 }
326
327 #[track_caller]
329 pub fn assert_stdout_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
330 assert_equals(self.stdout_utf8().trim(), expected.as_ref().trim());
331 self
332 }
333
334 #[track_caller]
336 pub fn assert_stdout_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
337 assert_not_contains(&self.stdout_utf8(), unexpected);
338 self
339 }
340
341 #[track_caller]
343 pub fn assert_stdout_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
344 assert_not_contains_regex(&self.stdout_utf8(), unexpected);
345 self
346 }
347
348 #[track_caller]
350 pub fn assert_stdout_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
351 assert_contains(&self.stdout_utf8(), expected);
352 self
353 }
354
355 #[track_caller]
357 pub fn assert_stdout_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
358 assert_contains_regex(&self.stdout_utf8(), expected);
359 self
360 }
361
362 #[track_caller]
364 pub fn assert_stderr_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
365 assert_equals(self.stderr_utf8().trim(), expected.as_ref().trim());
366 self
367 }
368
369 #[track_caller]
371 pub fn assert_stderr_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
372 assert_contains(&self.stderr_utf8(), expected);
373 self
374 }
375
376 #[track_caller]
378 pub fn assert_stderr_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
379 assert_contains_regex(&self.stderr_utf8(), expected);
380 self
381 }
382
383 #[track_caller]
385 pub fn assert_stderr_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
386 assert_not_contains(&self.stderr_utf8(), unexpected);
387 self
388 }
389
390 #[track_caller]
392 pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
393 assert_not_contains_regex(&self.stderr_utf8(), unexpected);
394 self
395 }
396
397 #[track_caller]
402 pub fn assert_exit_code(&self, code: i32) -> &Self {
403 assert_eq!(self.output.status.code(), Some(code));
404 self
405 }
406}
407
408impl From<Output> for CompletedProcess {
409 fn from(output: Output) -> Self {
410 Self { output }
411 }
412}