Skip to content

Commit f21554f

Browse files
committed
Auto merge of #126121 - Kobzol:runmake-cmd-wrapper, r=jieyouxu
Add a custom Command wrapper to `run-make-support` This should make it easier to make sure that we check process exit codes, and it should also make checking of stdout/stderr less verbose and more explicit in run-make tests. I prefer the `run()/run_fail().assert(...)` style to something like `run_fail_assert_exit_code`, because the former is more composable. Regarding #125747, I'm not sure if we really need a custom trait, I think that we can get far enough with just `Deref` on the `Cc/Clang/Rustc/Rustdoc/...` structs. But now that these structs don't even need `command_output` anymore, I think that they are fine-ish as they are with the macro. Related issues: #125617, #125747 Fixes: #125617 (because `command_output` is no longer a public method) r? `@jieyouxu`
2 parents 565cadb + 0a190e8 commit f21554f

File tree

30 files changed

+289
-247
lines changed

30 files changed

+289
-247
lines changed

src/tools/run-make-support/src/cc.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::Path;
2-
use std::process::Command;
32

4-
use crate::{bin_name, cygpath_windows, env_var, handle_failed_output, is_msvc, is_windows, uname};
3+
use crate::command::Command;
4+
use crate::{bin_name, cygpath_windows, env_var, is_msvc, is_windows, uname};
55

66
/// Construct a new platform-specific C compiler invocation.
77
///

src/tools/run-make-support/src/clang.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::Path;
2-
use std::process::Command;
32

4-
use crate::{bin_name, env_var, handle_failed_output};
3+
use crate::command::Command;
4+
use crate::{bin_name, env_var};
55

66
/// Construct a new `clang` invocation. `clang` is not always available for all targets.
77
pub fn clang() -> Clang {
@@ -68,9 +68,4 @@ impl Clang {
6868
self.cmd.arg(format!("-fuse-ld={ld}"));
6969
self
7070
}
71-
72-
/// Get the [`Output`][::std::process::Output] of the finished process.
73-
pub fn command_output(&mut self) -> ::std::process::Output {
74-
self.cmd.output().expect("failed to get output of finished process")
75-
}
7671
}
+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use crate::{assert_not_contains, handle_failed_output};
2+
use std::ffi::OsStr;
3+
use std::io::Write;
4+
use std::ops::{Deref, DerefMut};
5+
use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
6+
7+
/// This is a custom command wrapper that simplifies working with commands
8+
/// and makes it easier to ensure that we check the exit status of executed
9+
/// processes.
10+
#[derive(Debug)]
11+
pub struct Command {
12+
cmd: StdCommand,
13+
stdin: Option<Box<[u8]>>,
14+
}
15+
16+
impl Command {
17+
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
18+
Self { cmd: StdCommand::new(program), stdin: None }
19+
}
20+
21+
pub fn set_stdin(&mut self, stdin: Box<[u8]>) {
22+
self.stdin = Some(stdin);
23+
}
24+
25+
/// Run the constructed command and assert that it is successfully run.
26+
#[track_caller]
27+
pub fn run(&mut self) -> CompletedProcess {
28+
let caller_location = std::panic::Location::caller();
29+
let caller_line_number = caller_location.line();
30+
31+
let output = self.command_output();
32+
if !output.status().success() {
33+
handle_failed_output(&self, output, caller_line_number);
34+
}
35+
output
36+
}
37+
38+
/// Run the constructed command and assert that it does not successfully run.
39+
#[track_caller]
40+
pub fn run_fail(&mut self) -> CompletedProcess {
41+
let caller_location = std::panic::Location::caller();
42+
let caller_line_number = caller_location.line();
43+
44+
let output = self.command_output();
45+
if output.status().success() {
46+
handle_failed_output(&self, output, caller_line_number);
47+
}
48+
output
49+
}
50+
51+
#[track_caller]
52+
pub(crate) fn command_output(&mut self) -> CompletedProcess {
53+
// let's make sure we piped all the input and outputs
54+
self.cmd.stdin(Stdio::piped());
55+
self.cmd.stdout(Stdio::piped());
56+
self.cmd.stderr(Stdio::piped());
57+
58+
let output = if let Some(input) = &self.stdin {
59+
let mut child = self.cmd.spawn().unwrap();
60+
61+
{
62+
let mut stdin = child.stdin.take().unwrap();
63+
stdin.write_all(input.as_ref()).unwrap();
64+
}
65+
66+
child.wait_with_output().expect("failed to get output of finished process")
67+
} else {
68+
self.cmd.output().expect("failed to get output of finished process")
69+
};
70+
output.into()
71+
}
72+
}
73+
74+
impl Deref for Command {
75+
type Target = StdCommand;
76+
77+
fn deref(&self) -> &Self::Target {
78+
&self.cmd
79+
}
80+
}
81+
82+
impl DerefMut for Command {
83+
fn deref_mut(&mut self) -> &mut Self::Target {
84+
&mut self.cmd
85+
}
86+
}
87+
88+
/// Represents the result of an executed process.
89+
/// The various `assert_` helper methods should preferably be used for
90+
/// checking the contents of stdout/stderr.
91+
pub struct CompletedProcess {
92+
output: Output,
93+
}
94+
95+
impl CompletedProcess {
96+
pub fn stdout_utf8(&self) -> String {
97+
String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
98+
}
99+
100+
pub fn stderr_utf8(&self) -> String {
101+
String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
102+
}
103+
104+
pub fn status(&self) -> ExitStatus {
105+
self.output.status
106+
}
107+
108+
/// Checks that trimmed `stdout` matches trimmed `content`.
109+
#[track_caller]
110+
pub fn assert_stdout_equals<S: AsRef<str>>(self, content: S) -> Self {
111+
assert_eq!(self.stdout_utf8().trim(), content.as_ref().trim());
112+
self
113+
}
114+
115+
#[track_caller]
116+
pub fn assert_stdout_not_contains<S: AsRef<str>>(self, needle: S) -> Self {
117+
assert_not_contains(&self.stdout_utf8(), needle.as_ref());
118+
self
119+
}
120+
121+
/// Checks that trimmed `stderr` matches trimmed `content`.
122+
#[track_caller]
123+
pub fn assert_stderr_equals<S: AsRef<str>>(self, content: S) -> Self {
124+
assert_eq!(self.stderr_utf8().trim(), content.as_ref().trim());
125+
self
126+
}
127+
128+
#[track_caller]
129+
pub fn assert_stderr_contains<S: AsRef<str>>(self, needle: S) -> Self {
130+
assert!(self.stderr_utf8().contains(needle.as_ref()));
131+
self
132+
}
133+
134+
#[track_caller]
135+
pub fn assert_stderr_not_contains<S: AsRef<str>>(self, needle: S) -> Self {
136+
assert_not_contains(&self.stdout_utf8(), needle.as_ref());
137+
self
138+
}
139+
140+
#[track_caller]
141+
pub fn assert_exit_code(self, code: i32) -> Self {
142+
assert!(self.output.status.code() == Some(code));
143+
self
144+
}
145+
}
146+
147+
impl From<Output> for CompletedProcess {
148+
fn from(output: Output) -> Self {
149+
Self { output }
150+
}
151+
}

src/tools/run-make-support/src/lib.rs

+19-32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
pub mod cc;
77
pub mod clang;
8+
mod command;
89
pub mod diff;
910
pub mod llvm_readobj;
1011
pub mod run;
@@ -16,7 +17,6 @@ use std::ffi::OsString;
1617
use std::fs;
1718
use std::io;
1819
use std::path::{Path, PathBuf};
19-
use std::process::{Command, Output};
2020

2121
pub use gimli;
2222
pub use object;
@@ -27,7 +27,7 @@ pub use cc::{cc, extra_c_flags, extra_cxx_flags, Cc};
2727
pub use clang::{clang, Clang};
2828
pub use diff::{diff, Diff};
2929
pub use llvm_readobj::{llvm_readobj, LlvmReadobj};
30-
pub use run::{run, run_fail};
30+
pub use run::{cmd, run, run_fail};
3131
pub use rustc::{aux_build, rustc, Rustc};
3232
pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
3333

@@ -167,13 +167,12 @@ pub fn cygpath_windows<P: AsRef<Path>>(path: P) -> String {
167167
let mut cygpath = Command::new("cygpath");
168168
cygpath.arg("-w");
169169
cygpath.arg(path.as_ref());
170-
let output = cygpath.output().unwrap();
171-
if !output.status.success() {
170+
let output = cygpath.command_output();
171+
if !output.status().success() {
172172
handle_failed_output(&cygpath, output, caller_line_number);
173173
}
174-
let s = String::from_utf8(output.stdout).unwrap();
175174
// cygpath -w can attach a newline
176-
s.trim().to_string()
175+
output.stdout_utf8().trim().to_string()
177176
}
178177

179178
/// Run `uname`. This assumes that `uname` is available on the platform!
@@ -183,23 +182,23 @@ pub fn uname() -> String {
183182
let caller_line_number = caller_location.line();
184183

185184
let mut uname = Command::new("uname");
186-
let output = uname.output().unwrap();
187-
if !output.status.success() {
185+
let output = uname.command_output();
186+
if !output.status().success() {
188187
handle_failed_output(&uname, output, caller_line_number);
189188
}
190-
String::from_utf8(output.stdout).unwrap()
189+
output.stdout_utf8()
191190
}
192191

193-
fn handle_failed_output(cmd: &Command, output: Output, caller_line_number: u32) -> ! {
194-
if output.status.success() {
192+
fn handle_failed_output(cmd: &Command, output: CompletedProcess, caller_line_number: u32) -> ! {
193+
if output.status().success() {
195194
eprintln!("command unexpectedly succeeded at line {caller_line_number}");
196195
} else {
197196
eprintln!("command failed at line {caller_line_number}");
198197
}
199198
eprintln!("{cmd:?}");
200-
eprintln!("output status: `{}`", output.status);
201-
eprintln!("=== STDOUT ===\n{}\n\n", String::from_utf8(output.stdout).unwrap());
202-
eprintln!("=== STDERR ===\n{}\n\n", String::from_utf8(output.stderr).unwrap());
199+
eprintln!("output status: `{}`", output.status());
200+
eprintln!("=== STDOUT ===\n{}\n\n", output.stdout_utf8());
201+
eprintln!("=== STDERR ===\n{}\n\n", output.stderr_utf8());
203202
std::process::exit(1)
204203
}
205204

@@ -286,6 +285,7 @@ pub fn read_dir<F: Fn(&Path)>(dir: impl AsRef<Path>, callback: F) {
286285
}
287286

288287
/// Check that `haystack` does not contain `needle`. Panic otherwise.
288+
#[track_caller]
289289
pub fn assert_not_contains(haystack: &str, needle: &str) {
290290
if haystack.contains(needle) {
291291
eprintln!("=== HAYSTACK ===");
@@ -412,28 +412,14 @@ macro_rules! impl_common_helpers {
412412

413413
/// Run the constructed command and assert that it is successfully run.
414414
#[track_caller]
415-
pub fn run(&mut self) -> ::std::process::Output {
416-
let caller_location = ::std::panic::Location::caller();
417-
let caller_line_number = caller_location.line();
418-
419-
let output = self.command_output();
420-
if !output.status.success() {
421-
handle_failed_output(&self.cmd, output, caller_line_number);
422-
}
423-
output
415+
pub fn run(&mut self) -> crate::command::CompletedProcess {
416+
self.cmd.run()
424417
}
425418

426419
/// Run the constructed command and assert that it does not successfully run.
427420
#[track_caller]
428-
pub fn run_fail(&mut self) -> ::std::process::Output {
429-
let caller_location = ::std::panic::Location::caller();
430-
let caller_line_number = caller_location.line();
431-
432-
let output = self.command_output();
433-
if output.status.success() {
434-
handle_failed_output(&self.cmd, output, caller_line_number);
435-
}
436-
output
421+
pub fn run_fail(&mut self) -> crate::command::CompletedProcess {
422+
self.cmd.run_fail()
437423
}
438424

439425
/// Set the path where the command will be run.
@@ -445,4 +431,5 @@ macro_rules! impl_common_helpers {
445431
};
446432
}
447433

434+
use crate::command::{Command, CompletedProcess};
448435
pub(crate) use impl_common_helpers;

src/tools/run-make-support/src/llvm_readobj.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::{Path, PathBuf};
2-
use std::process::Command;
32

4-
use crate::{env_var, handle_failed_output};
3+
use crate::command::Command;
4+
use crate::env_var;
55

66
/// Construct a new `llvm-readobj` invocation. This assumes that `llvm-readobj` is available
77
/// at `$LLVM_BIN_DIR/llvm-readobj`.
@@ -39,10 +39,4 @@ impl LlvmReadobj {
3939
self.cmd.arg("--file-header");
4040
self
4141
}
42-
43-
/// Get the [`Output`][::std::process::Output] of the finished process.
44-
#[track_caller]
45-
pub fn command_output(&mut self) -> ::std::process::Output {
46-
self.cmd.output().expect("failed to get output of finished process")
47-
}
4842
}

src/tools/run-make-support/src/run.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::env;
2+
use std::ffi::OsStr;
23
use std::path::{Path, PathBuf};
3-
use std::process::{Command, Output};
44

5-
use crate::{cwd, env_var, is_windows};
5+
use crate::command::{Command, CompletedProcess};
6+
use crate::{cwd, env_var, is_windows, set_host_rpath};
67

78
use super::handle_failed_output;
89

9-
fn run_common(name: &str) -> (Command, Output) {
10+
fn run_common(name: &str) -> (Command, CompletedProcess) {
1011
let mut bin_path = PathBuf::new();
1112
bin_path.push(cwd());
1213
bin_path.push(name);
@@ -33,32 +34,40 @@ fn run_common(name: &str) -> (Command, Output) {
3334
cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
3435
}
3536

36-
let output = cmd.output().unwrap();
37+
let output = cmd.command_output();
3738
(cmd, output)
3839
}
3940

4041
/// Run a built binary and make sure it succeeds.
4142
#[track_caller]
42-
pub fn run(name: &str) -> Output {
43+
pub fn run(name: &str) -> CompletedProcess {
4344
let caller_location = std::panic::Location::caller();
4445
let caller_line_number = caller_location.line();
4546

4647
let (cmd, output) = run_common(name);
47-
if !output.status.success() {
48+
if !output.status().success() {
4849
handle_failed_output(&cmd, output, caller_line_number);
4950
}
5051
output
5152
}
5253

5354
/// Run a built binary and make sure it fails.
5455
#[track_caller]
55-
pub fn run_fail(name: &str) -> Output {
56+
pub fn run_fail(name: &str) -> CompletedProcess {
5657
let caller_location = std::panic::Location::caller();
5758
let caller_line_number = caller_location.line();
5859

5960
let (cmd, output) = run_common(name);
60-
if output.status.success() {
61+
if output.status().success() {
6162
handle_failed_output(&cmd, output, caller_line_number);
6263
}
6364
output
6465
}
66+
67+
/// Create a new custom Command.
68+
/// This should be preferred to creating `std::process::Command` directly.
69+
pub fn cmd<S: AsRef<OsStr>>(program: S) -> Command {
70+
let mut command = Command::new(program);
71+
set_host_rpath(&mut command);
72+
command
73+
}

0 commit comments

Comments
 (0)