diff --git a/src/bin/cargo/commands/bench.rs b/src/bin/cargo/commands/bench.rs index f66f40f3c12..b1398c7a4f8 100644 --- a/src/bin/cargo/commands/bench.rs +++ b/src/bin/cargo/commands/bench.rs @@ -73,12 +73,5 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { let bench_args = bench_args.chain(args.get_many::("args").unwrap_or_default()); let bench_args = bench_args.map(String::as_str).collect::>(); - let err = ops::run_benches(&ws, &ops, &bench_args)?; - match err { - None => Ok(()), - Some(err) => Err(match err.code { - Some(i) => CliError::new(anyhow::format_err!("bench failed"), i), - None => CliError::new(err.into(), 101), - }), - } + ops::run_benches(&ws, &ops, &bench_args) } diff --git a/src/bin/cargo/commands/test.rs b/src/bin/cargo/commands/test.rs index 2f374903e6a..f120c617f9e 100644 --- a/src/bin/cargo/commands/test.rs +++ b/src/bin/cargo/commands/test.rs @@ -1,5 +1,4 @@ use crate::command_prelude::*; -use anyhow::Error; use cargo::ops; pub fn cli() -> App { @@ -110,18 +109,5 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { compile_opts, }; - let err = ops::run_tests(&ws, &ops, &test_args)?; - match err { - None => Ok(()), - Some(err) => { - let context = anyhow::format_err!("{}", err.hint(&ws, &ops.compile_opts)); - let e = match err.code { - // Don't show "process didn't exit successfully" for simple errors. - Some(i) if cargo_util::is_simple_exit_code(i) => CliError::new(context, i), - Some(i) => CliError::new(Error::from(err).context(context), i), - None => CliError::new(Error::from(err).context(context), 101), - }; - Err(e) - } - } + ops::run_tests(&ws, &ops, &test_args) } diff --git a/src/cargo/ops/cargo_test.rs b/src/cargo/ops/cargo_test.rs index 5cadbaf15aa..b7e61982d48 100644 --- a/src/cargo/ops/cargo_test.rs +++ b/src/cargo/ops/cargo_test.rs @@ -3,9 +3,11 @@ use crate::core::shell::Verbosity; use crate::core::{TargetKind, Workspace}; use crate::ops; use crate::util::errors::CargoResult; -use crate::util::{add_path_args, CargoTestError, Config, Test}; +use crate::util::{add_path_args, CliError, CliResult, Config}; +use anyhow::format_err; use cargo_util::{ProcessBuilder, ProcessError}; use std::ffi::OsString; +use std::fmt::Write; use std::path::{Path, PathBuf}; pub struct TestOptions { @@ -14,61 +16,87 @@ pub struct TestOptions { pub no_fail_fast: bool, } -pub fn run_tests( - ws: &Workspace<'_>, - options: &TestOptions, - test_args: &[&str], -) -> CargoResult> { +/// The kind of test. +/// +/// This is needed because `Unit` does not track whether or not something is a +/// benchmark. +#[derive(Copy, Clone)] +enum TestKind { + Test, + Bench, + Doctest, +} + +/// A unit that failed to run. +struct UnitTestError { + unit: Unit, + kind: TestKind, +} + +impl UnitTestError { + /// Returns the CLI args needed to target this unit. + fn cli_args(&self, ws: &Workspace<'_>, opts: &ops::CompileOptions) -> String { + let mut args = if opts.spec.needs_spec_flag(ws) { + format!("-p {} ", self.unit.pkg.name()) + } else { + String::new() + }; + let mut add = |which| write!(args, "--{which} {}", self.unit.target.name()).unwrap(); + + match self.kind { + TestKind::Test | TestKind::Bench => match self.unit.target.kind() { + TargetKind::Lib(_) => args.push_str("--lib"), + TargetKind::Bin => add("bin"), + TargetKind::Test => add("test"), + TargetKind::Bench => add("bench"), + TargetKind::ExampleLib(_) | TargetKind::ExampleBin => add("example"), + TargetKind::CustomBuild => panic!("unexpected CustomBuild kind"), + }, + TestKind::Doctest => args.push_str("--doc"), + } + args + } +} + +/// Compiles and runs tests. +/// +/// On error, the returned [`CliError`] will have the appropriate process exit +/// code that Cargo should use. +pub fn run_tests(ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str]) -> CliResult { let compilation = compile_tests(ws, options)?; if options.no_run { if !options.compile_opts.build_config.emit_json() { display_no_run_information(ws, test_args, &compilation, "unittests")?; } - - return Ok(None); + return Ok(()); } - let (test, mut errors) = run_unit_tests(ws.config(), options, test_args, &compilation)?; + let mut errors = run_unit_tests(ws, options, test_args, &compilation, TestKind::Test)?; - // If we have an error and want to fail fast, then return. - if !errors.is_empty() && !options.no_fail_fast { - return Ok(Some(CargoTestError::new(test, errors))); - } - - let (doctest, docerrors) = run_doc_tests(ws, options, test_args, &compilation)?; - let test = if docerrors.is_empty() { test } else { doctest }; - errors.extend(docerrors); - if errors.is_empty() { - Ok(None) - } else { - Ok(Some(CargoTestError::new(test, errors))) - } + let doctest_errors = run_doc_tests(ws, options, test_args, &compilation)?; + errors.extend(doctest_errors); + no_fail_fast_err(ws, &options.compile_opts, &errors) } -pub fn run_benches( - ws: &Workspace<'_>, - options: &TestOptions, - args: &[&str], -) -> CargoResult> { +/// Compiles and runs benchmarks. +/// +/// On error, the returned [`CliError`] will have the appropriate process exit +/// code that Cargo should use. +pub fn run_benches(ws: &Workspace<'_>, options: &TestOptions, args: &[&str]) -> CliResult { let compilation = compile_tests(ws, options)?; if options.no_run { if !options.compile_opts.build_config.emit_json() { display_no_run_information(ws, args, &compilation, "benches")?; } - - return Ok(None); + return Ok(()); } let mut args = args.to_vec(); args.push("--bench"); - let (test, errors) = run_unit_tests(ws.config(), options, &args, &compilation)?; - - match errors.len() { - 0 => Ok(None), - _ => Ok(Some(CargoTestError::new(test, errors))), - } + let errors = run_unit_tests(ws, options, &args, &compilation, TestKind::Bench)?; + no_fail_fast_err(ws, &options.compile_opts, &errors) } fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult> { @@ -78,12 +106,17 @@ fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, -) -> CargoResult<(Test, Vec)> { + test_kind: TestKind, +) -> Result, CliError> { + let config = ws.config(); let cwd = config.cwd(); let mut errors = Vec::new(); @@ -110,46 +143,32 @@ fn run_unit_tests( .shell() .verbose(|shell| shell.status("Running", &cmd))?; - let result = cmd.exec(); - - if let Err(e) = result { - let e = e.downcast::()?; - errors.push(( - unit.target.kind().clone(), - unit.target.name().to_string(), - unit.pkg.name().to_string(), - e, - )); + if let Err(e) = cmd.exec() { + let code = fail_fast_code(&e); + let unit_err = UnitTestError { + unit: unit.clone(), + kind: test_kind, + }; + report_test_error(ws, &options.compile_opts, &unit_err, e); + errors.push(unit_err); if !options.no_fail_fast { - break; + return Err(CliError::code(code)); } } } - - if errors.len() == 1 { - let (kind, name, pkg_name, e) = errors.pop().unwrap(); - Ok(( - Test::UnitTest { - kind, - name, - pkg_name, - }, - vec![e], - )) - } else { - Ok(( - Test::Multiple, - errors.into_iter().map(|(_, _, _, e)| e).collect(), - )) - } + Ok(errors) } +/// Runs doc tests. +/// +/// Returns a `Vec` of tests that failed when `--no-fail-fast` is used. +/// If `--no-fail-fast` is *not* used, then this returns an `Err`. fn run_doc_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, -) -> CargoResult<(Test, Vec)> { +) -> Result, CliError> { let config = ws.config(); let mut errors = Vec::new(); let doctest_xcompile = config.cli_unstable().doctest_xcompile; @@ -258,16 +277,24 @@ fn run_doc_tests( .shell() .verbose(|shell| shell.status("Running", p.to_string()))?; if let Err(e) = p.exec() { - let e = e.downcast::()?; - errors.push(e); + let code = fail_fast_code(&e); + let unit_err = UnitTestError { + unit: unit.clone(), + kind: TestKind::Doctest, + }; + report_test_error(ws, &options.compile_opts, &unit_err, e); + errors.push(unit_err); if !options.no_fail_fast { - return Ok((Test::Doc, errors)); + return Err(CliError::code(code)); } } } - Ok((Test::Doc, errors)) + Ok(errors) } +/// Displays human-readable descriptions of the test executables. +/// +/// This is used when `cargo test --no-run` is used. fn display_no_run_information( ws: &Workspace<'_>, test_args: &[&str], @@ -303,6 +330,11 @@ fn display_no_run_information( return Ok(()); } +/// Creates a [`ProcessBuilder`] for executing a single test. +/// +/// Returns a tuple `(exe_display, process)` where `exe_display` is a string +/// to display that describes the executable path in a human-readable form. +/// `process` is the `ProcessBuilder` to use for executing the test. fn cmd_builds( config: &Config, cwd: &Path, @@ -341,3 +373,67 @@ fn cmd_builds( Ok((exe_display, cmd)) } + +/// Returns the error code to use when *not* using `--no-fail-fast`. +/// +/// Cargo will return the error code from the test process itself. If some +/// other error happened (like a failure to launch the process), then it will +/// return a standard 101 error code. +/// +/// When using `--no-fail-fast`, Cargo always uses the 101 exit code (since +/// there may not be just one process to report). +fn fail_fast_code(error: &anyhow::Error) -> i32 { + if let Some(proc_err) = error.downcast_ref::() { + if let Some(code) = proc_err.code { + return code; + } + } + 101 +} + +/// Returns the `CliError` when using `--no-fail-fast` and there is at least +/// one error. +fn no_fail_fast_err( + ws: &Workspace<'_>, + opts: &ops::CompileOptions, + errors: &[UnitTestError], +) -> CliResult { + // TODO: This could be improved by combining the flags on a single line when feasible. + let args: Vec<_> = errors + .iter() + .map(|unit_err| format!(" `{}`", unit_err.cli_args(ws, opts))) + .collect(); + let message = match errors.len() { + 0 => return Ok(()), + 1 => format!("1 target failed:\n{}", args.join("\n")), + n => format!("{n} targets failed:\n{}", args.join("\n")), + }; + Err(anyhow::Error::msg(message).into()) +} + +/// Displays an error on the console about a test failure. +fn report_test_error( + ws: &Workspace<'_>, + opts: &ops::CompileOptions, + unit_err: &UnitTestError, + test_error: anyhow::Error, +) { + let which = match unit_err.kind { + TestKind::Test => "test failed", + TestKind::Bench => "bench failed", + TestKind::Doctest => "doctest failed", + }; + + let mut err = format_err!("{}, to rerun pass `{}`", which, unit_err.cli_args(ws, opts)); + // Don't show "process didn't exit successfully" for simple errors. + // libtest exits with 101 for normal errors. + let is_simple = test_error + .downcast_ref::() + .and_then(|proc_err| proc_err.code) + .map_or(false, |code| code == 101); + if !is_simple { + err = test_error.context(err); + } + + crate::display_error(&err, &mut ws.config().shell()); +} diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 3668c92152f..1b48929792f 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -1,9 +1,6 @@ #![allow(unknown_lints)] -use crate::core::{TargetKind, Workspace}; -use crate::ops::CompileOptions; use anyhow::Error; -use cargo_util::ProcessError; use std::fmt; use std::path::PathBuf; @@ -197,91 +194,6 @@ impl<'a> Iterator for ManifestCauses<'a> { impl<'a> ::std::iter::FusedIterator for ManifestCauses<'a> {} -// ============================================================================= -// Cargo test errors. - -/// Error when testcases fail -#[derive(Debug)] -pub struct CargoTestError { - pub test: Test, - pub desc: String, - pub code: Option, - pub causes: Vec, -} - -impl fmt::Display for CargoTestError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.desc.fmt(f) - } -} - -impl std::error::Error for CargoTestError {} - -#[derive(Debug)] -pub enum Test { - Multiple, - Doc, - UnitTest { - kind: TargetKind, - name: String, - pkg_name: String, - }, -} - -impl CargoTestError { - pub fn new(test: Test, errors: Vec) -> Self { - if errors.is_empty() { - panic!("Cannot create CargoTestError from empty Vec") - } - let desc = errors - .iter() - .map(|error| error.desc.clone()) - .collect::>() - .join("\n"); - CargoTestError { - test, - desc, - code: errors[0].code, - causes: errors, - } - } - - pub fn hint(&self, ws: &Workspace<'_>, opts: &CompileOptions) -> String { - match self.test { - Test::UnitTest { - ref kind, - ref name, - ref pkg_name, - } => { - let pkg_info = if opts.spec.needs_spec_flag(ws) { - format!("-p {} ", pkg_name) - } else { - String::new() - }; - - match *kind { - TargetKind::Bench => { - format!("test failed, to rerun pass '{}--bench {}'", pkg_info, name) - } - TargetKind::Bin => { - format!("test failed, to rerun pass '{}--bin {}'", pkg_info, name) - } - TargetKind::Lib(_) => format!("test failed, to rerun pass '{}--lib'", pkg_info), - TargetKind::Test => { - format!("test failed, to rerun pass '{}--test {}'", pkg_info, name) - } - TargetKind::ExampleBin | TargetKind::ExampleLib(_) => { - format!("test failed, to rerun pass '{}--example {}", pkg_info, name) - } - _ => "test failed.".into(), - } - } - Test::Doc => "test failed, to rerun pass '--doc'".into(), - _ => "test failed.".into(), - } - } -} - // ============================================================================= // CLI errors diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index 4b8604f92fb..2cfea54d2ea 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -6,8 +6,8 @@ pub use self::config::{homedir, Config, ConfigValue}; pub(crate) use self::counter::MetricsCounter; pub use self::dependency_queue::DependencyQueue; pub use self::diagnostic_server::RustfixDiagnosticServer; -pub use self::errors::{internal, CargoResult, CliResult, Test}; -pub use self::errors::{CargoTestError, CliError}; +pub use self::errors::CliError; +pub use self::errors::{internal, CargoResult, CliResult}; pub use self::flock::{FileLock, Filesystem}; pub use self::graph::Graph; pub use self::hasher::StableHasher; diff --git a/tests/testsuite/bench.rs b/tests/testsuite/bench.rs index c76ca7d04fe..a825f444092 100644 --- a/tests/testsuite/bench.rs +++ b/tests/testsuite/bench.rs @@ -1153,7 +1153,7 @@ fn test_bench_no_fail_fast() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( - "src/foo.rs", + "src/main.rs", r#" #![feature(test)] #[cfg(test)] @@ -1177,15 +1177,36 @@ fn test_bench_no_fail_fast() { } "#, ) + .file( + "benches/b1.rs", + r#" + #![feature(test)] + extern crate test; + #[bench] + fn b1_fail(_b: &mut test::Bencher) { assert_eq!(1, 2); } + "#, + ) .build(); p.cargo("bench --no-fail-fast -- --test-threads=1") .with_status(101) - .with_stderr_contains("[RUNNING] [..] (target/release/deps/foo-[..][EXE])") + .with_stderr( + "\ +[COMPILING] foo v0.5.0 [..] +[FINISHED] bench [..] +[RUNNING] unittests src/main.rs (target/release/deps/foo[..]) +[ERROR] bench failed, to rerun pass `--bin foo` +[RUNNING] benches/b1.rs (target/release/deps/b1[..]) +[ERROR] bench failed, to rerun pass `--bench b1` +[ERROR] 2 targets failed: + `--bin foo` + `--bench b1` +", + ) .with_stdout_contains("running 2 tests") - .with_stderr_contains("[RUNNING] [..] (target/release/deps/foo-[..][EXE])") .with_stdout_contains("test bench_hello [..]") .with_stdout_contains("test bench_nope [..]") + .with_stdout_contains("test b1_fail [..]") .run(); } diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index f310d0e5c9d..eec47996082 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -6,7 +6,7 @@ use cargo_test_support::{ basic_bin_manifest, basic_lib_manifest, basic_manifest, cargo_exe, project, }; use cargo_test_support::{cross_compile, paths}; -use cargo_test_support::{rustc_host, sleep_ms}; +use cargo_test_support::{rustc_host, rustc_host_env, sleep_ms}; use std::fs; #[cargo_test] @@ -377,7 +377,7 @@ fn cargo_test_failing_test_in_bin() { [COMPILING] foo v0.5.0 ([CWD]) [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] [..] (target/debug/deps/foo-[..][EXE]) -[ERROR] test failed, to rerun pass '--bin foo'", +[ERROR] test failed, to rerun pass `--bin foo`", ) .with_stdout_contains( " @@ -426,7 +426,7 @@ fn cargo_test_failing_test_in_test() { [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] [..] (target/debug/deps/foo-[..][EXE]) [RUNNING] [..] (target/debug/deps/footest-[..][EXE]) -[ERROR] test failed, to rerun pass '--test footest'", +[ERROR] test failed, to rerun pass `--test footest`", ) .with_stdout_contains("running 0 tests") .with_stdout_contains( @@ -464,7 +464,7 @@ fn cargo_test_failing_test_in_lib() { [COMPILING] foo v0.5.0 ([CWD]) [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] [..] (target/debug/deps/foo-[..][EXE]) -[ERROR] test failed, to rerun pass '--lib'", +[ERROR] test failed, to rerun pass `--lib`", ) .with_stdout_contains( "\ @@ -2466,19 +2466,20 @@ fn no_fail_fast() { .build(); p.cargo("test --no-fail-fast") .with_status(101) - .with_stderr_contains( + .with_stderr( "\ -[COMPILING] foo v0.0.1 ([..]) -[FINISHED] test [unoptimized + debuginfo] target(s) in [..] -[RUNNING] [..] (target/debug/deps/foo-[..][EXE]) -[RUNNING] [..] (target/debug/deps/test_add_one-[..][EXE])", +[COMPILING] foo v0.0.1 [..] +[FINISHED] test [..] +[RUNNING] unittests src/lib.rs (target/debug/deps/foo[..]) +[RUNNING] tests/test_add_one.rs (target/debug/deps/test_add_one[..]) +[ERROR] test failed, to rerun pass `--test test_add_one` +[RUNNING] tests/test_sub_one.rs (target/debug/deps/test_sub_one[..]) +[DOCTEST] foo +[ERROR] 1 target failed: + `--test test_add_one` +", ) .with_stdout_contains("running 0 tests") - .with_stderr_contains( - "\ -[RUNNING] [..] (target/debug/deps/test_sub_one-[..][EXE]) -[DOCTEST] foo", - ) .with_stdout_contains("test result: FAILED. [..]") .with_stdout_contains("test sub_one_test ... ok") .with_stdout_contains_n("test [..] ... ok", 3) @@ -3576,10 +3577,7 @@ fn test_hint_not_masked_by_doctest() { .with_status(101) .with_stdout_contains("test this_fails ... FAILED") .with_stdout_contains("[..]this_works (line [..]ok") - .with_stderr_contains( - "[ERROR] test failed, to rerun pass \ - '--test integ'", - ) + .with_stderr_contains("[ERROR] test failed, to rerun pass `--test integ`") .run(); } @@ -3590,24 +3588,131 @@ fn test_hint_workspace_virtual() { "Cargo.toml", r#" [workspace] - members = ["a", "b"] + members = ["a", "b", "c"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn t1() {}") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "#[test] fn t1() {assert!(false)}") + .file("c/Cargo.toml", &basic_manifest("c", "0.1.0")) + .file( + "c/src/lib.rs", + r#" + /// ```rust + /// assert_eq!(1, 2); + /// ``` + pub fn foo() {} + "#, + ) + .file( + "c/src/main.rs", + r#" + fn main() {} + + #[test] + fn from_main() { assert_eq!(1, 2); } + "#, + ) + .file( + "c/tests/t1.rs", + r#" + #[test] + fn from_int_test() { assert_eq!(1, 2); } + "#, + ) + .file( + "c/examples/ex1.rs", + r#" + fn main() {} + + #[test] + fn from_example() { assert_eq!(1, 2); } + "#, + ) + // This does not use #[bench] since it is unstable. #[test] works just + // the same for our purpose of checking the hint. + .file( + "c/benches/b1.rs", + r#" + #[test] + fn from_bench() { assert_eq!(1, 2); } + "#, + ) .build(); + // This depends on Units being sorted so that `b` fails first. p.cargo("test") - .with_stderr_contains("[ERROR] test failed, to rerun pass '-p b --lib'") + .with_stderr_unordered( + "\ +[COMPILING] c v0.1.0 [..] +[COMPILING] a v0.1.0 [..] +[COMPILING] b v0.1.0 [..] +[FINISHED] test [..] +[RUNNING] unittests src/lib.rs (target/debug/deps/a[..]) +[RUNNING] unittests src/lib.rs (target/debug/deps/b[..]) +[ERROR] test failed, to rerun pass `-p b --lib` +", + ) .with_status(101) .run(); p.cargo("test") .cwd("b") - .with_stderr_contains("[ERROR] test failed, to rerun pass '--lib'") + .with_stderr( + "\ +[FINISHED] test [..] +[RUNNING] unittests src/lib.rs ([ROOT]/foo/target/debug/deps/b[..]) +[ERROR] test failed, to rerun pass `--lib` +", + ) .with_status(101) .run(); + p.cargo("test --no-fail-fast") + .with_stderr( + "\ +[FINISHED] test [..] +[RUNNING] unittests src/lib.rs (target/debug/deps/a[..]) +[RUNNING] unittests src/lib.rs (target/debug/deps/b[..]) +[ERROR] test failed, to rerun pass `-p b --lib` +[RUNNING] unittests src/lib.rs (target/debug/deps/c[..]) +[RUNNING] unittests src/main.rs (target/debug/deps/c[..]) +[ERROR] test failed, to rerun pass `-p c --bin c` +[RUNNING] tests/t1.rs (target/debug/deps/t1[..]) +[ERROR] test failed, to rerun pass `-p c --test t1` +[DOCTEST] a +[DOCTEST] b +[DOCTEST] c +[ERROR] doctest failed, to rerun pass `-p c --doc` +[ERROR] 4 targets failed: + `-p b --lib` + `-p c --bin c` + `-p c --test t1` + `-p c --doc` +", + ) + .with_status(101) + .run(); + // Check others that are not in the default set. + p.cargo("test -p c --examples --benches --no-fail-fast") + .with_stderr( + "\ +[COMPILING] c v0.1.0 [..] +[FINISHED] test [..] +[RUNNING] unittests src/lib.rs (target/debug/deps/c[..]) +[RUNNING] unittests src/main.rs (target/debug/deps/c[..]) +[ERROR] test failed, to rerun pass `-p c --bin c` +[RUNNING] benches/b1.rs (target/debug/deps/b1[..]) +[ERROR] test failed, to rerun pass `-p c --bench b1` +[RUNNING] unittests examples/ex1.rs (target/debug/examples/ex1[..]) +[ERROR] test failed, to rerun pass `-p c --example ex1` +[ERROR] 3 targets failed: + `-p c --bin c` + `-p c --bench b1` + `-p c --example ex1` +", + ) + .with_status(101) + .run() } #[cargo_test] @@ -3630,11 +3735,11 @@ fn test_hint_workspace_nonvirtual() { .build(); p.cargo("test --workspace") - .with_stderr_contains("[ERROR] test failed, to rerun pass '-p a --lib'") + .with_stderr_contains("[ERROR] test failed, to rerun pass `-p a --lib`") .with_status(101) .run(); p.cargo("test -p a") - .with_stderr_contains("[ERROR] test failed, to rerun pass '-p a --lib'") + .with_stderr_contains("[ERROR] test failed, to rerun pass `-p a --lib`") .with_status(101) .run(); } @@ -4494,3 +4599,108 @@ fn test_workspaces_cwd() { .with_stdout_contains("test test_integration_deep_cwd ... ok") .run(); } + +#[cargo_test] +fn execution_error() { + // Checks the behavior when a test fails to launch. + let p = project() + .file( + "tests/t1.rs", + r#" + #[test] + fn foo() {} + "#, + ) + .build(); + let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env()); + p.cargo("test") + .env(&key, "does_not_exist") + // The actual error is usually "no such file", but on Windows it has a + // custom message. Since matching against the error string produced by + // Rust is not very reliable, this just uses `[..]`. + .with_stderr( + "\ +[COMPILING] foo v0.0.1 [..] +[FINISHED] test [..] +[RUNNING] tests/t1.rs (target/debug/deps/t1[..]) +error: test failed, to rerun pass `--test t1` + +Caused by: + could not execute process `does_not_exist [ROOT]/foo/target/debug/deps/t1[..]` (never executed) + +Caused by: + [..] +", + ) + .with_status(101) + .run(); +} + +#[cargo_test] +fn nonzero_exit_status() { + // Tests for nonzero exit codes from tests. + let p = project() + .file( + "tests/t1.rs", + r#" + #[test] + fn t() { panic!("this is a normal error") } + "#, + ) + .file( + "tests/t2.rs", + r#" + #[test] + fn t() { std::process::exit(4) } + "#, + ) + .build(); + + p.cargo("test --test t1") + .with_stderr( + "\ +[COMPILING] foo [..] +[FINISHED] test [..] +[RUNNING] tests/t1.rs (target/debug/deps/t1[..]) +error: test failed, to rerun pass `--test t1` +", + ) + .with_stdout_contains("[..]this is a normal error[..]") + .with_status(101) + .run(); + + p.cargo("test --test t2") + .with_stderr( + "\ +[COMPILING] foo v0.0.1 [..] +[FINISHED] test [..] +[RUNNING] tests/t2.rs (target/debug/deps/t2[..]) +error: test failed, to rerun pass `--test t2` + +Caused by: + process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2[..]` (exit [..]: 4) +", + ) + .with_status(4) + .run(); + + // no-fail-fast always uses 101 + p.cargo("test --no-fail-fast") + .with_stderr( + "\ +[FINISHED] test [..] +[RUNNING] tests/t1.rs (target/debug/deps/t1[..]) +error: test failed, to rerun pass `--test t1` +[RUNNING] tests/t2.rs (target/debug/deps/t2[..]) +error: test failed, to rerun pass `--test t2` + +Caused by: + process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2[..]` (exit [..]: 4) +error: 2 targets failed: + `--test t1` + `--test t2` +", + ) + .with_status(101) + .run(); +}