Skip to content

Commit 617aea4

Browse files
committed
rustbuild: Add ./x.py test --no-fail-fast
This option forwards to each `cargo test` invocation, and applies the same logic across all test steps to keep going after failures. At the end, a brief summary line reports how many commands failed, if any. Note that if a test program fails to even start at all, or if an auxiliary build command related to testing fails, these are still left to stop everything right away. Fixes #40219.
1 parent d7798c3 commit 617aea4

File tree

5 files changed

+115
-31
lines changed

5 files changed

+115
-31
lines changed

src/bootstrap/check.rs

+46-15
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,28 @@ impl fmt::Display for TestKind {
5858
}
5959
}
6060

61+
fn try_run(build: &Build, cmd: &mut Command) {
62+
if build.flags.cmd.no_fail_fast() {
63+
if !build.try_run(cmd) {
64+
let failures = build.delayed_failures.get();
65+
build.delayed_failures.set(failures + 1);
66+
}
67+
} else {
68+
build.run(cmd);
69+
}
70+
}
71+
72+
fn try_run_quiet(build: &Build, cmd: &mut Command) {
73+
if build.flags.cmd.no_fail_fast() {
74+
if !build.try_run_quiet(cmd) {
75+
let failures = build.delayed_failures.get();
76+
build.delayed_failures.set(failures + 1);
77+
}
78+
} else {
79+
build.run_quiet(cmd);
80+
}
81+
}
82+
6183
/// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler.
6284
///
6385
/// This tool in `src/tools` will verify the validity of all our links in the
@@ -67,8 +89,8 @@ pub fn linkcheck(build: &Build, host: &str) {
6789
let compiler = Compiler::new(0, host);
6890

6991
let _time = util::timeit();
70-
build.run(build.tool_cmd(&compiler, "linkchecker")
71-
.arg(build.out.join(host).join("doc")));
92+
try_run(build, build.tool_cmd(&compiler, "linkchecker")
93+
.arg(build.out.join(host).join("doc")));
7294
}
7395

7496
/// Runs the `cargotest` tool as compiled in `stage` by the `host` compiler.
@@ -87,10 +109,10 @@ pub fn cargotest(build: &Build, stage: u32, host: &str) {
87109
let _time = util::timeit();
88110
let mut cmd = Command::new(build.tool(&Compiler::new(0, host), "cargotest"));
89111
build.prepare_tool_cmd(compiler, &mut cmd);
90-
build.run(cmd.arg(&build.cargo)
91-
.arg(&out_dir)
92-
.env("RUSTC", build.compiler_path(compiler))
93-
.env("RUSTDOC", build.rustdoc(compiler)))
112+
try_run(build, cmd.arg(&build.cargo)
113+
.arg(&out_dir)
114+
.env("RUSTC", build.compiler_path(compiler))
115+
.env("RUSTDOC", build.rustdoc(compiler)));
94116
}
95117

96118
/// Runs `cargo test` for `cargo` packaged with Rust.
@@ -107,6 +129,9 @@ pub fn cargo(build: &Build, stage: u32, host: &str) {
107129

108130
let mut cargo = build.cargo(compiler, Mode::Tool, host, "test");
109131
cargo.arg("--manifest-path").arg(build.src.join("src/tools/cargo/Cargo.toml"));
132+
if build.flags.cmd.no_fail_fast() {
133+
cargo.arg("--no-fail-fast");
134+
}
110135

111136
// Don't build tests dynamically, just a pain to work with
112137
cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");
@@ -115,7 +140,7 @@ pub fn cargo(build: &Build, stage: u32, host: &str) {
115140
// available.
116141
cargo.env("CFG_DISABLE_CROSS_TESTS", "1");
117142

118-
build.run(cargo.env("PATH", newpath));
143+
try_run(build, cargo.env("PATH", newpath));
119144
}
120145

121146
/// Runs the `tidy` tool as compiled in `stage` by the `host` compiler.
@@ -135,7 +160,7 @@ pub fn tidy(build: &Build, host: &str) {
135160
if build.config.quiet_tests {
136161
cmd.arg("--quiet");
137162
}
138-
build.run(&mut cmd);
163+
try_run(build, &mut cmd);
139164
}
140165

141166
fn testdir(build: &Build, host: &str) -> PathBuf {
@@ -286,7 +311,7 @@ pub fn compiletest(build: &Build,
286311
build.ci_env.force_coloring_in_ci(&mut cmd);
287312

288313
let _time = util::timeit();
289-
build.run(&mut cmd);
314+
try_run(build, &mut cmd);
290315
}
291316

292317
/// Run `rustdoc --test` for all documentation in `src/doc`.
@@ -362,9 +387,9 @@ fn markdown_test(build: &Build, compiler: &Compiler, markdown: &Path) {
362387
cmd.arg("--test-args").arg(test_args);
363388

364389
if build.config.quiet_tests {
365-
build.run_quiet(&mut cmd);
390+
try_run_quiet(build, &mut cmd);
366391
} else {
367-
build.run(&mut cmd);
392+
try_run(build, &mut cmd);
368393
}
369394
}
370395

@@ -419,6 +444,9 @@ pub fn krate(build: &Build,
419444
cargo.arg("--manifest-path")
420445
.arg(build.src.join(path).join("Cargo.toml"))
421446
.arg("--features").arg(features);
447+
if test_kind.subcommand() == "test" && build.flags.cmd.no_fail_fast() {
448+
cargo.arg("--no-fail-fast");
449+
}
422450

423451
match krate {
424452
Some(krate) => {
@@ -478,7 +506,7 @@ pub fn krate(build: &Build,
478506
krate_remote(build, &compiler, target, mode);
479507
} else {
480508
cargo.args(&build.flags.cmd.test_args());
481-
build.run(&mut cargo);
509+
try_run(build, &mut cargo);
482510
}
483511
}
484512

@@ -499,7 +527,7 @@ fn krate_emscripten(build: &Build,
499527
if build.config.quiet_tests {
500528
cmd.arg("--quiet");
501529
}
502-
build.run(&mut cmd);
530+
try_run(build, &mut cmd);
503531
}
504532
}
505533

@@ -521,7 +549,7 @@ fn krate_remote(build: &Build,
521549
cmd.arg("--quiet");
522550
}
523551
cmd.args(&build.flags.cmd.test_args());
524-
build.run(&mut cmd);
552+
try_run(build, &mut cmd);
525553
}
526554
}
527555

@@ -637,6 +665,9 @@ pub fn bootstrap(build: &Build) {
637665
.current_dir(build.src.join("src/bootstrap"))
638666
.env("CARGO_TARGET_DIR", build.out.join("bootstrap"))
639667
.env("RUSTC", &build.rustc);
668+
if build.flags.cmd.no_fail_fast() {
669+
cmd.arg("--no-fail-fast");
670+
}
640671
cmd.arg("--").args(&build.flags.cmd.test_args());
641-
build.run(&mut cmd);
672+
try_run(build, &mut cmd);
642673
}

src/bootstrap/flags.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub enum Subcommand {
6161
Test {
6262
paths: Vec<PathBuf>,
6363
test_args: Vec<String>,
64+
no_fail_fast: bool,
6465
},
6566
Bench {
6667
paths: Vec<PathBuf>,
@@ -141,7 +142,10 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`");
141142

142143
// Some subcommands get extra options
143144
match subcommand.as_str() {
144-
"test" => { opts.optmulti("", "test-args", "extra arguments", "ARGS"); },
145+
"test" => {
146+
opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
147+
opts.optmulti("", "test-args", "extra arguments", "ARGS");
148+
},
145149
"bench" => { opts.optmulti("", "test-args", "extra arguments", "ARGS"); },
146150
_ => { },
147151
};
@@ -263,6 +267,7 @@ Arguments:
263267
Subcommand::Test {
264268
paths: paths,
265269
test_args: matches.opt_strs("test-args"),
270+
no_fail_fast: matches.opt_present("no-fail-fast"),
266271
}
267272
}
268273
"bench" => {
@@ -342,6 +347,13 @@ impl Subcommand {
342347
_ => Vec::new(),
343348
}
344349
}
350+
351+
pub fn no_fail_fast(&self) -> bool {
352+
match *self {
353+
Subcommand::Test { no_fail_fast, .. } => no_fail_fast,
354+
_ => false,
355+
}
356+
}
345357
}
346358

347359
fn split(s: Vec<String>) -> Vec<String> {

src/bootstrap/lib.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ extern crate toml;
7979
#[cfg(unix)]
8080
extern crate libc;
8181

82+
use std::cell::Cell;
8283
use std::cmp;
8384
use std::collections::HashMap;
8485
use std::env;
@@ -88,7 +89,7 @@ use std::io::Read;
8889
use std::path::{PathBuf, Path};
8990
use std::process::Command;
9091

91-
use build_helper::{run_silent, run_suppressed, output, mtime};
92+
use build_helper::{run_silent, run_suppressed, try_run_silent, try_run_suppressed, output, mtime};
9293

9394
use util::{exe, libdir, add_lib_path, OutputFolder, CiEnv};
9495

@@ -180,6 +181,7 @@ pub struct Build {
180181
is_sudo: bool,
181182
src_is_git: bool,
182183
ci_env: CiEnv,
184+
delayed_failures: Cell<usize>,
183185
}
184186

185187
#[derive(Debug)]
@@ -274,6 +276,7 @@ impl Build {
274276
is_sudo: is_sudo,
275277
src_is_git: src_is_git,
276278
ci_env: CiEnv::current(),
279+
delayed_failures: Cell::new(0),
277280
}
278281
}
279282

@@ -784,6 +787,22 @@ impl Build {
784787
run_suppressed(cmd)
785788
}
786789

790+
/// Runs a command, printing out nice contextual information if it fails.
791+
/// Exits if the command failed to execute at all, otherwise returns its
792+
/// `status.success()`.
793+
fn try_run(&self, cmd: &mut Command) -> bool {
794+
self.verbose(&format!("running: {:?}", cmd));
795+
try_run_silent(cmd)
796+
}
797+
798+
/// Runs a command, printing out nice contextual information if it fails.
799+
/// Exits if the command failed to execute at all, otherwise returns its
800+
/// `status.success()`.
801+
fn try_run_quiet(&self, cmd: &mut Command) -> bool {
802+
self.verbose(&format!("running: {:?}", cmd));
803+
try_run_suppressed(cmd)
804+
}
805+
787806
/// Prints a message if this build is configured in verbose mode.
788807
fn verbose(&self, msg: &str) {
789808
if self.flags.verbose() || self.config.verbose() {

src/bootstrap/step.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
2929
use std::collections::{BTreeMap, HashSet, HashMap};
3030
use std::mem;
31+
use std::process;
3132

3233
use check::{self, TestKind};
3334
use compile;
@@ -1174,8 +1175,8 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
11741175
let (kind, paths) = match self.build.flags.cmd {
11751176
Subcommand::Build { ref paths } => (Kind::Build, &paths[..]),
11761177
Subcommand::Doc { ref paths } => (Kind::Doc, &paths[..]),
1177-
Subcommand::Test { ref paths, test_args: _ } => (Kind::Test, &paths[..]),
1178-
Subcommand::Bench { ref paths, test_args: _ } => (Kind::Bench, &paths[..]),
1178+
Subcommand::Test { ref paths, .. } => (Kind::Test, &paths[..]),
1179+
Subcommand::Bench { ref paths, .. } => (Kind::Bench, &paths[..]),
11791180
Subcommand::Dist { ref paths } => (Kind::Dist, &paths[..]),
11801181
Subcommand::Install { ref paths } => (Kind::Install, &paths[..]),
11811182
Subcommand::Clean => panic!(),
@@ -1268,6 +1269,13 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
12681269
self.build.verbose(&format!("executing step {:?}", step));
12691270
(self.rules[step.name].run)(step);
12701271
}
1272+
1273+
// Check for postponed failures from `test --no-fail-fast`.
1274+
let failures = self.build.delayed_failures.get();
1275+
if failures > 0 {
1276+
println!("\n{} command(s) did not execute successfully.\n", failures);
1277+
process::exit(1);
1278+
}
12711279
}
12721280

12731281
/// From the top level targets `steps` generate a topological ordering of

src/build_helper/lib.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -42,35 +42,49 @@ pub fn run(cmd: &mut Command) {
4242
}
4343

4444
pub fn run_silent(cmd: &mut Command) {
45+
if !try_run_silent(cmd) {
46+
std::process::exit(1);
47+
}
48+
}
49+
50+
pub fn try_run_silent(cmd: &mut Command) -> bool {
4551
let status = match cmd.status() {
4652
Ok(status) => status,
4753
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
4854
cmd, e)),
4955
};
5056
if !status.success() {
51-
fail(&format!("command did not execute successfully: {:?}\n\
52-
expected success, got: {}",
53-
cmd,
54-
status));
57+
println!("\n\ncommand did not execute successfully: {:?}\n\
58+
expected success, got: {}\n\n",
59+
cmd,
60+
status);
5561
}
62+
status.success()
5663
}
5764

5865
pub fn run_suppressed(cmd: &mut Command) {
66+
if !try_run_suppressed(cmd) {
67+
std::process::exit(1);
68+
}
69+
}
70+
71+
pub fn try_run_suppressed(cmd: &mut Command) -> bool {
5972
let output = match cmd.output() {
6073
Ok(status) => status,
6174
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
6275
cmd, e)),
6376
};
6477
if !output.status.success() {
65-
fail(&format!("command did not execute successfully: {:?}\n\
66-
expected success, got: {}\n\n\
67-
stdout ----\n{}\n\
68-
stderr ----\n{}\n",
69-
cmd,
70-
output.status,
71-
String::from_utf8_lossy(&output.stdout),
72-
String::from_utf8_lossy(&output.stderr)));
78+
println!("\n\ncommand did not execute successfully: {:?}\n\
79+
expected success, got: {}\n\n\
80+
stdout ----\n{}\n\
81+
stderr ----\n{}\n\n",
82+
cmd,
83+
output.status,
84+
String::from_utf8_lossy(&output.stdout),
85+
String::from_utf8_lossy(&output.stderr));
7386
}
87+
output.status.success()
7488
}
7589

7690
pub fn gnu_target(target: &str) -> String {

0 commit comments

Comments
 (0)