diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 991592ec522d0..2addfa1f2b8e2 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -21,6 +21,7 @@ use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; use crate::core::build_steps::test::compiletest::CompiletestMode; +use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; use crate::core::build_steps::tool::{ self, RustcPrivateCompilers, SourceType, TEST_FLOAT_PARSE_ALLOW_FEATURES, Tool, ToolTargetBuildMode, get_tool_target_compiler, @@ -45,6 +46,7 @@ use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; use crate::{CLang, CodegenBackendKind, GitRepo, Mode, PathSet, TestTarget, envify}; mod compiletest; +pub mod failed_tests; /// Runs `cargo test` on various internal tools used by bootstrap. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -86,6 +88,7 @@ impl Step for CrateBootstrap { fn run(self, builder: &Builder<'_>) { let bootstrap_host = builder.config.host_target; let compiler = builder.compiler(0, bootstrap_host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut path = self.path.to_str().unwrap(); // Map alias `tidyselftest` back to the actual crate path of tidy. @@ -105,7 +108,7 @@ impl Step for CrateBootstrap { ); let crate_name = path.rsplit_once('/').unwrap().1; - run_cargo_test(cargo, &[], &[], crate_name, bootstrap_host, builder); + run_cargo_test(cargo, &[], &[], crate_name, bootstrap_host, builder, record_failed_tests); } fn metadata(&self) -> Option { @@ -162,6 +165,7 @@ You can skip linkcheck with --skip src/tools/linkchecker" // Test the linkchecker itself. let bootstrap_host = builder.config.host_target; let compiler = builder.compiler(0, bootstrap_host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let cargo = tool::prepare_tool_cargo( builder, @@ -173,7 +177,15 @@ You can skip linkcheck with --skip src/tools/linkchecker" SourceType::InTree, &[], ); - run_cargo_test(cargo, &[], &[], "linkchecker self tests", bootstrap_host, builder); + run_cargo_test( + cargo, + &[], + &[], + "linkchecker self tests", + bootstrap_host, + builder, + record_failed_tests, + ); if !builder.test_target.runs_doctests() { return; @@ -365,6 +377,7 @@ impl Step for Cargo { // using stage 1 cargo. So we actually build cargo using the stage 0 compiler, and then // run its tests against the stage 1 compiler (called `tested_compiler` below). builder.ensure(tool::Cargo::from_build_compiler(self.build_compiler, self.host)); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let tested_compiler = builder.compiler(self.build_compiler.stage + 1, self.host); builder.std(tested_compiler, self.host); @@ -429,7 +442,7 @@ impl Step for Cargo { ); let _time = helpers::timeit(builder); - add_flags_and_try_run_tests(builder, &mut cargo); + add_flags_and_try_run_tests(builder, &mut cargo, record_failed_tests); } fn metadata(&self) -> Option { @@ -468,6 +481,7 @@ impl Step for RustAnalyzer { fn run(self, builder: &Builder<'_>) { let build_compiler = self.compilers.build_compiler(); let target = self.compilers.target(); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); // NOTE: rust-analyzer repo currently (as of 2025-12-11) does not run tests against 32-bit // targets, so we also don't run them in rust-lang/rust CI (because that will just mean that @@ -482,13 +496,14 @@ impl Step for RustAnalyzer { return; } + let suite = "src/tools/rust-analyzer"; let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, Mode::ToolRustcPrivate, target, Kind::Test, - "src/tools/rust-analyzer", + suite, SourceType::InTree, &["in-rust-tree".to_owned()], ); @@ -539,7 +554,15 @@ impl Step for RustAnalyzer { let skip_tests = skip_tests.iter().map(|s| s.as_str()).collect::>(); cargo.add_rustc_lib_path(builder); - run_cargo_test(cargo, skip_tests.as_slice(), &[], "rust-analyzer", target, builder); + run_cargo_test( + cargo, + skip_tests.as_slice(), + &[], + "rust-analyzer", + target, + builder, + record_failed_tests, + ); } fn metadata(&self) -> Option { @@ -578,6 +601,7 @@ impl Step for Rustfmt { fn run(self, builder: &Builder<'_>) { let build_compiler = self.compilers.build_compiler(); let target = self.compilers.target(); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut cargo = tool::prepare_tool_cargo( builder, @@ -596,7 +620,7 @@ impl Step for Rustfmt { cargo.add_rustc_lib_path(builder); - run_cargo_test(cargo, &[], &[], "rustfmt", target, builder); + run_cargo_test(cargo, &[], &[], "rustfmt", target, builder, record_failed_tests); } fn metadata(&self) -> Option { @@ -867,6 +891,7 @@ impl Step for CompiletestTest { /// Runs `cargo test` for compiletest. fn run(self, builder: &Builder<'_>) { let host = self.host; + let record_failed_tests = builder.ensure(SetupFailedTestsFile); // Now that compiletest uses only stable Rust, building it always uses // the stage 0 compiler. However, some of its unit tests need to be able @@ -900,7 +925,15 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // format, namely that of the staged compiler. cargo.env("TEST_RUSTC", builder.rustc(staged_compiler)); - run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder); + run_cargo_test( + cargo, + &[], + &[], + "compiletest self test", + host, + builder, + record_failed_tests, + ); } } @@ -1240,6 +1273,7 @@ impl Step for RustdocGUI { fn run(self, builder: &Builder<'_>) { builder.std(self.test_compiler, self.target); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut cmd = builder.tool_cmd(Tool::RustdocGUITest); @@ -1308,7 +1342,7 @@ impl Step for RustdocGUI { let _time = helpers::timeit(builder); let _guard = builder.msg_test("rustdoc-gui", self.target, self.test_compiler.stage); - try_run_tests(builder, &mut cmd, true); + try_run_tests(builder, &mut cmd, true, record_failed_tests); } fn metadata(&self) -> Option { @@ -1476,6 +1510,7 @@ impl Step for CrateRunMakeSupport { fn run(self, builder: &Builder<'_>) { let host = self.host; let compiler = builder.compiler(0, host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut cargo = tool::prepare_tool_cargo( builder, @@ -1488,7 +1523,15 @@ impl Step for CrateRunMakeSupport { &[], ); cargo.allow_features("test"); - run_cargo_test(cargo, &[], &[], "run-make-support self test", host, builder); + run_cargo_test( + cargo, + &[], + &[], + "run-make-support self test", + host, + builder, + record_failed_tests, + ); } } @@ -1513,6 +1556,7 @@ impl Step for CrateBuildHelper { fn run(self, builder: &Builder<'_>) { let host = self.host; let compiler = builder.compiler(0, host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut cargo = tool::prepare_tool_cargo( builder, @@ -1525,7 +1569,15 @@ impl Step for CrateBuildHelper { &[], ); cargo.allow_features("test"); - run_cargo_test(cargo, &[], &[], "build_helper self test", host, builder); + run_cargo_test( + cargo, + &[], + &[], + "build_helper self test", + host, + builder, + record_failed_tests, + ); } } @@ -1908,6 +1960,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let target = self.target; let mode = self.mode; let suite = self.suite; + let record_failed_tests = builder.ensure(SetupFailedTestsFile); // Path for test suite let suite_path = self.path; @@ -2556,7 +2609,7 @@ Please disable assertions with `rust.debug-assertions = false`. target, test_compiler.stage, ); - try_run_tests(builder, &mut cmd, false); + try_run_tests(builder, &mut cmd, false, record_failed_tests.clone()); if let Some(compare_mode) = compare_mode { cmd.arg("--compare-mode").arg(compare_mode); @@ -2579,7 +2632,7 @@ Please disable assertions with `rust.debug-assertions = false`. suite, mode, compare_mode, &test_compiler.host, target )); let _time = helpers::timeit(builder); - try_run_tests(builder, &mut cmd, false); + try_run_tests(builder, &mut cmd, false, record_failed_tests); } } @@ -2971,6 +3024,7 @@ fn run_cargo_test<'a>( description: impl Into>, target: TargetSelection, builder: &Builder<'_>, + record_failed_tests: RecordFailedTests, ) -> bool { let compiler = cargo.compiler(); let stage = match cargo.mode() { @@ -2993,7 +3047,7 @@ fn run_cargo_test<'a>( }, builder, ); - add_flags_and_try_run_tests(builder, &mut cargo) + add_flags_and_try_run_tests(builder, &mut cargo, record_failed_tests) } /// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`. @@ -3123,6 +3177,7 @@ impl Step for Crate { // Prepare sysroot // See [field@compile::Std::force_recompile]. builder.ensure(Std::new(build_compiler, build_compiler.host).force_recompile(true)); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let mut cargo = if builder.kind == Kind::Miri { if builder.top_stage == 0 { @@ -3217,9 +3272,9 @@ impl Step for Crate { } if crates.iter().any(|crate_| crate_ == "alloc") { crates.push("alloctests".to_owned()); - } - - run_cargo_test(cargo, &[], &crates, &*crate_description(&self.crates), target, builder); + }; + let description = crate_description(&self.crates); + run_cargo_test(cargo, &[], &crates, &*description, target, builder, record_failed_tests); } } @@ -3250,6 +3305,7 @@ impl Step for CrateRustdoc { fn run(self, builder: &Builder<'_>) { let target = self.host; + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let compiler = if builder.download_rustc() { builder.compiler(builder.top_stage, target) @@ -3316,7 +3372,15 @@ impl Step for CrateRustdoc { dylib_path.insert(0, PathBuf::from(&*libdir)); cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); - run_cargo_test(cargo, &[], &["rustdoc:0.0.0".to_string()], "rustdoc", target, builder); + run_cargo_test( + cargo, + &[], + &["rustdoc:0.0.0".to_string()], + "rustdoc", + target, + builder, + record_failed_tests, + ); } } @@ -3352,6 +3416,7 @@ impl Step for CrateRustdocJsonTypes { fn run(self, builder: &Builder<'_>) { let target = self.target; + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let cargo = tool::prepare_tool_cargo( builder, @@ -3378,6 +3443,7 @@ impl Step for CrateRustdocJsonTypes { "rustdoc-json-types", target, builder, + record_failed_tests, ); } } @@ -3624,6 +3690,7 @@ impl Step for Bootstrap { fn run(self, builder: &Builder<'_>) { let host = builder.config.host_target; let build_compiler = builder.compiler(0, host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); // Some tests require cargo submodule to be present. builder.build.require_submodule("src/tools/cargo", None); @@ -3655,7 +3722,7 @@ impl Step for Bootstrap { cargo.env("INSTA_UPDATE", "always"); } - run_cargo_test(cargo, &[], &[], None, host, builder); + run_cargo_test(cargo, &[], &[], None, host, builder, record_failed_tests); } fn make_run(run: RunConfig<'_>) { @@ -3792,6 +3859,7 @@ impl Step for RustInstaller { fn run(self, builder: &Builder<'_>) { let bootstrap_host = builder.config.host_target; let build_compiler = builder.compiler(0, bootstrap_host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let cargo = tool::prepare_tool_cargo( builder, build_compiler, @@ -3804,7 +3872,7 @@ impl Step for RustInstaller { ); let _guard = builder.msg_test("rust-installer", bootstrap_host, 1); - run_cargo_test(cargo, &[], &[], None, bootstrap_host, builder); + run_cargo_test(cargo, &[], &[], None, bootstrap_host, builder, record_failed_tests); // We currently don't support running the test.sh script outside linux(?) environments. // Eventually this should likely migrate to #[test]s in rust-installer proper rather than a @@ -4171,6 +4239,7 @@ impl Step for TestFloatParse { // Build the standard library that will be tested, and a stdlib for host code builder.std(build_compiler, target); builder.std(build_compiler, builder.host_target); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); // Run any unit tests in the crate let mut cargo_test = tool::prepare_tool_cargo( @@ -4185,7 +4254,15 @@ impl Step for TestFloatParse { ); cargo_test.allow_features(TEST_FLOAT_PARSE_ALLOW_FEATURES); - run_cargo_test(cargo_test, &[], &[], "test-float-parse", target, builder); + run_cargo_test( + cargo_test, + &[], + &[], + "test-float-parse", + target, + builder, + record_failed_tests, + ); // Run the actual parse tests. let mut cargo_run = tool::prepare_tool_cargo( @@ -4267,6 +4344,7 @@ impl Step for RemoteTestClientTests { fn run(self, builder: &Builder<'_>) { let bootstrap_host = builder.config.host_target; let compiler = builder.compiler(0, bootstrap_host); + let record_failed_tests = builder.ensure(SetupFailedTestsFile); let cargo = tool::prepare_tool_cargo( builder, @@ -4279,6 +4357,14 @@ impl Step for RemoteTestClientTests { &[], ); - run_cargo_test(cargo, &[], &[], "remote-test-client", bootstrap_host, builder); + run_cargo_test( + cargo, + &[], + &[], + "remote-test-client", + bootstrap_host, + builder, + record_failed_tests, + ); } } diff --git a/src/bootstrap/src/core/build_steps/test/failed_tests.rs b/src/bootstrap/src/core/build_steps/test/failed_tests.rs new file mode 100644 index 0000000000000..8be6e50fd3db1 --- /dev/null +++ b/src/bootstrap/src/core/build_steps/test/failed_tests.rs @@ -0,0 +1,110 @@ +use std::collections::BTreeSet; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, ErrorKind}; +use std::path::{Path, PathBuf}; + +use crate::core::builder::{Builder, ShouldRun, Step}; +use crate::t; + +#[derive(Clone)] +pub struct RecordFailedTests { + failed_tests_path: Option, +} + +impl RecordFailedTests { + pub fn path(&self) -> Option<&Path> { + self.failed_tests_path.as_deref() + } +} + +/// This step is run as a dependency of most testing steps. +/// Upon running, a file is created for failed tests to be recorded in if `--record` is passed on +/// the command line. +/// +/// This step is the only way to get access to a token type called [`RecordFailedTests`]. +/// Having this token type signifies the fact that a file was created to store failed tests in, +/// and is required to create a `Renderer`, the type that renders the outputs of tests. +/// +/// If `--rerun` isn't passed, or we're in dry-run mode, running this step is a no-op, +/// and the `RecordFailedTest` type doesn't (need to) signify anything. +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] +pub struct SetupFailedTestsFile; +impl Step for SetupFailedTestsFile { + type Output = RecordFailedTests; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.never() + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + if !builder.config.cmd.record() || builder.config.dry_run() { + return RecordFailedTests { failed_tests_path: None }; + } + + let failed_tests_path = builder.config.record_failed_tests_path.clone(); + println!( + "setting up tracking of failed tests in {} (`--record` was passed)", + failed_tests_path.display() + ); + if failed_tests_path.exists() { + println!("deleting previously recorded failed tests"); + t!(fs::remove_file(&failed_tests_path)); + } + RecordFailedTests { failed_tests_path: Some(failed_tests_path) } + } +} + +pub fn collect_previously_failed_tests(failed_tests_file_path: &PathBuf) -> Vec { + let mut paths = BTreeSet::new(); + + println!( + "`--rerun` passed so looking for failed tests in {}", + failed_tests_file_path.display() + ); + + let lines: Vec = match File::open(failed_tests_file_path) { + Ok(f) => t!(BufReader::new(f).lines().collect()), + Err(e) if e.kind() == ErrorKind::NotFound => { + println!( + "WARNING: failed tests file doesn't exist: `--rerun` only makes sense after a previous test run with `--record`" + ); + return Vec::new(); + } + Err(e) => t!(Err(e)), + }; + + const MAX_RERUN_PRINTS: usize = 10; + + for line in lines { + let trimmed = line.as_str().trim(); + let without_revision = + trimmed.rsplit_once("#").map(|(before, _)| before).unwrap_or(trimmed); + let without_suite_prefix = without_revision + .strip_prefix("[") + .and_then(|rest| rest.split_once("]")) + .map(|(_, after)| after.trim()) + .unwrap_or(without_revision); + + let failed_test_path = PathBuf::from(without_suite_prefix.to_string()); + if paths.insert(failed_test_path.clone()) { + if paths.len() == 1 { + println!("rerunning previously failed tests:"); + } + if paths.len() <= MAX_RERUN_PRINTS { + println!(" {}", failed_test_path.display()); + } + } + } + + if paths.len() > MAX_RERUN_PRINTS { + println!(" and {} more...", paths.len() - MAX_RERUN_PRINTS) + } + + if paths.is_empty() { + println!( + "WARNING: failed tests file doesn't contain any failed tests: `--rerun` only makes sense after a previous test run with `--record`" + ); + } + + paths.into_iter().collect() +} diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index b933e668c26ae..5a9c7264c006f 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -30,6 +30,7 @@ use tracing::{instrument, span}; use crate::core::build_steps::llvm; use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS; +use crate::core::build_steps::test::failed_tests::collect_previously_failed_tests; pub use crate::core::config::flags::Subcommand; use crate::core::config::flags::{Color, Flags, Warnings}; use crate::core::config::target_selection::TargetSelectionList; @@ -125,6 +126,7 @@ pub struct Config { pub stage0_metadata: build_helper::stage0_parser::Stage0, pub android_ndk: Option, pub optimized_compiler_builtins: CompilerBuiltins, + pub record_failed_tests_path: PathBuf, pub stdout_is_tty: bool, pub stderr_is_tty: bool, @@ -507,6 +509,7 @@ impl Config { dist_stage: build_dist_stage, bench_stage: build_bench_stage, patch_binaries_for_nix: build_patch_binaries_for_nix, + record_failed_tests_path: build_record_failed_tests_path, // This field is only used by bootstrap.py metrics: _, android_ndk: build_android_ndk, @@ -1305,6 +1308,19 @@ impl Config { ); let verbose_tests = rust_verbose_tests.unwrap_or(exec_ctx.is_verbose()); + let record_failed_tests_path = + out.join(build_record_failed_tests_path.unwrap_or_else(|| "failed-tests".to_string())); + + let paths = { + let mut paths = Vec::new(); + if flags_cmd.rerun() { + paths = collect_previously_failed_tests(&record_failed_tests_path); + } else { + paths.extend(flags_paths); + } + paths + }; + Config { // tidy-alphabetical-start android_ndk: build_android_ndk, @@ -1435,13 +1451,14 @@ impl Config { out, patch_binaries_for_nix: build_patch_binaries_for_nix, path_modification_cache, - paths: flags_paths, + paths, prefix: install_prefix.map(PathBuf::from), print_step_rusage: build_print_step_rusage.unwrap_or(false), print_step_timings: build_print_step_timings.unwrap_or(false), profiler: build_profiler.unwrap_or(false), python: build_python.map(PathBuf::from), quiet: flags_quiet, + record_failed_tests_path, reproducible_artifacts: flags_reproducible_artifact, reuse: build_reuse.map(PathBuf::from), rust_analyzer_info, diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index dce8a4c09a7e4..bf171f1de34e0 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -440,6 +440,15 @@ pub enum Subcommand { #[arg(long)] #[doc(hidden)] no_doc: bool, + + /// Record all the failed tests in a file in the build directory. + /// + /// On subsequent invocations, this set of tests can be rerun by passing `--rerun` + #[arg(long)] + record: bool, + /// Rerun tests that previously failed, and stored with `--record`. + #[arg(long)] + rerun: bool, }, /// Build and run some test suites *in Miri* Miri { @@ -723,6 +732,20 @@ impl Subcommand { _ => false, } } + + pub fn record(&self) -> bool { + match self { + Subcommand::Test { record, .. } => *record, + _ => false, + } + } + + pub fn rerun(&self) -> bool { + match self { + Subcommand::Test { rerun, .. } => *rerun, + _ => false, + } + } } /// Returns the shell completion for a given shell, if the result differs from the current diff --git a/src/bootstrap/src/core/config/toml/build.rs b/src/bootstrap/src/core/config/toml/build.rs index 27bf753f6914d..ed20a2958e382 100644 --- a/src/bootstrap/src/core/config/toml/build.rs +++ b/src/bootstrap/src/core/config/toml/build.rs @@ -78,6 +78,7 @@ define_config! { tidy_extra_checks: Option = "tidy-extra-checks", ccache: Option = "ccache", exclude: Option> = "exclude", + record_failed_tests_path: Option = "record_failed_tests_path", } } diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 331403f959b4c..566ad003ebe01 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -621,6 +621,11 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "`x.py` stopped accepting partial argument names. Use full names to avoid errors.", }, + ChangeInfo { + change_id: 154586, + severity: ChangeSeverity::Info, + summary: "New option `build.record_failed_tests_path` to store failed tests when passing `--record`. These can be rerun with `--rerun`.", + }, ChangeInfo { change_id: 154587, severity: ChangeSeverity::Info, diff --git a/src/bootstrap/src/utils/render_tests.rs b/src/bootstrap/src/utils/render_tests.rs index 1d133a9c9e2f3..dbcbb372b6ea0 100644 --- a/src/bootstrap/src/utils/render_tests.rs +++ b/src/bootstrap/src/utils/render_tests.rs @@ -6,12 +6,14 @@ //! and rustc) libtest doesn't include the rendered human-readable output as a JSON field. We had //! to reimplement all the rendering logic in this module because of that. +use std::fs::File; use std::io::{BufRead, BufReader, Read, Write}; use std::process::ChildStdout; use std::time::Duration; use termcolor::{Color, ColorSpec, WriteColor}; +use crate::core::build_steps::test::failed_tests::RecordFailedTests; use crate::core::builder::Builder; use crate::utils::exec::BootstrapCommand; @@ -20,21 +22,23 @@ const TERSE_TESTS_PER_LINE: usize = 88; pub(crate) fn add_flags_and_try_run_tests( builder: &Builder<'_>, cmd: &mut BootstrapCommand, + record_failed_tests: RecordFailedTests, ) -> bool { if !cmd.get_args().any(|arg| arg == "--") { cmd.arg("--"); } cmd.args(["-Z", "unstable-options", "--format", "json"]); - try_run_tests(builder, cmd, false) + try_run_tests(builder, cmd, false, record_failed_tests) } pub(crate) fn try_run_tests( builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool, + record_failed_tests: RecordFailedTests, ) -> bool { - if run_tests(builder, cmd, stream) { + if run_tests(builder, cmd, stream, record_failed_tests) { return true; } @@ -47,7 +51,12 @@ pub(crate) fn try_run_tests( false } -fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> bool { +fn run_tests( + builder: &Builder<'_>, + cmd: &mut BootstrapCommand, + stream: bool, + record_failed_tests: RecordFailedTests, +) -> bool { builder.do_if_verbose(|| println!("running: {cmd:?}")); let Some(mut streaming_command) = cmd.stream_capture_stdout(&builder.config.exec_ctx) else { @@ -56,7 +65,8 @@ fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> // This runs until the stdout of the child is closed, which means the child exited. We don't // run this on another thread since the builder is not Sync. - let renderer = Renderer::new(streaming_command.stdout.take().unwrap(), builder); + let renderer = + Renderer::new(streaming_command.stdout.take().unwrap(), builder, record_failed_tests); if stream { renderer.stream_all(); } else { @@ -87,10 +97,30 @@ struct Renderer<'a> { ignored_tests: usize, terse_tests_in_line: usize, ci_latest_logged_percentage: f64, + + failed_tests: Option, } impl<'a> Renderer<'a> { - fn new(stdout: ChildStdout, builder: &'a Builder<'a>) -> Self { + fn new( + stdout: ChildStdout, + builder: &'a Builder<'a>, + record_failed_tests: RecordFailedTests, + ) -> Self { + let failed_tests = record_failed_tests.path().and_then(|path| { + // create the file (overwriting any previous) to get ready to record new failed tests + match File::options().create(true).append(true).truncate(false).open(path) { + Ok(f) => Some(f), + Err(e) => { + println!( + "Couldn't open file {} to write test failutes to: {e}. (attempted because `--record` was passed). Test failures will not be recorded.", + path.display() + ); + None + } + } + }); + Self { stdout: BufReader::new(stdout), benches: Vec::new(), @@ -102,6 +132,7 @@ impl<'a> Renderer<'a> { ignored_tests: 0, terse_tests_in_line: 0, ci_latest_logged_percentage: 0.0, + failed_tests, } } @@ -268,6 +299,13 @@ impl<'a> Renderer<'a> { for failure in &self.failures { println!(" {}", failure.name); } + + if self.failed_tests.is_some() { + println!( + "This list of test failures was recorded.\nUse `x test --rerun` to retry just these {} failed tests.", + self.failures.len(), + ) + } } if !self.benches.is_empty() { @@ -360,6 +398,13 @@ impl<'a> Renderer<'a> { } Message::Test(TestMessage::Failed(outcome)) => { self.render_test_outcome(Outcome::Failed, &outcome); + if let Some(failed_tests) = &mut self.failed_tests + && let Err(e) = writeln!(failed_tests, "{}", outcome.name) + { + eprintln!( + "failed to write test failure to file: {e} (attempted because `--record` was passed)" + ); + } self.failures.push(outcome); } Message::Test(TestMessage::Timeout { name }) => { diff --git a/src/etc/completions/x.fish b/src/etc/completions/x.fish index 0b50ee19f8a57..4d40b28414677 100644 --- a/src/etc/completions/x.fish +++ b/src/etc/completions/x.fish @@ -465,6 +465,8 @@ complete -c x -n "__fish_x_using_subcommand test" -l rustfix-coverage -d 'enable complete -c x -n "__fish_x_using_subcommand test" -l no-capture -d 'don\'t capture stdout/stderr of tests' complete -c x -n "__fish_x_using_subcommand test" -l bypass-ignore-backends -d 'Ignore `//@ ignore-backends` directives' complete -c x -n "__fish_x_using_subcommand test" -l no-doc -d 'Deprecated. Use `--all-targets` or `--tests` instead' +complete -c x -n "__fish_x_using_subcommand test" -l record -d 'Record all the failed tests in a file in the build directory' +complete -c x -n "__fish_x_using_subcommand test" -l rerun -d 'Rerun tests that previously failed, and stored with `--record`' complete -c x -n "__fish_x_using_subcommand test" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x -n "__fish_x_using_subcommand test" -s q -l quiet -d 'use quiet output' complete -c x -n "__fish_x_using_subcommand test" -s i -l incremental -d 'use incremental compilation' @@ -520,6 +522,8 @@ complete -c x -n "__fish_x_using_subcommand t" -l rustfix-coverage -d 'enable th complete -c x -n "__fish_x_using_subcommand t" -l no-capture -d 'don\'t capture stdout/stderr of tests' complete -c x -n "__fish_x_using_subcommand t" -l bypass-ignore-backends -d 'Ignore `//@ ignore-backends` directives' complete -c x -n "__fish_x_using_subcommand t" -l no-doc -d 'Deprecated. Use `--all-targets` or `--tests` instead' +complete -c x -n "__fish_x_using_subcommand t" -l record -d 'Record all the failed tests in a file in the build directory' +complete -c x -n "__fish_x_using_subcommand t" -l rerun -d 'Rerun tests that previously failed, and stored with `--record`' complete -c x -n "__fish_x_using_subcommand t" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x -n "__fish_x_using_subcommand t" -s q -l quiet -d 'use quiet output' complete -c x -n "__fish_x_using_subcommand t" -s i -l incremental -d 'use incremental compilation' diff --git a/src/etc/completions/x.ps1 b/src/etc/completions/x.ps1 index e03994f4acaf0..f9bebb22b916f 100644 --- a/src/etc/completions/x.ps1 +++ b/src/etc/completions/x.ps1 @@ -543,6 +543,8 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock { [CompletionResult]::new('--no-capture', '--no-capture', [CompletionResultType]::ParameterName, 'don''t capture stdout/stderr of tests') [CompletionResult]::new('--bypass-ignore-backends', '--bypass-ignore-backends', [CompletionResultType]::ParameterName, 'Ignore `//@ ignore-backends` directives') [CompletionResult]::new('--no-doc', '--no-doc', [CompletionResultType]::ParameterName, 'Deprecated. Use `--all-targets` or `--tests` instead') + [CompletionResult]::new('--record', '--record', [CompletionResultType]::ParameterName, 'Record all the failed tests in a file in the build directory') + [CompletionResult]::new('--rerun', '--rerun', [CompletionResultType]::ParameterName, 'Rerun tests that previously failed, and stored with `--record`') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'use quiet output') @@ -606,6 +608,8 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock { [CompletionResult]::new('--no-capture', '--no-capture', [CompletionResultType]::ParameterName, 'don''t capture stdout/stderr of tests') [CompletionResult]::new('--bypass-ignore-backends', '--bypass-ignore-backends', [CompletionResultType]::ParameterName, 'Ignore `//@ ignore-backends` directives') [CompletionResult]::new('--no-doc', '--no-doc', [CompletionResultType]::ParameterName, 'Deprecated. Use `--all-targets` or `--tests` instead') + [CompletionResult]::new('--record', '--record', [CompletionResultType]::ParameterName, 'Record all the failed tests in a file in the build directory') + [CompletionResult]::new('--rerun', '--rerun', [CompletionResultType]::ParameterName, 'Rerun tests that previously failed, and stored with `--record`') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'use quiet output') diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish index 8e3bec4fdd925..b098de6e7daff 100644 --- a/src/etc/completions/x.py.fish +++ b/src/etc/completions/x.py.fish @@ -465,6 +465,8 @@ complete -c x.py -n "__fish_x.py_using_subcommand test" -l rustfix-coverage -d ' complete -c x.py -n "__fish_x.py_using_subcommand test" -l no-capture -d 'don\'t capture stdout/stderr of tests' complete -c x.py -n "__fish_x.py_using_subcommand test" -l bypass-ignore-backends -d 'Ignore `//@ ignore-backends` directives' complete -c x.py -n "__fish_x.py_using_subcommand test" -l no-doc -d 'Deprecated. Use `--all-targets` or `--tests` instead' +complete -c x.py -n "__fish_x.py_using_subcommand test" -l record -d 'Record all the failed tests in a file in the build directory' +complete -c x.py -n "__fish_x.py_using_subcommand test" -l rerun -d 'Rerun tests that previously failed, and stored with `--record`' complete -c x.py -n "__fish_x.py_using_subcommand test" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_x.py_using_subcommand test" -s q -l quiet -d 'use quiet output' complete -c x.py -n "__fish_x.py_using_subcommand test" -s i -l incremental -d 'use incremental compilation' @@ -520,6 +522,8 @@ complete -c x.py -n "__fish_x.py_using_subcommand t" -l rustfix-coverage -d 'ena complete -c x.py -n "__fish_x.py_using_subcommand t" -l no-capture -d 'don\'t capture stdout/stderr of tests' complete -c x.py -n "__fish_x.py_using_subcommand t" -l bypass-ignore-backends -d 'Ignore `//@ ignore-backends` directives' complete -c x.py -n "__fish_x.py_using_subcommand t" -l no-doc -d 'Deprecated. Use `--all-targets` or `--tests` instead' +complete -c x.py -n "__fish_x.py_using_subcommand t" -l record -d 'Record all the failed tests in a file in the build directory' +complete -c x.py -n "__fish_x.py_using_subcommand t" -l rerun -d 'Rerun tests that previously failed, and stored with `--record`' complete -c x.py -n "__fish_x.py_using_subcommand t" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_x.py_using_subcommand t" -s q -l quiet -d 'use quiet output' complete -c x.py -n "__fish_x.py_using_subcommand t" -s i -l incremental -d 'use incremental compilation' diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1 index edcc5d0bb5ef1..6c952de7eaac9 100644 --- a/src/etc/completions/x.py.ps1 +++ b/src/etc/completions/x.py.ps1 @@ -543,6 +543,8 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--no-capture', '--no-capture', [CompletionResultType]::ParameterName, 'don''t capture stdout/stderr of tests') [CompletionResult]::new('--bypass-ignore-backends', '--bypass-ignore-backends', [CompletionResultType]::ParameterName, 'Ignore `//@ ignore-backends` directives') [CompletionResult]::new('--no-doc', '--no-doc', [CompletionResultType]::ParameterName, 'Deprecated. Use `--all-targets` or `--tests` instead') + [CompletionResult]::new('--record', '--record', [CompletionResultType]::ParameterName, 'Record all the failed tests in a file in the build directory') + [CompletionResult]::new('--rerun', '--rerun', [CompletionResultType]::ParameterName, 'Rerun tests that previously failed, and stored with `--record`') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'use quiet output') @@ -606,6 +608,8 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--no-capture', '--no-capture', [CompletionResultType]::ParameterName, 'don''t capture stdout/stderr of tests') [CompletionResult]::new('--bypass-ignore-backends', '--bypass-ignore-backends', [CompletionResultType]::ParameterName, 'Ignore `//@ ignore-backends` directives') [CompletionResult]::new('--no-doc', '--no-doc', [CompletionResultType]::ParameterName, 'Deprecated. Use `--all-targets` or `--tests` instead') + [CompletionResult]::new('--record', '--record', [CompletionResultType]::ParameterName, 'Record all the failed tests in a file in the build directory') + [CompletionResult]::new('--rerun', '--rerun', [CompletionResultType]::ParameterName, 'Rerun tests that previously failed, and stored with `--record`') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'use quiet output') diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh index f32a078b2094d..b2542b94b9468 100644 --- a/src/etc/completions/x.py.sh +++ b/src/etc/completions/x.py.sh @@ -4638,7 +4638,7 @@ _x.py() { return 0 ;; x.py__test) - opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." + opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --record --rerun --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -4856,7 +4856,7 @@ _x.py() { return 0 ;; x.py__test) - opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." + opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --record --rerun --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh index 16fca305719ca..5199c6cbaf350 100644 --- a/src/etc/completions/x.py.zsh +++ b/src/etc/completions/x.py.zsh @@ -544,6 +544,8 @@ _arguments "${_arguments_options[@]}" : \ '--no-capture[don'\''t capture stdout/stderr of tests]' \ '--bypass-ignore-backends[Ignore \`//@ ignore-backends\` directives]' \ '--no-doc[Deprecated. Use \`--all-targets\` or \`--tests\` instead]' \ +'--record[Record all the failed tests in a file in the build directory]' \ +'--rerun[Rerun tests that previously failed, and stored with \`--record\`]' \ '(-q --quiet)*-v[use verbose output (-vv for very verbose)]' \ '(-q --quiet)*--verbose[use verbose output (-vv for very verbose)]' \ '(-v --verbose)-q[use quiet output]' \ @@ -609,6 +611,8 @@ _arguments "${_arguments_options[@]}" : \ '--no-capture[don'\''t capture stdout/stderr of tests]' \ '--bypass-ignore-backends[Ignore \`//@ ignore-backends\` directives]' \ '--no-doc[Deprecated. Use \`--all-targets\` or \`--tests\` instead]' \ +'--record[Record all the failed tests in a file in the build directory]' \ +'--rerun[Rerun tests that previously failed, and stored with \`--record\`]' \ '(-q --quiet)*-v[use verbose output (-vv for very verbose)]' \ '(-q --quiet)*--verbose[use verbose output (-vv for very verbose)]' \ '(-v --verbose)-q[use quiet output]' \ diff --git a/src/etc/completions/x.sh b/src/etc/completions/x.sh index 27b2a45efd53f..5740459a414d2 100644 --- a/src/etc/completions/x.sh +++ b/src/etc/completions/x.sh @@ -4638,7 +4638,7 @@ _x() { return 0 ;; x__test) - opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." + opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --record --rerun --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -4856,7 +4856,7 @@ _x() { return 0 ;; x__test) - opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." + opts="-v -q -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --all-targets --doc --tests --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --no-capture --verbose-run-make-subprocess-output --test-codegen-backend --bypass-ignore-backends --no-doc --record --rerun --verbose --quiet --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --json-output --compile-time-deps --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --ci --skip-std-check-if-no-download-rustc --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/src/etc/completions/x.zsh b/src/etc/completions/x.zsh index 8382f900d021e..31d9e43ef8e89 100644 --- a/src/etc/completions/x.zsh +++ b/src/etc/completions/x.zsh @@ -544,6 +544,8 @@ _arguments "${_arguments_options[@]}" : \ '--no-capture[don'\''t capture stdout/stderr of tests]' \ '--bypass-ignore-backends[Ignore \`//@ ignore-backends\` directives]' \ '--no-doc[Deprecated. Use \`--all-targets\` or \`--tests\` instead]' \ +'--record[Record all the failed tests in a file in the build directory]' \ +'--rerun[Rerun tests that previously failed, and stored with \`--record\`]' \ '(-q --quiet)*-v[use verbose output (-vv for very verbose)]' \ '(-q --quiet)*--verbose[use verbose output (-vv for very verbose)]' \ '(-v --verbose)-q[use quiet output]' \ @@ -609,6 +611,8 @@ _arguments "${_arguments_options[@]}" : \ '--no-capture[don'\''t capture stdout/stderr of tests]' \ '--bypass-ignore-backends[Ignore \`//@ ignore-backends\` directives]' \ '--no-doc[Deprecated. Use \`--all-targets\` or \`--tests\` instead]' \ +'--record[Record all the failed tests in a file in the build directory]' \ +'--rerun[Rerun tests that previously failed, and stored with \`--record\`]' \ '(-q --quiet)*-v[use verbose output (-vv for very verbose)]' \ '(-q --quiet)*--verbose[use verbose output (-vv for very verbose)]' \ '(-v --verbose)-q[use quiet output]' \