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    /// Specify a stdin input buffer. This is a convenience helper,
67    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    /// Configuration for the child process’s standard input (stdin) handle.
73    ///
74    /// See [`std::process::Command::stdin`].
75    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
76        self.stdin = Some(cfg.into());
77        self
78    }
79
80    /// Configuration for the child process’s standard output (stdout) handle.
81    ///
82    /// See [`std::process::Command::stdout`].
83    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
84        self.stdout = Some(cfg.into());
85        self
86    }
87
88    /// Configuration for the child process’s standard error (stderr) handle.
89    ///
90    /// See [`std::process::Command::stderr`].
91    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
92        self.stderr = Some(cfg.into());
93        self
94    }
95
96    /// Specify an environment variable.
97    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    /// Remove an environmental variable.
107    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    /// Generic command argument provider. Prefer specific helper methods if possible.
116    /// Note that for some executables, arguments might be platform specific. For C/C++
117    /// compilers, arguments might be platform *and* compiler specific.
118    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    /// Generic command arguments provider. Prefer specific helper methods if possible.
127    /// Note that for some executables, arguments might be platform specific. For C/C++
128    /// compilers, arguments might be platform *and* compiler specific.
129    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    /// Inspect what the underlying [`std::process::Command`] is up to the
139    /// current construction.
140    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    /// Set the path where the command will be run.
149    pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
150        self.cmd.current_dir(path);
151        self
152    }
153
154    /// Set an auxiliary stream passed to the process, besides the stdio streams.
155    ///
156    /// # Notes
157    ///
158    /// Use with caution! Ideally, only set one aux fd; if there are multiple, their old `fd` may
159    /// overlap with another's `new_fd`, and may break. The caller must make sure this is not the
160    /// case. This function is only "safe" because the safety requirements are practically not
161    /// possible to uphold.
162    #[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        // NOTE: If more than 1 auxiliary file descriptor is needed, this function should be
170        // rewritten.
171        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        // Ensure fd stays open until the fork.
177        let fd = mem::ManuallyDrop::new(fd.into());
178        let fd = fd.as_raw_fd();
179
180        if fd == new_fd {
181            // If the new file descriptor is already the same as fd, just turn off `FD_CLOEXEC`.
182            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            // Clear `FD_CLOEXEC`.
190            let fd_flags = fd_flags & !libc::FD_CLOEXEC;
191
192            // SAFETY(io-safety): `fd` is already owned.
193            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                // SAFETY(io-safety): it's the caller's responsibility that we won't override the
199                // target fd.
200                cvt(unsafe { libc::dup2(fd, new_fd) })?;
201            }
202            Ok(())
203        };
204        // SAFETY(pre-exec-safe): `dup2` is pre-exec-safe.
205        unsafe { self.cmd.pre_exec(pre_exec) };
206        self
207    }
208
209    /// Run the constructed command and assert that it is successfully run.
210    ///
211    /// By default, std{in,out,err} are [`Stdio::piped()`].
212    #[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    /// Run the constructed command and assert that it does not successfully run.
222    ///
223    /// By default, std{in,out,err} are [`Stdio::piped()`].
224    #[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    /// Run the command but do not check its exit status. Only use if you explicitly don't care
234    /// about the exit status.
235    ///
236    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
237    #[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        // let's make sure we piped all the input and outputs
252        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
272/// Represents the result of an executed process.
273/// The various `assert_` helper methods should preferably be used for
274/// checking the contents of stdout/stderr.
275pub 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    /// Checks that trimmed `stdout` matches trimmed `expected`.
322    #[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    /// Checks that `stdout` does not contain `unexpected`.
329    #[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    /// Checks that `stdout` does not contain the regex pattern `unexpected`.
336    #[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    /// Checks that `stdout` contains `expected`.
343    #[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    /// Checks that `stdout` contains the regex pattern `expected`.
350    #[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    /// Checks that trimmed `stderr` matches trimmed `expected`.
357    #[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    /// Checks that `stderr` contains `expected`.
364    #[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    /// Checks that `stderr` contains the regex pattern `expected`.
371    #[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    /// Checks that `stderr` does not contain `unexpected`.
378    #[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    /// Checks that `stderr` does not contain the regex pattern `unexpected`.
385    #[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    /// Check the **exit status** of the process. On Unix, this is *not* the **wait status**.
392    ///
393    /// See [`std::process::ExitStatus::code`]. This is not to be confused with
394    /// [`std::process::ExitCode`].
395    #[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}