Skip to content

Commit 0e8135f

Browse files
Rollup merge of rust-lang#42363 - cuviper:no-fail-fast, r=alexcrichton
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 rust-lang#40219.
2 parents 739c5ab + 4880ae8 commit 0e8135f

File tree

5 files changed

+84
-18
lines changed

5 files changed

+84
-18
lines changed

src/bootstrap/check.rs

+34-14
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ 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+
6172
/// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler.
6273
///
6374
/// This tool in `src/tools` will verify the validity of all our links in the
@@ -67,8 +78,8 @@ pub fn linkcheck(build: &Build, host: &str) {
6778
let compiler = Compiler::new(0, host);
6879

6980
let _time = util::timeit();
70-
build.run(build.tool_cmd(&compiler, "linkchecker")
71-
.arg(build.out.join(host).join("doc")));
81+
try_run(build, build.tool_cmd(&compiler, "linkchecker")
82+
.arg(build.out.join(host).join("doc")));
7283
}
7384

7485
/// Runs the `cargotest` tool as compiled in `stage` by the `host` compiler.
@@ -87,10 +98,10 @@ pub fn cargotest(build: &Build, stage: u32, host: &str) {
8798
let _time = util::timeit();
8899
let mut cmd = Command::new(build.tool(&Compiler::new(0, host), "cargotest"));
89100
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)))
101+
try_run(build, cmd.arg(&build.cargo)
102+
.arg(&out_dir)
103+
.env("RUSTC", build.compiler_path(compiler))
104+
.env("RUSTDOC", build.rustdoc(compiler)));
94105
}
95106

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

108119
let mut cargo = build.cargo(compiler, Mode::Tool, host, "test");
109120
cargo.arg("--manifest-path").arg(build.src.join("src/tools/cargo/Cargo.toml"));
121+
if build.flags.cmd.no_fail_fast() {
122+
cargo.arg("--no-fail-fast");
123+
}
110124

111125
// Don't build tests dynamically, just a pain to work with
112126
cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");
@@ -115,7 +129,7 @@ pub fn cargo(build: &Build, stage: u32, host: &str) {
115129
// available.
116130
cargo.env("CFG_DISABLE_CROSS_TESTS", "1");
117131

118-
build.run(cargo.env("PATH", newpath));
132+
try_run(build, cargo.env("PATH", newpath));
119133
}
120134

121135
/// Runs the `tidy` tool as compiled in `stage` by the `host` compiler.
@@ -131,7 +145,7 @@ pub fn tidy(build: &Build, host: &str) {
131145
if !build.config.vendor {
132146
cmd.arg("--no-vendor");
133147
}
134-
build.run(&mut cmd);
148+
try_run(build, &mut cmd);
135149
}
136150

137151
fn testdir(build: &Build, host: &str) -> PathBuf {
@@ -279,7 +293,7 @@ pub fn compiletest(build: &Build,
279293
}
280294

281295
let _time = util::timeit();
282-
build.run(&mut cmd);
296+
try_run(build, &mut cmd);
283297
}
284298

285299
/// Run `rustdoc --test` for all documentation in `src/doc`.
@@ -355,7 +369,7 @@ fn markdown_test(build: &Build, compiler: &Compiler, markdown: &Path) {
355369
}
356370
cmd.arg("--test-args").arg(test_args);
357371

358-
build.run(&mut cmd);
372+
try_run(build, &mut cmd);
359373
}
360374

361375
/// Run all unit tests plus documentation tests for an entire crate DAG defined
@@ -406,6 +420,9 @@ pub fn krate(build: &Build,
406420
cargo.arg("--manifest-path")
407421
.arg(build.src.join(path).join("Cargo.toml"))
408422
.arg("--features").arg(features);
423+
if test_kind.subcommand() == "test" && build.flags.cmd.no_fail_fast() {
424+
cargo.arg("--no-fail-fast");
425+
}
409426

410427
match krate {
411428
Some(krate) => {
@@ -465,7 +482,7 @@ pub fn krate(build: &Build,
465482
krate_remote(build, &compiler, target, mode);
466483
} else {
467484
cargo.args(&build.flags.cmd.test_args());
468-
build.run(&mut cargo);
485+
try_run(build, &mut cargo);
469486
}
470487
}
471488

@@ -486,7 +503,7 @@ fn krate_emscripten(build: &Build,
486503
if build.config.quiet_tests {
487504
cmd.arg("--quiet");
488505
}
489-
build.run(&mut cmd);
506+
try_run(build, &mut cmd);
490507
}
491508
}
492509

@@ -508,7 +525,7 @@ fn krate_remote(build: &Build,
508525
cmd.arg("--quiet");
509526
}
510527
cmd.args(&build.flags.cmd.test_args());
511-
build.run(&mut cmd);
528+
try_run(build, &mut cmd);
512529
}
513530
}
514531

@@ -624,6 +641,9 @@ pub fn bootstrap(build: &Build) {
624641
.current_dir(build.src.join("src/bootstrap"))
625642
.env("CARGO_TARGET_DIR", build.out.join("bootstrap"))
626643
.env("RUSTC", &build.rustc);
644+
if build.flags.cmd.no_fail_fast() {
645+
cmd.arg("--no-fail-fast");
646+
}
627647
cmd.arg("--").args(&build.flags.cmd.test_args());
628-
build.run(&mut cmd);
648+
try_run(build, &mut cmd);
629649
}

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

+12-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, output, mtime};
9293

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

@@ -179,6 +180,7 @@ pub struct Build {
179180
crates: HashMap<String, Crate>,
180181
is_sudo: bool,
181182
src_is_git: bool,
183+
delayed_failures: Cell<usize>,
182184
}
183185

184186
#[derive(Debug)]
@@ -272,6 +274,7 @@ impl Build {
272274
lldb_python_dir: None,
273275
is_sudo: is_sudo,
274276
src_is_git: src_is_git,
277+
delayed_failures: Cell::new(0),
275278
}
276279
}
277280

@@ -779,6 +782,14 @@ impl Build {
779782
run_suppressed(cmd)
780783
}
781784

785+
/// Runs a command, printing out nice contextual information if it fails.
786+
/// Exits if the command failed to execute at all, otherwise returns its
787+
/// `status.success()`.
788+
fn try_run(&self, cmd: &mut Command) -> bool {
789+
self.verbose(&format!("running: {:?}", cmd));
790+
try_run_silent(cmd)
791+
}
792+
782793
/// Prints a message if this build is configured in verbose mode.
783794
fn verbose(&self, msg: &str) {
784795
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

+15
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ pub fn run_suppressed(cmd: &mut Command) {
7373
}
7474
}
7575

76+
pub fn try_run_silent(cmd: &mut Command) -> bool {
77+
let status = match cmd.status() {
78+
Ok(status) => status,
79+
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
80+
cmd, e)),
81+
};
82+
if !status.success() {
83+
println!("\n\ncommand did not execute successfully: {:?}\n\
84+
expected success, got: {}\n\n",
85+
cmd,
86+
status);
87+
}
88+
status.success()
89+
}
90+
7691
pub fn gnu_target(target: &str) -> String {
7792
match target {
7893
"i686-pc-windows-msvc" => "i686-pc-win32".to_string(),

0 commit comments

Comments
 (0)