Skip to main content

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, verbose_print_command};
10use crate::{
11    assert_contains, assert_contains_regex, assert_equals, assert_not_contains,
12    assert_not_contains_regex,
13};
14
15/// This is a custom command wrapper that simplifies working with commands and makes it easier to
16/// ensure that we check the exit status of executed processes.
17///
18/// # A [`Command`] must be executed exactly once
19///
20/// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
21/// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
22/// to panic. Execution methods [`run`] and [`run_fail`] will defuse the drop bomb. A test
23/// containing constructed but never executed commands is dangerous because it can give a false
24/// sense of confidence.
25///
26/// Each [`Command`] invocation can also only be executed once, because we want to enforce
27/// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
28/// cloneable.
29///
30/// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
31///
32/// [`run`]: Self::run
33/// [`run_fail`]: Self::run_fail
34/// [`run_unchecked`]: Self::run_unchecked
35#[derive(Debug)]
36pub struct Command {
37    cmd: StdCommand,
38    // Convience for providing a quick stdin buffer.
39    stdin_buf: Option<Box<[u8]>>,
40
41    // Configurations for child process's std{in,out,err} handles.
42    stdin: Option<Stdio>,
43    stdout: Option<Stdio>,
44    stderr: Option<Stdio>,
45
46    // Emulate linear type semantics.
47    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    // Internal-only.
70    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    /// Appends context to the command, to provide a better error message if the command fails.
80    pub fn context(&mut self, ctx: &str) -> &mut Self {
81        self.context.push_str(&format!("{ctx}\n"));
82        self
83    }
84
85    /// Specify a stdin input buffer. This is a convenience helper,
86    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    /// Configuration for the child process’s standard input (stdin) handle.
92    ///
93    /// See [`std::process::Command::stdin`].
94    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
95        self.stdin = Some(cfg.into());
96        self
97    }
98
99    /// Configuration for the child process’s standard output (stdout) handle.
100    ///
101    /// See [`std::process::Command::stdout`].
102    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
103        self.stdout = Some(cfg.into());
104        self
105    }
106
107    /// Configuration for the child process’s standard error (stderr) handle.
108    ///
109    /// See [`std::process::Command::stderr`].
110    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
111        self.stderr = Some(cfg.into());
112        self
113    }
114
115    /// Specify an environment variable.
116    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    /// Remove an environmental variable.
126    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    /// Generic command argument provider. Prefer specific helper methods if possible.
135    /// Note that for some executables, arguments might be platform specific. For C/C++
136    /// compilers, arguments might be platform *and* compiler specific.
137    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    /// Generic command arguments provider. Prefer specific helper methods if possible.
146    /// Note that for some executables, arguments might be platform specific. For C/C++
147    /// compilers, arguments might be platform *and* compiler specific.
148    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    /// Inspect what the underlying [`std::process::Command`] is up to the
158    /// current construction.
159    pub fn inspect<I>(&self, inspector: I) -> &Self
160    where
161        I: FnOnce(&StdCommand),
162    {
163        inspector(&self.cmd);
164        self
165    }
166
167    /// Set the path where the command will be run.
168    pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
169        self.cmd.current_dir(path);
170        self
171    }
172
173    /// Set an auxiliary stream passed to the process, besides the stdio streams.
174    ///
175    /// # Notes
176    ///
177    /// Use with caution! Ideally, only set one aux fd; if there are multiple, their old `fd` may
178    /// overlap with another's `new_fd`, and may break. The caller must make sure this is not the
179    /// case. This function is only "safe" because the safety requirements are practically not
180    /// possible to uphold.
181    #[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        // NOTE: If more than 1 auxiliary file descriptor is needed, this function should be
189        // rewritten.
190        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        // Ensure fd stays open until the fork.
196        let fd = mem::ManuallyDrop::new(fd.into());
197        let fd = fd.as_raw_fd();
198
199        if fd == new_fd {
200            // If the new file descriptor is already the same as fd, just turn off `FD_CLOEXEC`.
201            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            // Clear `FD_CLOEXEC`.
209            let fd_flags = fd_flags & !libc::FD_CLOEXEC;
210
211            // SAFETY(io-safety): `fd` is already owned.
212            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                // SAFETY(io-safety): it's the caller's responsibility that we won't override the
218                // target fd.
219                cvt(unsafe { libc::dup2(fd, new_fd) })?;
220            }
221            Ok(())
222        };
223        // SAFETY(pre-exec-safe): `dup2` is pre-exec-safe.
224        unsafe { self.cmd.pre_exec(pre_exec) };
225        self
226    }
227
228    /// Run the constructed command and assert that it is successfully run.
229    ///
230    /// By default, std{in,out,err} are [`Stdio::piped()`].
231    #[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        } else {
237            verbose_print_command(self, &output);
238        }
239        output
240    }
241
242    /// Run the constructed command and assert that it does not successfully run.
243    ///
244    /// By default, std{in,out,err} are [`Stdio::piped()`].
245    #[track_caller]
246    pub fn run_fail(&mut self) -> CompletedProcess {
247        let output = self.command_output();
248        if output.status().success() {
249            handle_failed_output(&self, output, panic::Location::caller().line());
250        } else {
251            verbose_print_command(self, &output);
252        }
253        output
254    }
255
256    /// Run the command but do not check its exit status. Only use if you explicitly don't care
257    /// about the exit status.
258    ///
259    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
260    #[track_caller]
261    pub fn run_unchecked(&mut self) -> CompletedProcess {
262        self.command_output()
263    }
264
265    #[track_caller]
266    fn command_output(&mut self) -> CompletedProcess {
267        if self.already_executed {
268            panic!("command was already executed");
269        } else {
270            self.already_executed = true;
271        }
272
273        self.drop_bomb.defuse();
274        // let's make sure we piped all the input and outputs
275        self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
276        self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
277        self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
278
279        let output = if let Some(input) = &self.stdin_buf {
280            let mut child = self.cmd.spawn().unwrap();
281
282            {
283                let mut stdin = child.stdin.take().unwrap();
284                stdin.write_all(input.as_ref()).unwrap();
285            }
286
287            child.wait_with_output().expect("failed to get output of finished process")
288        } else {
289            self.cmd.output().expect("failed to get output of finished process")
290        };
291        output.into()
292    }
293}
294
295/// Represents the result of an executed process.
296/// The various `assert_` helper methods should preferably be used for
297/// checking the contents of stdout/stderr.
298pub struct CompletedProcess {
299    output: Output,
300}
301
302impl CompletedProcess {
303    #[must_use]
304    #[track_caller]
305    pub fn stdout(&self) -> Vec<u8> {
306        self.output.stdout.clone()
307    }
308
309    #[must_use]
310    #[track_caller]
311    pub fn stdout_utf8(&self) -> String {
312        String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
313    }
314
315    #[must_use]
316    #[track_caller]
317    pub fn invalid_stdout_utf8(&self) -> String {
318        String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
319    }
320
321    #[must_use]
322    #[track_caller]
323    pub fn stderr(&self) -> Vec<u8> {
324        self.output.stderr.clone()
325    }
326
327    #[must_use]
328    #[track_caller]
329    pub fn stderr_utf8(&self) -> String {
330        String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
331    }
332
333    #[must_use]
334    #[track_caller]
335    pub fn invalid_stderr_utf8(&self) -> String {
336        String::from_utf8_lossy(&self.output.stderr.clone()).to_string()
337    }
338
339    #[must_use]
340    pub fn status(&self) -> ExitStatus {
341        self.output.status
342    }
343
344    /// Checks that trimmed `stdout` matches trimmed `expected`.
345    #[track_caller]
346    pub fn assert_stdout_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
347        assert_equals(self.stdout_utf8().trim(), expected.as_ref().trim());
348        self
349    }
350
351    /// Checks that `stdout` does not contain `unexpected`.
352    #[track_caller]
353    pub fn assert_stdout_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
354        assert_not_contains(&self.stdout_utf8(), unexpected);
355        self
356    }
357
358    /// Checks that `stdout` does not contain the regex pattern `unexpected`.
359    #[track_caller]
360    pub fn assert_stdout_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
361        assert_not_contains_regex(&self.stdout_utf8(), unexpected);
362        self
363    }
364
365    /// Checks that `stdout` contains `expected`.
366    #[track_caller]
367    pub fn assert_stdout_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
368        assert_contains(&self.stdout_utf8(), expected);
369        self
370    }
371
372    /// Checks that `stdout` contains the regex pattern `expected`.
373    #[track_caller]
374    pub fn assert_stdout_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
375        assert_contains_regex(&self.stdout_utf8(), expected);
376        self
377    }
378
379    /// Checks that trimmed `stderr` matches trimmed `expected`.
380    #[track_caller]
381    pub fn assert_stderr_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
382        assert_equals(self.stderr_utf8().trim(), expected.as_ref().trim());
383        self
384    }
385
386    /// Checks that `stderr` contains `expected`.
387    #[track_caller]
388    pub fn assert_stderr_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
389        assert_contains(&self.stderr_utf8(), expected);
390        self
391    }
392
393    /// Checks that `stderr` contains the regex pattern `expected`.
394    #[track_caller]
395    pub fn assert_stderr_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
396        assert_contains_regex(&self.stderr_utf8(), expected);
397        self
398    }
399
400    /// Checks that `stderr` does not contain `unexpected`.
401    #[track_caller]
402    pub fn assert_stderr_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
403        assert_not_contains(&self.stderr_utf8(), unexpected);
404        self
405    }
406
407    /// Checks that `stderr` doesn't contain the Internal Compiler Error message.
408    #[track_caller]
409    pub fn assert_not_ice(&self) -> &Self {
410        self.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug");
411        self
412    }
413
414    /// Checks that `stderr` does not contain the regex pattern `unexpected`.
415    #[track_caller]
416    pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
417        assert_not_contains_regex(&self.stderr_utf8(), unexpected);
418        self
419    }
420
421    /// Check the **exit status** of the process. On Unix, this is *not* the **wait status**.
422    ///
423    /// See [`std::process::ExitStatus::code`]. This is not to be confused with
424    /// [`std::process::ExitCode`].
425    #[track_caller]
426    pub fn assert_exit_code(&self, code: i32) -> &Self {
427        assert_eq!(self.output.status.code(), Some(code));
428        self
429    }
430}
431
432impl From<Output> for CompletedProcess {
433    fn from(output: Output) -> Self {
434        Self { output }
435    }
436}