Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pretty print assertion failures in tests #79001

Closed
wants to merge 11 commits into from
Closed
66 changes: 53 additions & 13 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,25 @@ macro_rules! panic {
/// ```
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(core_panic)]
#[allow_internal_unstable(core_panic, panic_internals)]
macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = $crate::panicking::AssertKind::Eq;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::None);
$crate::panicking::assert_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Eq,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($right),
},
&*left_val,
&*right_val,
$crate::option::Option::None,
);
}
}
}
Expand All @@ -50,11 +58,19 @@ macro_rules! assert_eq {
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = $crate::panicking::AssertKind::Eq;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::Some($crate::format_args!($($arg)+)));
$crate::panicking::assert_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Eq,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($right),
},
&*left_val,
&*right_val,
$crate::option::Option::Some($crate::format_args!($($arg)+)),
);
}
}
}
Expand All @@ -80,17 +96,25 @@ macro_rules! assert_eq {
/// ```
#[macro_export]
#[stable(feature = "assert_ne", since = "1.13.0")]
#[allow_internal_unstable(core_panic)]
#[allow_internal_unstable(core_panic, panic_internals)]
macro_rules! assert_ne {
($left:expr, $right:expr $(,)?) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if *left_val == *right_val {
let kind = $crate::panicking::AssertKind::Ne;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::None);
$crate::panicking::assert_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Ne,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($right),
},
&*left_val,
&*right_val,
$crate::option::Option::None,
);
}
}
}
Expand All @@ -99,11 +123,19 @@ macro_rules! assert_ne {
match (&($left), &($right)) {
(left_val, right_val) => {
if *left_val == *right_val {
let kind = $crate::panicking::AssertKind::Ne;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::Some($crate::format_args!($($arg)+)));
$crate::panicking::assert_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Ne,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($right),
},
&*left_val,
&*right_val,
$crate::option::Option::Some($crate::format_args!($($arg)+)),
);
}
}
}
Expand Down Expand Up @@ -137,16 +169,20 @@ macro_rules! assert_ne {
/// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
/// ```
#[unstable(feature = "assert_matches", issue = "82775")]
#[allow_internal_unstable(core_panic)]
#[allow_internal_unstable(core_panic, panic_internals)]
#[rustc_macro_transparency = "semitransparent"]
pub macro assert_matches {
($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => ({
match $left {
$( $pattern )|+ $( if $guard )? => {}
ref left_val => {
$crate::panicking::assert_matches_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Match,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($($pattern)|+ $(if $guard)?),
},
left_val,
$crate::stringify!($($pattern)|+ $(if $guard)?),
$crate::option::Option::None
);
}
Expand All @@ -157,8 +193,12 @@ pub macro assert_matches {
$( $pattern )|+ $( if $guard )? => {}
ref left_val => {
$crate::panicking::assert_matches_failed(
&$crate::panic::assert_info::BinaryAssertionStaticData {
kind: $crate::panic::assert_info::BinaryAssertKind::Match,
left_expr: $crate::stringify!($left),
right_expr: $crate::stringify!($($pattern)|+ $(if $guard)?),
},
left_val,
$crate::stringify!($($pattern)|+ $(if $guard)?),
$crate::option::Option::Some($crate::format_args!($($arg)+))
);
}
Expand Down
14 changes: 14 additions & 0 deletions library/core/src/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@

#![stable(feature = "core_panic_info", since = "1.41.0")]

#[unstable(
feature = "panic_internals",
reason = "internal details of the implementation of the `panic!` and related macros",
issue = "none"
)]
#[doc(hidden)]
pub mod assert_info;
mod location;
#[unstable(
feature = "panic_internals",
reason = "internal details of the implementation of the `panic!` and related macros",
issue = "none"
)]
#[doc(hidden)]
pub mod panic_description;
mod panic_info;
mod unwind_safe;

Expand Down
97 changes: 97 additions & 0 deletions library/core/src/panic/assert_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::fmt::{self, Debug};

/// Information about a failed assertion.
#[derive(Debug)]
pub struct AssertInfo<'a> {
/// The assertion that failed.
pub assertion: Assertion<'a>,
/// Optional additional message to include in the failure report.
pub message: Option<crate::fmt::Arguments<'a>>,
}

impl fmt::Display for AssertInfo<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.assertion {
Assertion::Binary(ref assertion) => match self.message {
Some(message) => write!(
formatter,
r#"assertion failed: `(left {} right)`
left: `{:?}`,
right: `{:?}`: {}"#,
assertion.static_data.kind.op(),
assertion.left_val,
assertion.right_val,
message
),
None => write!(
formatter,
r#"assertion failed: `(left {} right)`
left: `{:?}`,
right: `{:?}`"#,
assertion.static_data.kind.op(),
assertion.left_val,
assertion.right_val
),
},
}
}
}

/// Details about the expression that failed an assertion.
#[derive(Debug)]
pub enum Assertion<'a> {
/// The failed assertion is a binary expression.
Binary(BinaryAssertion<'a>),
}

/// Information about a failed binary assertion.
#[derive(Debug)]
pub struct BinaryAssertion<'a> {
/// Static information about the failed assertion.
pub static_data: &'static BinaryAssertionStaticData,
/// The left value of the binary assertion.
pub left_val: &'a dyn Debug,
/// The right value of the binary assertion.
pub right_val: &'a dyn Debug,
}

/// Information about a binary assertion that can be constructed at compile time.
///
/// This struct helps to reduce the size `AssertInfo`.
#[derive(Debug)]
pub struct BinaryAssertionStaticData {
/// The kind of the binary assertion
pub kind: BinaryAssertKind,
/// The left expression of the binary assertion.
pub left_expr: &'static str,
/// The right expression of the binary assertion.
pub right_expr: &'static str,
}

/// The kind of a binary assertion
#[derive(Debug)]
pub enum BinaryAssertKind {
Eq,
Ne,
Match,
}

impl BinaryAssertKind {
/// The name of the macro that triggered the panic.
pub fn macro_name(&self) -> &'static str {
match self {
Self::Eq { .. } => "assert_eq",
Self::Ne { .. } => "assert_ne",
Self::Match { .. } => "assert_matches",
}
}

/// Symbolic representation of the binary assertion.
pub fn op(&self) -> &'static str {
match self {
Self::Eq { .. } => "==",
Self::Ne { .. } => "!=",
Self::Match { .. } => "matches",
}
}
}
27 changes: 27 additions & 0 deletions library/core/src/panic/panic_description.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::fmt;
use crate::panic::assert_info::AssertInfo;

/// Describes the cause of the panic.
#[doc(hidden)]
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum PanicDescription<'a> {
/// Formatted arguments that were passed to the panic.
Message(&'a fmt::Arguments<'a>),
/// Information about the assertion that caused the panic.
AssertInfo(&'a AssertInfo<'a>),
}

#[unstable(
feature = "panic_internals",
reason = "internal details of the implementation of the `panic!` and related macros",
issue = "none"
)]
impl fmt::Display for PanicDescription<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Message(message) => write!(formatter, "{}", message),
Self::AssertInfo(info) => write!(formatter, "{}", info),
}
}
}
44 changes: 38 additions & 6 deletions library/core/src/panic/panic_info.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::any::Any;
use crate::fmt;
use crate::panic::assert_info::AssertInfo;
use crate::panic::panic_description::PanicDescription;
use crate::panic::Location;

/// A struct providing information about a panic.
Expand Down Expand Up @@ -29,7 +31,7 @@ use crate::panic::Location;
#[derive(Debug)]
pub struct PanicInfo<'a> {
payload: &'a (dyn Any + Send),
message: Option<&'a fmt::Arguments<'a>>,
description: Option<PanicDescription<'a>>,
location: &'a Location<'a>,
}

Expand All @@ -42,11 +44,11 @@ impl<'a> PanicInfo<'a> {
#[doc(hidden)]
#[inline]
pub fn internal_constructor(
message: Option<&'a fmt::Arguments<'a>>,
description: Option<PanicDescription<'a>>,
location: &'a Location<'a>,
) -> Self {
struct NoPayload;
PanicInfo { location, message, payload: &NoPayload }
PanicInfo { location, description, payload: &NoPayload }
}

#[unstable(
Expand Down Expand Up @@ -86,12 +88,27 @@ impl<'a> PanicInfo<'a> {
self.payload
}

/// Get the description of the cause of the panic.
///
/// Returns `None` if no description was provided.
#[unstable(
feature = "panic_internals",
reason = "internal details of the implementation of the `panic!` and related macros",
issue = "none"
)]
pub fn description(&self) -> Option<PanicDescription<'_>> {
self.description
}

/// If the `panic!` macro from the `core` crate (not from `std`)
/// was used with a formatting string and some additional arguments,
/// returns that message ready to be used for example with [`fmt::write`]
#[unstable(feature = "panic_info_message", issue = "66745")]
pub fn message(&self) -> Option<&fmt::Arguments<'_>> {
self.message
match self.description {
Some(PanicDescription::Message(message)) => Some(message),
_ => None,
}
}

/// Returns information about the location from which the panic originated,
Expand Down Expand Up @@ -124,14 +141,29 @@ impl<'a> PanicInfo<'a> {
// deal with that case in std::panicking::default_hook and core::panicking::panic_fmt.
Some(&self.location)
}

/// Get the information about the assertion that caused the panic.
///
/// Returns `None` if the panic was not caused by an assertion.
#[unstable(
feature = "panic_internals",
reason = "internal details of the implementation of the `panic!` and related macros",
issue = "none"
)]
pub fn assert_info(&self) -> Option<&AssertInfo<'_>> {
match self.description {
Some(PanicDescription::AssertInfo(info)) => Some(info),
_ => None,
}
}
}

#[stable(feature = "panic_hook_display", since = "1.26.0")]
impl fmt::Display for PanicInfo<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("panicked at ")?;
if let Some(message) = self.message {
write!(formatter, "'{}', ", message)?
if let Some(description) = self.description {
write!(formatter, "'{}', ", description)?
} else if let Some(payload) = self.payload.downcast_ref::<&'static str>() {
write!(formatter, "'{}', ", payload)?
}
Expand Down
Loading