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;
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>(&mut self, inspector: I) -> &mut 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        }
237        output
238    }
239
240    /// Run the constructed command and assert that it does not successfully run.
241    ///
242    /// By default, std{in,out,err} are [`Stdio::piped()`].
243    #[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    /// Run the command but do not check its exit status. Only use if you explicitly don't care
253    /// about the exit status.
254    ///
255    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
256    #[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        // let's make sure we piped all the input and outputs
271        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
291/// Represents the result of an executed process.
292/// The various `assert_` helper methods should preferably be used for
293/// checking the contents of stdout/stderr.
294pub 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    /// Checks that trimmed `stdout` matches trimmed `expected`.
341    #[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    /// Checks that `stdout` does not contain `unexpected`.
348    #[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    /// Checks that `stdout` does not contain the regex pattern `unexpected`.
355    #[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    /// Checks that `stdout` contains `expected`.
362    #[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    /// Checks that `stdout` contains the regex pattern `expected`.
369    #[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    /// Checks that trimmed `stderr` matches trimmed `expected`.
376    #[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    /// Checks that `stderr` contains `expected`.
383    #[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    /// Checks that `stderr` contains the regex pattern `expected`.
390    #[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    /// Checks that `stderr` does not contain `unexpected`.
397    #[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    /// Checks that `stderr` doesn't contain the Internal Compiler Error message.
404    #[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    /// Checks that `stderr` does not contain the regex pattern `unexpected`.
411    #[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    /// Check the **exit status** of the process. On Unix, this is *not* the **wait status**.
418    ///
419    /// See [`std::process::ExitStatus::code`]. This is not to be confused with
420    /// [`std::process::ExitCode`].
421    #[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}