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/// 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
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    // Internal-only.
67    pub(crate) fn into_raw_command(mut self) -> std::process::Command {
68        self.drop_bomb.defuse();
69        self.cmd
70    }
71
72    /// Specify a stdin input buffer. This is a convenience helper,
73    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    /// Configuration for the child process’s standard input (stdin) handle.
79    ///
80    /// See [`std::process::Command::stdin`].
81    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
82        self.stdin = Some(cfg.into());
83        self
84    }
85
86    /// Configuration for the child process’s standard output (stdout) handle.
87    ///
88    /// See [`std::process::Command::stdout`].
89    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
90        self.stdout = Some(cfg.into());
91        self
92    }
93
94    /// Configuration for the child process’s standard error (stderr) handle.
95    ///
96    /// See [`std::process::Command::stderr`].
97    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
98        self.stderr = Some(cfg.into());
99        self
100    }
101
102    /// Specify an environment variable.
103    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    /// Remove an environmental variable.
113    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    /// Generic command argument provider. Prefer specific helper methods if possible.
122    /// Note that for some executables, arguments might be platform specific. For C/C++
123    /// compilers, arguments might be platform *and* compiler specific.
124    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    /// Generic command arguments provider. Prefer specific helper methods if possible.
133    /// Note that for some executables, arguments might be platform specific. For C/C++
134    /// compilers, arguments might be platform *and* compiler specific.
135    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    /// Inspect what the underlying [`std::process::Command`] is up to the
145    /// current construction.
146    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    /// Set the path where the command will be run.
155    pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
156        self.cmd.current_dir(path);
157        self
158    }
159
160    /// Set an auxiliary stream passed to the process, besides the stdio streams.
161    ///
162    /// # Notes
163    ///
164    /// Use with caution! Ideally, only set one aux fd; if there are multiple, their old `fd` may
165    /// overlap with another's `new_fd`, and may break. The caller must make sure this is not the
166    /// case. This function is only "safe" because the safety requirements are practically not
167    /// possible to uphold.
168    #[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        // NOTE: If more than 1 auxiliary file descriptor is needed, this function should be
176        // rewritten.
177        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        // Ensure fd stays open until the fork.
183        let fd = mem::ManuallyDrop::new(fd.into());
184        let fd = fd.as_raw_fd();
185
186        if fd == new_fd {
187            // If the new file descriptor is already the same as fd, just turn off `FD_CLOEXEC`.
188            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            // Clear `FD_CLOEXEC`.
196            let fd_flags = fd_flags & !libc::FD_CLOEXEC;
197
198            // SAFETY(io-safety): `fd` is already owned.
199            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                // SAFETY(io-safety): it's the caller's responsibility that we won't override the
205                // target fd.
206                cvt(unsafe { libc::dup2(fd, new_fd) })?;
207            }
208            Ok(())
209        };
210        // SAFETY(pre-exec-safe): `dup2` is pre-exec-safe.
211        unsafe { self.cmd.pre_exec(pre_exec) };
212        self
213    }
214
215    /// Run the constructed command and assert that it is successfully run.
216    ///
217    /// By default, std{in,out,err} are [`Stdio::piped()`].
218    #[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    /// Run the constructed command and assert that it does not successfully run.
228    ///
229    /// By default, std{in,out,err} are [`Stdio::piped()`].
230    #[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    /// Run the command but do not check its exit status. Only use if you explicitly don't care
240    /// about the exit status.
241    ///
242    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
243    #[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        // let's make sure we piped all the input and outputs
258        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
278/// Represents the result of an executed process.
279/// The various `assert_` helper methods should preferably be used for
280/// checking the contents of stdout/stderr.
281pub 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    /// Checks that trimmed `stdout` matches trimmed `expected`.
328    #[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    /// Checks that `stdout` does not contain `unexpected`.
335    #[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    /// Checks that `stdout` does not contain the regex pattern `unexpected`.
342    #[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    /// Checks that `stdout` contains `expected`.
349    #[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    /// Checks that `stdout` contains the regex pattern `expected`.
356    #[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    /// Checks that trimmed `stderr` matches trimmed `expected`.
363    #[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    /// Checks that `stderr` contains `expected`.
370    #[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    /// Checks that `stderr` contains the regex pattern `expected`.
377    #[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    /// Checks that `stderr` does not contain `unexpected`.
384    #[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    /// Checks that `stderr` does not contain the regex pattern `unexpected`.
391    #[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    /// Check the **exit status** of the process. On Unix, this is *not* the **wait status**.
398    ///
399    /// See [`std::process::ExitStatus::code`]. This is not to be confused with
400    /// [`std::process::ExitCode`].
401    #[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}