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 context: String,
51}
52
53impl Command {
54 #[track_caller]
55 pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
56 let program = program.as_ref();
57 Self {
58 cmd: StdCommand::new(program),
59 stdin_buf: None,
60 drop_bomb: DropBomb::arm(program),
61 stdin: None,
62 stdout: None,
63 stderr: None,
64 already_executed: false,
65 context: String::new(),
66 }
67 }
68
69 pub(crate) fn into_raw_command(mut self) -> std::process::Command {
71 self.drop_bomb.defuse();
72 self.cmd
73 }
74
75 pub(crate) fn get_context(&self) -> &str {
76 &self.context
77 }
78
79 pub fn context(&mut self, ctx: &str) -> &mut Self {
81 self.context.push_str(&format!("{ctx}\n"));
82 self
83 }
84
85 pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
87 self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
88 self
89 }
90
91 pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
95 self.stdin = Some(cfg.into());
96 self
97 }
98
99 pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
103 self.stdout = Some(cfg.into());
104 self
105 }
106
107 pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
111 self.stderr = Some(cfg.into());
112 self
113 }
114
115 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
117 where
118 K: AsRef<ffi::OsStr>,
119 V: AsRef<ffi::OsStr>,
120 {
121 self.cmd.env(key, value);
122 self
123 }
124
125 pub fn env_remove<K>(&mut self, key: K) -> &mut Self
127 where
128 K: AsRef<ffi::OsStr>,
129 {
130 self.cmd.env_remove(key);
131 self
132 }
133
134 pub fn arg<S>(&mut self, arg: S) -> &mut Self
138 where
139 S: AsRef<ffi::OsStr>,
140 {
141 self.cmd.arg(arg);
142 self
143 }
144
145 pub fn args<S, V>(&mut self, args: V) -> &mut Self
149 where
150 S: AsRef<ffi::OsStr>,
151 V: AsRef<[S]>,
152 {
153 self.cmd.args(args.as_ref());
154 self
155 }
156
157 pub fn inspect<I>(&mut self, inspector: I) -> &mut Self
160 where
161 I: FnOnce(&StdCommand),
162 {
163 inspector(&self.cmd);
164 self
165 }
166
167 pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
169 self.cmd.current_dir(path);
170 self
171 }
172
173 #[cfg(unix)]
182 pub fn set_aux_fd<F: Into<std::os::fd::OwnedFd>>(
183 &mut self,
184 new_fd: std::os::fd::RawFd,
185 fd: F,
186 ) -> &mut Self {
187 use std::mem;
188 use std::os::fd::AsRawFd;
191 use std::os::unix::process::CommandExt;
192
193 let cvt = |x| if x == -1 { Err(std::io::Error::last_os_error()) } else { Ok(()) };
194
195 let fd = mem::ManuallyDrop::new(fd.into());
197 let fd = fd.as_raw_fd();
198
199 if fd == new_fd {
200 let fd_flags = {
202 let ret = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
203 if ret < 0 {
204 panic!("failed to read fd flags: {}", std::io::Error::last_os_error());
205 }
206 ret
207 };
208 let fd_flags = fd_flags & !libc::FD_CLOEXEC;
210
211 cvt(unsafe { libc::fcntl(fd, libc::F_SETFD, fd_flags as libc::c_int) })
213 .expect("disabling CLOEXEC failed");
214 }
215 let pre_exec = move || {
216 if fd.as_raw_fd() != new_fd {
217 cvt(unsafe { libc::dup2(fd, new_fd) })?;
220 }
221 Ok(())
222 };
223 unsafe { self.cmd.pre_exec(pre_exec) };
225 self
226 }
227
228 #[track_caller]
232 pub fn run(&mut self) -> CompletedProcess {
233 let output = self.command_output();
234 if !output.status().success() {
235 handle_failed_output(&self, output, panic::Location::caller().line());
236 }
237 output
238 }
239
240 #[track_caller]
244 pub fn run_fail(&mut self) -> CompletedProcess {
245 let output = self.command_output();
246 if output.status().success() {
247 handle_failed_output(&self, output, panic::Location::caller().line());
248 }
249 output
250 }
251
252 #[track_caller]
257 pub fn run_unchecked(&mut self) -> CompletedProcess {
258 self.command_output()
259 }
260
261 #[track_caller]
262 fn command_output(&mut self) -> CompletedProcess {
263 if self.already_executed {
264 panic!("command was already executed");
265 } else {
266 self.already_executed = true;
267 }
268
269 self.drop_bomb.defuse();
270 self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
272 self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
273 self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
274
275 let output = if let Some(input) = &self.stdin_buf {
276 let mut child = self.cmd.spawn().unwrap();
277
278 {
279 let mut stdin = child.stdin.take().unwrap();
280 stdin.write_all(input.as_ref()).unwrap();
281 }
282
283 child.wait_with_output().expect("failed to get output of finished process")
284 } else {
285 self.cmd.output().expect("failed to get output of finished process")
286 };
287 output.into()
288 }
289}
290
291pub struct CompletedProcess {
295 output: Output,
296}
297
298impl CompletedProcess {
299 #[must_use]
300 #[track_caller]
301 pub fn stdout(&self) -> Vec<u8> {
302 self.output.stdout.clone()
303 }
304
305 #[must_use]
306 #[track_caller]
307 pub fn stdout_utf8(&self) -> String {
308 String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
309 }
310
311 #[must_use]
312 #[track_caller]
313 pub fn invalid_stdout_utf8(&self) -> String {
314 String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
315 }
316
317 #[must_use]
318 #[track_caller]
319 pub fn stderr(&self) -> Vec<u8> {
320 self.output.stderr.clone()
321 }
322
323 #[must_use]
324 #[track_caller]
325 pub fn stderr_utf8(&self) -> String {
326 String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
327 }
328
329 #[must_use]
330 #[track_caller]
331 pub fn invalid_stderr_utf8(&self) -> String {
332 String::from_utf8_lossy(&self.output.stderr.clone()).to_string()
333 }
334
335 #[must_use]
336 pub fn status(&self) -> ExitStatus {
337 self.output.status
338 }
339
340 #[track_caller]
342 pub fn assert_stdout_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
343 assert_equals(self.stdout_utf8().trim(), expected.as_ref().trim());
344 self
345 }
346
347 #[track_caller]
349 pub fn assert_stdout_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
350 assert_not_contains(&self.stdout_utf8(), unexpected);
351 self
352 }
353
354 #[track_caller]
356 pub fn assert_stdout_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
357 assert_not_contains_regex(&self.stdout_utf8(), unexpected);
358 self
359 }
360
361 #[track_caller]
363 pub fn assert_stdout_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
364 assert_contains(&self.stdout_utf8(), expected);
365 self
366 }
367
368 #[track_caller]
370 pub fn assert_stdout_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
371 assert_contains_regex(&self.stdout_utf8(), expected);
372 self
373 }
374
375 #[track_caller]
377 pub fn assert_stderr_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
378 assert_equals(self.stderr_utf8().trim(), expected.as_ref().trim());
379 self
380 }
381
382 #[track_caller]
384 pub fn assert_stderr_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
385 assert_contains(&self.stderr_utf8(), expected);
386 self
387 }
388
389 #[track_caller]
391 pub fn assert_stderr_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
392 assert_contains_regex(&self.stderr_utf8(), expected);
393 self
394 }
395
396 #[track_caller]
398 pub fn assert_stderr_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
399 assert_not_contains(&self.stderr_utf8(), unexpected);
400 self
401 }
402
403 #[track_caller]
405 pub fn assert_not_ice(&self) -> &Self {
406 self.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug");
407 self
408 }
409
410 #[track_caller]
412 pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
413 assert_not_contains_regex(&self.stderr_utf8(), unexpected);
414 self
415 }
416
417 #[track_caller]
422 pub fn assert_exit_code(&self, code: i32) -> &Self {
423 assert_eq!(self.output.status.code(), Some(code));
424 self
425 }
426}
427
428impl From<Output> for CompletedProcess {
429 fn from(output: Output) -> Self {
430 Self { output }
431 }
432}