Rework test error handling

This commit is contained in:
Eric Huss 2022-08-27 17:45:11 -07:00
parent e5ec3a8ff9
commit 23735d4c09
7 changed files with 427 additions and 209 deletions

View File

@ -73,12 +73,5 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
let bench_args = bench_args.chain(args.get_many::<String>("args").unwrap_or_default());
let bench_args = bench_args.map(String::as_str).collect::<Vec<_>>();
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)
}

View File

@ -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)
}

View File

@ -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<Option<CargoTestError>> {
/// 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<Option<CargoTestError>> {
/// 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<Compilation<'a>> {
@ -78,12 +106,17 @@ fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<C
}
/// Runs the unit and integration tests of a package.
///
/// 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_unit_tests(
config: &Config,
ws: &Workspace<'_>,
options: &TestOptions,
test_args: &[&str],
compilation: &Compilation<'_>,
) -> CargoResult<(Test, Vec<ProcessError>)> {
test_kind: TestKind,
) -> Result<Vec<UnitTestError>, 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::<ProcessError>()?;
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<ProcessError>)> {
) -> Result<Vec<UnitTestError>, 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::<ProcessError>()?;
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::<ProcessError>() {
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::<ProcessError>()
.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());
}

View File

@ -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<i32>,
pub causes: Vec<ProcessError>,
}
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<ProcessError>) -> Self {
if errors.is_empty() {
panic!("Cannot create CargoTestError from empty Vec")
}
let desc = errors
.iter()
.map(|error| error.desc.clone())
.collect::<Vec<String>>()
.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

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}