Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions library/std/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ use crate::fmt::{self, Write};
/// the `Debug` output means `Report` is an ideal starting place for formatting errors returned
/// from `main`.
///
/// ```should_panic
/// ```
/// #![feature(error_reporter)]
/// use std::error::Report;
/// # use std::error::Error;
Expand Down Expand Up @@ -154,10 +154,14 @@ use crate::fmt::{self, Write};
/// # Err(SuperError { source: SuperErrorSideKick })
/// # }
///
/// fn main() -> Result<(), Report<SuperError>> {
/// fn run() -> Result<(), Report<SuperError>> {
/// get_super_error()?;
/// Ok(())
/// }
///
/// fn main() {
/// assert!(run().is_err());
/// }
/// ```
///
/// This example produces the following output:
Expand All @@ -170,7 +174,7 @@ use crate::fmt::{self, Write};
/// output format. If you want to make sure your `Report`s are pretty printed and include backtrace
/// you will need to manually convert and enable those flags.
///
/// ```should_panic
/// ```
/// #![feature(error_reporter)]
/// use std::error::Report;
/// # use std::error::Error;
Expand Down Expand Up @@ -201,12 +205,16 @@ use crate::fmt::{self, Write};
/// # Err(SuperError { source: SuperErrorSideKick })
/// # }
///
/// fn main() -> Result<(), Report<SuperError>> {
/// fn run() -> Result<(), Report<SuperError>> {
/// get_super_error()
/// .map_err(Report::from)
/// .map_err(|r| r.pretty(true).show_backtrace(true))?;
/// Ok(())
/// }
///
/// fn main() {
/// assert!(run().is_err());
/// }
/// ```
///
/// This example produces the following output:
Expand Down
4 changes: 3 additions & 1 deletion library/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ pub mod test {
pub use crate::cli::{TestOpts, parse_opts};
pub use crate::helpers::metrics::{Metric, MetricMap};
pub use crate::options::{Options, RunIgnored, RunStrategy, ShouldPanic};
pub use crate::test_result::{TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk};
pub use crate::test_result::{
RustdocResult, TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk, get_rustdoc_result,
};
pub use crate::time::{TestExecTime, TestTimeOptions};
pub use crate::types::{
DynTestFn, DynTestName, StaticBenchFn, StaticTestFn, StaticTestName, TestDesc,
Expand Down
117 changes: 108 additions & 9 deletions library/test/src/test_result.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::any::Any;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;
use std::process::{ExitStatus, Output};
use std::{fmt, io};

pub use self::TestResult::*;
use super::bench::BenchSamples;
Expand Down Expand Up @@ -103,15 +104,14 @@ pub(crate) fn calc_result(
result
}

/// Creates a `TestResult` depending on the exit code of test subprocess.
pub(crate) fn get_result_from_exit_code(
desc: &TestDesc,
/// Creates a `TestResult` depending on the exit code of test subprocess
pub(crate) fn get_result_from_exit_code_inner(
status: ExitStatus,
time_opts: Option<&time::TestTimeOptions>,
exec_time: Option<&time::TestExecTime>,
success_error_code: i32,
) -> TestResult {
let result = match status.code() {
Some(TR_OK) => TestResult::TrOk,
match status.code() {
Some(error_code) if error_code == success_error_code => TestResult::TrOk,
Some(crate::ERROR_EXIT_CODE) => TestResult::TrFailed,
#[cfg(windows)]
Some(STATUS_FAIL_FAST_EXCEPTION) => TestResult::TrFailed,
#[cfg(unix)]
Expand All @@ -131,7 +131,17 @@ pub(crate) fn get_result_from_exit_code(
Some(code) => TestResult::TrFailedMsg(format!("got unexpected return code {code}")),
#[cfg(not(any(windows, unix)))]
Some(_) => TestResult::TrFailed,
};
}
}

/// Creates a `TestResult` depending on the exit code of test subprocess and on its runtime.
pub(crate) fn get_result_from_exit_code(
desc: &TestDesc,
status: ExitStatus,
time_opts: Option<&time::TestTimeOptions>,
exec_time: Option<&time::TestExecTime>,
) -> TestResult {
let result = get_result_from_exit_code_inner(status, TR_OK);

// If test is already failed (or allowed to fail), do not change the result.
if result != TestResult::TrOk {
Expand All @@ -147,3 +157,92 @@ pub(crate) fn get_result_from_exit_code(

result
}

pub enum RustdocResult {
/// The test failed to compile.
CompileError,
/// The test is marked `compile_fail` but compiled successfully.
UnexpectedCompilePass,
/// The test failed to compile (as expected) but the compiler output did not contain all
/// expected error codes.
MissingErrorCodes(Vec<String>),
/// The test binary was unable to be executed.
ExecutionError(io::Error),
/// The test binary exited with a non-zero exit code.
///
/// This typically means an assertion in the test failed or another form of panic occurred.
ExecutionFailure(Output),
/// The test is marked `should_panic` but the test binary executed successfully.
NoPanic(Option<String>),
}

impl fmt::Display for RustdocResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CompileError => {
write!(f, "Couldn't compile the test.")
}
Self::UnexpectedCompilePass => {
write!(f, "Test compiled successfully, but it's marked `compile_fail`.")
}
Self::NoPanic(msg) => {
write!(f, "Test didn't panic, but it's marked `should_panic`")?;
if let Some(msg) = msg {
write!(f, " ({msg})")?;
}
f.write_str(".")
}
Self::MissingErrorCodes(codes) => {
write!(f, "Some expected error codes were not found: {codes:?}")
}
Self::ExecutionError(err) => {
write!(f, "Couldn't run the test: {err}")?;
if err.kind() == io::ErrorKind::PermissionDenied {
f.write_str(" - maybe your tempdir is mounted with noexec?")?;
}
Ok(())
}
Self::ExecutionFailure(out) => {
writeln!(f, "Test executable failed ({reason}).", reason = out.status)?;

// FIXME(#12309): An unfortunate side-effect of capturing the test
// executable's output is that the relative ordering between the test's
// stdout and stderr is lost. However, this is better than the
// alternative: if the test executable inherited the parent's I/O
// handles the output wouldn't be captured at all, even on success.
//
// The ordering could be preserved if the test process' stderr was
// redirected to stdout, but that functionality does not exist in the
// standard library, so it may not be portable enough.
let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
let stderr = str::from_utf8(&out.stderr).unwrap_or_default();

if !stdout.is_empty() || !stderr.is_empty() {
writeln!(f)?;

if !stdout.is_empty() {
writeln!(f, "stdout:\n{stdout}")?;
}

if !stderr.is_empty() {
writeln!(f, "stderr:\n{stderr}")?;
}
}
Ok(())
}
}
}
}

pub fn get_rustdoc_result(output: Output, should_panic: bool) -> Result<(), RustdocResult> {
let result = get_result_from_exit_code_inner(output.status, 0);
match (result, should_panic) {
(TestResult::TrFailed, true) | (TestResult::TrOk, false) => Ok(()),
(TestResult::TrOk, true) => Err(RustdocResult::NoPanic(None)),
(TestResult::TrFailedMsg(msg), true) => Err(RustdocResult::NoPanic(Some(msg))),
(TestResult::TrFailedMsg(_) | TestResult::TrFailed, false) => {
Err(RustdocResult::ExecutionFailure(output))
}
_ => unreachable!("unexpected status for rustdoc test output"),
}
}
Loading
Loading