diff --git a/src/assertions.rs b/src/assertions.rs index 5c0edad..c473ddd 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -11,8 +11,7 @@ #![allow(clippy::wrong_self_convention, clippy::return_self_not_must_use)] use crate::spec::{CollectFailures, DebugRepresentation, GetFailures, Represent, Spec}; -use crate::std::ops::RangeBounds; -use crate::std::string::String; +use crate::std::{ops::RangeBounds, string::String}; /// Assert whether two values are equal or not. /// diff --git a/src/boolean/mod.rs b/src/boolean/mod.rs index 0fbb0d5..dde32c6 100644 --- a/src/boolean/mod.rs +++ b/src/boolean/mod.rs @@ -7,8 +7,7 @@ use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::format; -use crate::std::string::String; +use crate::std::{format, string::String}; impl AssertBoolean for Spec<'_, bool, D, R> where diff --git a/src/char/mod.rs b/src/char/mod.rs index 48644c1..ad90192 100644 --- a/src/char/mod.rs +++ b/src/char/mod.rs @@ -9,8 +9,10 @@ use crate::spec::{ DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Spec, }; -use crate::std::format; -use crate::std::string::{String, ToString}; +use crate::std::{ + format, + string::{String, ToString}, +}; impl AssertChar for Spec<'_, char, D, R> where diff --git a/src/char_count.rs b/src/char_count.rs index c760341..304ffe0 100644 --- a/src/char_count.rs +++ b/src/char_count.rs @@ -11,9 +11,7 @@ use crate::properties::CharCountProperty; use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Represent, Represented, Spec, }; -use crate::std::format; -use crate::std::ops::RangeBounds; -use crate::std::string::String; +use crate::std::{format, ops::RangeBounds, string::String}; impl AssertHasCharCount for Spec<'_, S, D, R> where diff --git a/src/colored/mod.rs b/src/colored/mod.rs index a583f5b..b2612cd 100644 --- a/src/colored/mod.rs +++ b/src/colored/mod.rs @@ -52,10 +52,13 @@ pub use with_colored_feature::{ }; use crate::spec::{DiffFormat, DisplayRepresentation, Highlight, Represent, Represented}; -use crate::std::format; -use crate::std::string::{String, ToString}; -use crate::std::vec::Vec; +use crate::std::{ + format, + string::{String, ToString}, + vec::Vec, +}; use hashbrown::HashSet; + #[cfg(feature = "colored")] use with_colored_feature::{ configured_diff_format_impl, mark_diff_impl, mark_missing_impl, mark_unexpected_impl, diff --git a/src/derived_spec/mod.rs b/src/derived_spec/mod.rs index 54d6976..bec710f 100644 --- a/src/derived_spec/mod.rs +++ b/src/derived_spec/mod.rs @@ -40,16 +40,18 @@ use crate::spec::{ Location, PanicOnFail, Represent, Represented, RepresentedAs, RepresentedBy, Satisfies, SoftPanic, Spec, }; -use crate::std::borrow::{Cow, ToOwned}; -use crate::std::boxed::Box; -use crate::std::error::Error; -use crate::std::fmt; -use crate::std::fmt::{Debug, Display}; -use crate::std::format; -use crate::std::ops::RangeBounds; -use crate::std::slice; -use crate::std::string::{String, ToString}; -use crate::std::vec::Vec; +use crate::std::{ + borrow::{Cow, ToOwned}, + boxed::Box, + error::Error, + fmt, + fmt::{Debug, Display}, + format, + ops::RangeBounds, + slice, + string::{String, ToString}, + vec::Vec, +}; use hashbrown::HashSet; /// A `DerivedSpec` does assertions on a derived subject while keeping track diff --git a/src/derived_spec/tests.rs b/src/derived_spec/tests.rs index 4d855f1..10e550f 100644 --- a/src/derived_spec/tests.rs +++ b/src/derived_spec/tests.rs @@ -1,7 +1,9 @@ use crate::prelude::*; -use crate::std::string::{String, ToString}; -use crate::std::vec; -use crate::std::vec::Vec; +use crate::std::{ + string::{String, ToString}, + vec, + vec::Vec, +}; #[cfg(feature = "bigdecimal")] use bigdecimal::BigDecimal; #[cfg(feature = "float-cmp")] @@ -939,6 +941,8 @@ fn extracting_ref_vec_contains_all_in_order() { mod iterator_all_elements { use super::*; + #[cfg(feature = "std")] + use crate::assert_panic_message; #[derive(Debug, Clone)] struct Person { @@ -988,16 +992,19 @@ mod iterator_all_elements { }); } + #[cfg(feature = "std")] #[test] - #[should_panic = "expected numbers.val[1] to be not equal to 4\n but was: 4\n expected: not 4\n"] fn assert_each_element_of_an_iterator_panics_if_one_assertion_fails() { let subject = Numbers(vec![2, 4, 6, 8, 10]); - assert_that(subject) - .named("numbers") - .extracting_ref("val", |numbers| &numbers.0) - .is_not_empty() - .each_element(|e| e.is_not_equal_to(4)); + assert_panic_message!( + assert_that(subject) + .named("numbers") + .extracting_ref("val", |numbers| &numbers.0) + .is_not_empty() + .each_element(|e| e.is_not_equal_to(4)), + "expected numbers.val[1] to be not equal to 4\n but was: 4\n expected: not 4\n" + ); } #[test] diff --git a/src/env.rs b/src/env.rs index 649545c..89ccfce 100644 --- a/src/env.rs +++ b/src/env.rs @@ -14,8 +14,7 @@ pub use fake_env::*; #[cfg(test)] mod fake_env { - use crate::std::cell::RefCell; - use crate::std::env::VarError; + use crate::std::{cell::RefCell, env::VarError}; use fakeenv::EnvStore; thread_local! { diff --git a/src/equality.rs b/src/equality.rs index ae5dbe6..e30093a 100644 --- a/src/equality.rs +++ b/src/equality.rs @@ -12,9 +12,11 @@ use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::fmt::{Debug, Display}; -use crate::std::format; -use crate::std::string::{String, ToString}; +use crate::std::{ + fmt::{Debug, Display}, + format, + string::{String, ToString}, +}; impl AssertEquality for Spec<'_, S, D, R> where diff --git a/src/error/mod.rs b/src/error/mod.rs index 469502c..3c2f386 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -7,9 +7,11 @@ use crate::spec::{ DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Spec, }; -use crate::std::error::Error; -use crate::std::format; -use crate::std::string::{String, ToString}; +use crate::std::{ + error::Error, + format, + string::{String, ToString}, +}; impl<'a, S, D, R> AssertErrorHasSource for Spec<'a, S, D, R> where diff --git a/src/error/tests.rs b/src/error/tests.rs index 3f08477..4c1fab7 100644 --- a/src/error/tests.rs +++ b/src/error/tests.rs @@ -1,7 +1,9 @@ use crate::prelude::*; -use crate::std::error::Error; -use crate::std::fmt::{self, Display}; -use crate::std::vec::Vec; +use crate::std::{ + error::Error, + fmt::{self, Display}, + vec::Vec, +}; #[derive(Debug)] struct SuperError { diff --git a/src/expectations.rs b/src/expectations.rs index 424c3a9..c0dc191 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -3,8 +3,7 @@ #![allow(missing_docs)] #![warn(clippy::return_self_not_must_use)] -use crate::std::marker::PhantomData; -use crate::std::{string::String, vec::Vec}; +use crate::std::{marker::PhantomData, string::String, vec::Vec}; use hashbrown::HashSet; #[cfg(feature = "regex")] use regex::Regex; diff --git a/src/iterator/mod.rs b/src/iterator/mod.rs index 818c6ee..0496d67 100644 --- a/src/iterator/mod.rs +++ b/src/iterator/mod.rs @@ -23,10 +23,7 @@ use crate::spec::{ DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, GetFailures, Invertible, PanicOnFail, Represent, Represented, RepresentedBy, Spec, }; -use crate::std::borrow::ToOwned; -use crate::std::cmp::Ordering; -use crate::std::mem; -use crate::std::{format, string::String, vec, vec::Vec}; +use crate::std::{borrow::ToOwned, cmp::Ordering, format, mem, string::String, vec, vec::Vec}; use hashbrown::HashSet; impl<'a, S, T, E, D, R> AssertIteratorContains for Spec<'a, S, D, R> diff --git a/src/iterator/tests.rs b/src/iterator/tests.rs index abb09a3..df31ae7 100644 --- a/src/iterator/tests.rs +++ b/src/iterator/tests.rs @@ -1,6 +1,7 @@ +#[cfg(feature = "std")] +use crate::assert_panic_message; use crate::prelude::*; -use crate::std::string::String; -use crate::std::{vec, vec::Vec}; +use crate::std::{string::String, vec, vec::Vec}; #[derive(Debug)] struct CustomCollection { @@ -295,15 +296,18 @@ mod all_elements { .each_element(|person| person.extracting("name", |p| &p.name).starts_with('J')); } + #[cfg(feature = "std")] #[test] - #[should_panic = "expected numbers [1] to be not equal to 4\n but was: 4\n expected: not 4\n"] fn assert_each_element_of_an_iterator_panics_if_one_assertion_fails() { let subject = [2, 4, 6, 8, 10]; - assert_that(subject) - .named("numbers") - .is_not_empty() - .each_element(|e| e.is_not_equal_to(4)); + assert_panic_message!( + assert_that(subject) + .named("numbers") + .is_not_empty() + .each_element(|e| e.is_not_equal_to(4)), + "expected numbers [1] to be not equal to 4\n but was: 4\n expected: not 4\n" + ); } #[test] diff --git a/src/length.rs b/src/length.rs index 57c5b4e..c5e8fc4 100644 --- a/src/length.rs +++ b/src/length.rs @@ -12,8 +12,7 @@ use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::ops::RangeBounds; -use crate::std::{format, string::String}; +use crate::std::{format, ops::RangeBounds, string::String}; impl AssertEmptiness for Spec<'_, S, D, R> where diff --git a/src/lib.rs b/src/lib.rs index 95eef23..a4683ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1114,6 +1114,7 @@ pub mod assertions; pub mod colored; pub mod derived_spec; pub mod expectations; +pub mod panic; pub mod prelude; pub mod properties; #[cfg(feature = "recursive")] @@ -1147,7 +1148,7 @@ mod order; #[cfg(feature = "std")] mod os_sting; #[cfg(feature = "panic")] -mod panic; +mod panic_assertion; mod predicate; mod range; mod representation; diff --git a/src/map/mod.rs b/src/map/mod.rs index d7f271d..41c369e 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -15,9 +15,7 @@ use crate::spec::{ DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::format; -use crate::std::string::String; -use crate::std::vec::Vec; +use crate::std::{format, string::String, vec::Vec}; use hashbrown::HashSet; impl AssertMapContainsKey for Spec<'_, S, D, R> @@ -579,8 +577,7 @@ mod hashbrown_impls { #[cfg(feature = "std")] mod std_hashmap_impls { use crate::properties::MapProperties; - use crate::std::collections::HashMap; - use crate::std::iter::Iterator; + use crate::std::{collections::HashMap, iter::Iterator}; impl MapProperties for HashMap { type Key = K; @@ -636,8 +633,7 @@ mod std_hashmap_impls { mod btree_map_impls { use crate::properties::MapProperties; - use crate::std::collections::BTreeMap; - use crate::std::iter::Iterator; + use crate::std::{collections::BTreeMap, iter::Iterator}; impl MapProperties for BTreeMap { type Key = K; diff --git a/src/map/tests.rs b/src/map/tests.rs index 0c45862..16db13e 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -1,7 +1,6 @@ mod hashbrown { use crate::prelude::*; - use crate::std::format; - use crate::std::vec::Vec; + use crate::std::{format, vec::Vec}; use hashbrown::HashMap; #[test] @@ -402,8 +401,7 @@ mod hashbrown { #[cfg(feature = "std")] mod std_hash_map { use crate::prelude::*; - use crate::std::collections::HashMap; - use crate::std::format; + use crate::std::{collections::HashMap, format}; #[test] fn hashmap_is_empty() { @@ -730,9 +728,7 @@ mod std_hash_map { mod btree_map { use crate::prelude::*; - use crate::std::collections::BTreeMap; - use crate::std::format; - use crate::std::vec::Vec; + use crate::std::{collections::BTreeMap, format, vec::Vec}; #[test] fn btree_map_is_empty() { @@ -1055,8 +1051,7 @@ mod btree_map { #[cfg(feature = "colored")] mod colored { use crate::prelude::*; - use crate::std::format; - use crate::std::vec::Vec; + use crate::std::{format, vec::Vec}; use hashbrown::HashMap; #[test] diff --git a/src/mapping.rs b/src/mapping.rs index 7f09253..2e58670 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -1,8 +1,10 @@ use crate::assertions::{AssertDebugString, AssertDisplayString}; use crate::spec::{DebugRepresentation, FailingStrategy, Spec}; -use crate::std::fmt::{Debug, Display}; -use crate::std::format; -use crate::std::string::{String, ToString}; +use crate::std::{ + fmt::{Debug, Display}, + format, + string::{String, ToString}, +}; impl<'a, S, D, R> AssertDebugString for Spec<'a, S, D, R> where diff --git a/src/number.rs b/src/number.rs index 317e9a3..af95558 100644 --- a/src/number.rs +++ b/src/number.rs @@ -17,8 +17,7 @@ use crate::spec::{ DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::format; -use crate::std::string::String; +use crate::std::{format, string::String}; impl AssertSignum for Spec<'_, S, D, R> where diff --git a/src/option/mod.rs b/src/option/mod.rs index 096aa20..774990b 100644 --- a/src/option/mod.rs +++ b/src/option/mod.rs @@ -48,7 +48,7 @@ where let value_representation = self.representation().clone(); self.mapping(|subject| match subject { None => { - panic!("expected the subject to be `Some(_)`, but was `None`") + crate::panic::trigger_panic("expected the subject to be `Some(_)`, but was `None`") }, Some(value) => value, }) @@ -67,7 +67,7 @@ where let value_representation = self.representation().clone(); self.mapping(|subject| match subject { None => { - panic!("expected the subject to be `Some(_)`, but was `None`") + crate::panic::trigger_panic("expected the subject to be `Some(_)`, but was `None`") }, Some(value) => value, }) diff --git a/src/panic/mod.rs b/src/panic/mod.rs index a09bfe2..74bfced 100644 --- a/src/panic/mod.rs +++ b/src/panic/mod.rs @@ -1,157 +1,178 @@ -//! Implementation of assertions for code that should or should not panic. +//! Handle panics in `std` and `no_std` environments. +//! +//! In assertion functions we use the [`trigger_panic`] function of this module +//! which supports a custom payload in panics when in `std` environment and +//! a string-formatted payload in `no_std` environments. The custom payload adds +//! the ability to verify the location of the test assertion by using the +//! [`assert_panic_location`] macro. +//! +//! The macros [`assert_panic_location!`] and [`assert_panic_message!`] provide +//! a convenient way for verifying the panic location and the panic message. +//! Note: These macros are intended for testing of assertion functions itself +//! but not for being used in end-user tests testing a project's code. +//! +//! [`assert_panic_location!`]: crate::assert_panic_location +//! [`assert_panic_message!`]: crate::assert_panic_message -use crate::assertions::AssertCodePanics; -use crate::colored::{mark_missing, mark_unexpected}; -use crate::expectations::{DoesNotPanic, DoesPanic, does_not_panic, does_panic}; -use crate::spec::{ - Code, DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, - Expression, FailingStrategy, Spec, -}; -use crate::std::any::Any; -use crate::std::panic; +use crate::std::string::String; -const ONLY_ONE_EXPECTATION: &str = "only one expectation allowed when asserting closures!"; -const UNKNOWN_PANIC_MESSAGE: &str = ""; - -impl<'a, S, D, R> AssertCodePanics for Spec<'a, Code, D, R> -where - S: FnOnce(), - R: FailingStrategy, -{ - type Mapped = Spec<'a, (), DebugRepresentation, R>; - - fn does_not_panic(self) -> Self::Mapped { - self.expecting(does_not_panic()).mapping(|_| ()) +/// Panics with the given message and the location of the caller. +/// +/// Using this function instead of the std `panic!` macro provides the +/// possibility to verify the panic location of assertions by using the macro +/// [`assert_panic_location!`]. +/// +/// In `std`-environments this function triggers a `std::panic::panic_any` with +/// a payload of type [`AssertionPanicPayload`]. In `no_std`-environments it +/// panics with a string-formatted payload. +/// +/// # Panics +/// +/// Calling this function **always** leads to a panic. +/// +/// * If the `std` crate feature is active, the panic is triggered by calling +/// [`std::panic::panic_any`] with a payload of type [`AssertionPanicPayload`]. +/// * In `no_std`-environments it panics with a string-formatted payload using +/// the [`std::panic!`] macro. +/// +/// [`assert_panic_location!`]: crate::assert_panic_location +#[track_caller] +pub fn trigger_panic(message: impl Into) -> ! { + #[cfg(feature = "std")] + { + std::panic::panic_any(AssertionPanicPayload { + message: message.into(), + location: core::panic::Location::caller(), + }) } - - fn panics(self) -> Self::Mapped { - self.expecting(does_panic()).mapping(|_| ()) - } - - fn panics_with_message(self, message: impl Into) -> Self::Mapped { - self.expecting(does_panic().with_message(message)) - .mapping(|_| ()) + #[cfg(not(feature = "std"))] + { + let message = message.into(); + crate::std::panic!("{message}") } } -impl Expectation, D> for DoesNotPanic -where - S: FnOnce(), -{ - fn test(&mut self, subject: &Code) -> bool { - if let Some(function) = subject.take() { - let result = panic::catch_unwind(panic::AssertUnwindSafe(function)); - match result { - Ok(()) => true, - Err(panic_message) => { - self.actual_message = Some(panic_message); - false - }, - } - } else { - self.actual_message = Some(Box::new(ONLY_ONE_EXPECTATION)); - false - } - } +/// A payload for panic calls that contains the panic message and the panic +/// location. +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[derive(Debug)] +pub struct AssertionPanicPayload { + /// The panic message. + pub message: String, + /// The location where the panic occurred. + pub location: &'static core::panic::Location<'static>, +} - fn message( - &self, - expression: &Expression<'_>, - _actual: &Code, - _inverted: bool, - _representation: &D, - format: &DiffFormat, - ) -> String { - let panic_message = read_panic_message(self.actual_message.as_ref()) - .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string()); +#[cfg(feature = "std")] +mod std_impl { + use super::AssertionPanicPayload; + use crate::std::{fmt, fmt::Display}; - if panic_message == ONLY_ONE_EXPECTATION { - format!("error in test assertion: {ONLY_ONE_EXPECTATION}") - } else { - let marked_did_panic = mark_unexpected("did panic", &DisplayRepresentation, format); - let marked_panic_message = - mark_unexpected(&panic_message, &DisplayRepresentation, format); - format!( - "expected {expression} to not panic, but {marked_did_panic}\n with message: \"{marked_panic_message}\"" - ) + impl Display for AssertionPanicPayload { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) } } } -impl Expectation, D> for DoesPanic -where - S: FnOnce(), -{ - fn test(&mut self, subject: &Code) -> bool { - if let Some(function) = subject.take() { - let result = panic::catch_unwind(panic::AssertUnwindSafe(function)); - match result { - Ok(()) => false, - Err(panic_message) => { - let panic_message = read_panic_message(Some(panic_message).as_ref()) - .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string()); - let test_result = if let Some(expected_message) = &self.expected_message { - &panic_message == expected_message - } else { - // did panic - panic message should not be asserted - true - }; - self.actual_message = Some(panic_message); - test_result - }, - } - } else { - self.actual_message = Some(ONLY_ONE_EXPECTATION.to_string()); - false - } - } +/// Verifies that an assertion panics at the location in the tests. +/// +/// This macro is intended for testing of assertion functions itself but not +/// for being used in end-user tests testing a project's code. +/// +/// The expected location is taken from the line where the macro call starts. +/// This macro works only properly if the given assertion is located at the +/// same line as the macro call. For longer lines it might be necessary to +/// suppress automatic code formatting using the attribute `#[rustfmt::skip]`. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// use asserting::assert_panic_location; +/// +/// assert_panic_location!(assert_that!(41).is_equal_to(42)); +/// +/// #[rustfmt::skip] +/// assert_panic_location!(assert_that!("some longer assertion").starts_with("some").contains("much longer")); +/// ``` +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[macro_export] +macro_rules! assert_panic_location { + ($expression:expr) => {{ + let expected_line = line!(); + let expected_file = file!(); - fn message( - &self, - expression: &Expression<'_>, - _actual: &Code, - _inverted: bool, - _representation: &D, - format: &DiffFormat, - ) -> String { - if let Some(actual_message) = self.actual_message.as_ref() { - if actual_message == ONLY_ONE_EXPECTATION { - format!("error in test assertion: {ONLY_ONE_EXPECTATION}") - } else if let Some(expected_message) = &self.expected_message { - let marked_expected_message = - mark_missing(expected_message, &DisplayRepresentation, format); - let marked_actual_message = - mark_unexpected(actual_message, &DisplayRepresentation, format); - format!( - "expected {expression} to panic with message {expected_message:?}\n but was: \"{marked_actual_message}\"\n expected: \"{marked_expected_message}\"" - ) - } else { - // should be unreachable - format!("expected {expression} to panic, but did not panic") - } - } else if let Some(expected_message) = &self.expected_message { - let marked_did_not_panic = - mark_unexpected("did not panic", &DisplayRepresentation, format); - format!( - "expected {expression} to panic with message {expected_message:?},\n but {marked_did_not_panic}" - ) - } else { - let marked_did_not_panic = - mark_unexpected("did not panic", &DisplayRepresentation, format); - format!("expected {expression} to panic, but {marked_did_not_panic}") - } - } -} + let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| { + $expression; + })); + + let panic_error = result.expect_err("the assertion should have panicked!"); + let panic_payload = panic_error + .downcast_ref::<$crate::panic::AssertionPanicPayload>() + .expect("panic is not caused by an assertion!"); + + // normalize file path + let expected_file = expected_file.replace("\\", "/"); + let actual_file = panic_payload.location.file().replace("\\", "/"); + let actual_line = panic_payload.location.line(); -fn read_panic_message(error: Option<&Box>) -> Option { - error.and_then(|message| { - let message = &**message; - message - .downcast_ref::() - .cloned() - .or_else(|| message.downcast_ref::<&str>().map(ToString::to_string)) - }) + assert!( + actual_file == expected_file && actual_line == expected_line, + "wrong location in panic message!\n expected location: {expected_file}:{expected_line}\n actual location: {actual_file}:{actual_line}", + ); + }}; } -#[cfg(test)] -mod tests; +/// Verifies that an assertion panics with the given message. +/// +/// This macro is intended for testing of assertion functions itself but not +/// for being used in end-user tests testing a project's code. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// use asserting::assert_panic_message; +/// +/// assert_panic_message!( +/// assert_that!(41) +/// .with_diff_format(DIFF_FORMAT_RED_YELLOW) +/// .is_zero(), +/// "expected 41 to be zero\n but was: \u{1b}[31m41\u{1b}[0m\n expected: \u{1b}[33m0\u{1b}[0m\n" +/// ); +/// +/// assert_panic_message!( +/// assert_that!(-42) +/// .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) +/// .is_positive(), +/// "expected -42 to be positive\n but was: -42\n expected: > 0\n" +/// ); +/// ``` +#[cfg(feature = "std")] +#[macro_export] +macro_rules! assert_panic_message { + ($expression:expr, $expected_message:expr) => {{ + let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| { + $expression; + })); + + let panic_error = result.expect_err("the assertion should have panicked!"); + + let actual_message = panic_error + .downcast_ref::<$crate::panic::AssertionPanicPayload>() + .map(|payload| payload.message.clone()) + .or_else(|| panic_error.downcast_ref::().cloned()) + .or_else(|| panic_error.downcast_ref::<&str>().map(ToString::to_string)) + .expect("panic is not caused by an assertion!"); + + let expected_message = $expected_message; + + assert_eq!( + &actual_message, expected_message, + "the panic message differs from the expected one!\n expected message: {expected_message}\n actual message: {actual_message}", + ); + }}; +} diff --git a/src/panic_assertion/mod.rs b/src/panic_assertion/mod.rs new file mode 100644 index 0000000..4aec597 --- /dev/null +++ b/src/panic_assertion/mod.rs @@ -0,0 +1,158 @@ +//! Implementation of assertions for code that should or should not panic. + +use crate::assertions::AssertCodePanics; +use crate::colored::{mark_missing, mark_unexpected}; +use crate::expectations::{DoesNotPanic, DoesPanic, does_not_panic, does_panic}; +use crate::panic::AssertionPanicPayload; +use crate::spec::{ + Code, DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, + Expression, FailingStrategy, Spec, +}; +use core::any::Any; + +const ONLY_ONE_EXPECTATION: &str = "only one expectation allowed when asserting closures!"; +const UNKNOWN_PANIC_MESSAGE: &str = ""; + +impl<'a, S, D, R> AssertCodePanics for Spec<'a, Code, D, R> +where + S: FnOnce(), + R: FailingStrategy, +{ + type Mapped = Spec<'a, (), DebugRepresentation, R>; + + fn does_not_panic(self) -> Self::Mapped { + self.expecting(does_not_panic()).mapping(|_| ()) + } + + fn panics(self) -> Self::Mapped { + self.expecting(does_panic()).mapping(|_| ()) + } + + fn panics_with_message(self, message: impl Into) -> Self::Mapped { + self.expecting(does_panic().with_message(message)) + .mapping(|_| ()) + } +} + +impl Expectation, D> for DoesNotPanic +where + S: FnOnce(), +{ + fn test(&mut self, subject: &Code) -> bool { + if let Some(function) = subject.take() { + let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(function)); + match result { + Ok(()) => true, + Err(panic_message) => { + self.actual_message = Some(panic_message); + false + }, + } + } else { + self.actual_message = Some(Box::new(ONLY_ONE_EXPECTATION)); + false + } + } + + fn message( + &self, + expression: &Expression<'_>, + _actual: &Code, + _inverted: bool, + _representation: &D, + format: &DiffFormat, + ) -> String { + let panic_message = read_panic_message(self.actual_message.as_ref()) + .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string()); + + if panic_message == ONLY_ONE_EXPECTATION { + format!("error in test assertion: {ONLY_ONE_EXPECTATION}") + } else { + let marked_did_panic = mark_unexpected("did panic", &DisplayRepresentation, format); + let marked_panic_message = + mark_unexpected(&panic_message, &DisplayRepresentation, format); + format!( + "expected {expression} to not panic, but {marked_did_panic}\n with message: \"{marked_panic_message}\"" + ) + } + } +} + +impl Expectation, D> for DoesPanic +where + S: FnOnce(), +{ + fn test(&mut self, subject: &Code) -> bool { + if let Some(function) = subject.take() { + let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(function)); + match result { + Ok(()) => false, + Err(panic_message) => { + let panic_message = read_panic_message(Some(panic_message).as_ref()) + .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string()); + let test_result = if let Some(expected_message) = &self.expected_message { + &panic_message == expected_message + } else { + // did panic - panic message should not be asserted + true + }; + self.actual_message = Some(panic_message); + test_result + }, + } + } else { + self.actual_message = Some(ONLY_ONE_EXPECTATION.to_string()); + false + } + } + + fn message( + &self, + expression: &Expression<'_>, + _actual: &Code, + _inverted: bool, + _representation: &D, + format: &DiffFormat, + ) -> String { + if let Some(actual_message) = self.actual_message.as_ref() { + if actual_message == ONLY_ONE_EXPECTATION { + format!("error in test assertion: {ONLY_ONE_EXPECTATION}") + } else if let Some(expected_message) = &self.expected_message { + let marked_expected_message = + mark_missing(expected_message, &DisplayRepresentation, format); + let marked_actual_message = + mark_unexpected(actual_message, &DisplayRepresentation, format); + format!( + "expected {expression} to panic with message {expected_message:?}\n but was: \"{marked_actual_message}\"\n expected: \"{marked_expected_message}\"" + ) + } else { + // should be unreachable + format!("expected {expression} to panic, but did not panic") + } + } else if let Some(expected_message) = &self.expected_message { + let marked_did_not_panic = + mark_unexpected("did not panic", &DisplayRepresentation, format); + format!( + "expected {expression} to panic with message {expected_message:?},\n but {marked_did_not_panic}" + ) + } else { + let marked_did_not_panic = + mark_unexpected("did not panic", &DisplayRepresentation, format); + format!("expected {expression} to panic, but {marked_did_not_panic}") + } + } +} + +fn read_panic_message(error: Option<&Box>) -> Option { + error.and_then(|message| { + let message = &**message; + message + .downcast_ref::() + .map(|payload| payload.message.clone()) + .or_else(|| message.downcast_ref::().cloned()) + .or_else(|| message.downcast_ref::<&str>().map(ToString::to_string)) + }) +} + +#[cfg(test)] +mod tests; diff --git a/src/panic/tests.rs b/src/panic_assertion/tests.rs similarity index 100% rename from src/panic/tests.rs rename to src/panic_assertion/tests.rs diff --git a/src/range/mod.rs b/src/range/mod.rs index 7f3c806..ead56b2 100644 --- a/src/range/mod.rs +++ b/src/range/mod.rs @@ -8,9 +8,11 @@ use crate::spec::{ DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Represented, Spec, }; -use crate::std::format; -use crate::std::ops::{Bound, Range, RangeBounds, RangeInclusive}; -use crate::std::string::String; +use crate::std::{ + format, + ops::{Bound, Range, RangeBounds, RangeInclusive}, + string::String, +}; impl IsEmptyProperty for Range where diff --git a/src/recursive_comparison/macros/tests.rs b/src/recursive_comparison/macros/tests.rs index 1d917f9..d1b987f 100644 --- a/src/recursive_comparison/macros/tests.rs +++ b/src/recursive_comparison/macros/tests.rs @@ -1,6 +1,5 @@ use crate::recursive_comparison::value::*; -use crate::std::string::ToString; -use crate::std::vec::Vec; +use crate::std::{string::ToString, vec::Vec}; #[test] fn false_value() { diff --git a/src/recursive_comparison/mod.rs b/src/recursive_comparison/mod.rs index ae35728..e572634 100644 --- a/src/recursive_comparison/mod.rs +++ b/src/recursive_comparison/mod.rs @@ -325,10 +325,13 @@ use crate::spec::{ AssertFailure, CollectFailures, DiffFormat, DoFail, FailingStrategy, GetFailures, SoftPanic, Spec, }; -use crate::std::fmt::{self, Display}; -use crate::std::string::{String, ToString}; -use crate::std::vec::Vec; -use crate::std::{format, vec}; +use crate::std::{ + fmt::{self, Display}, + format, + string::{String, ToString}, + vec, + vec::Vec, +}; use serde_core::Serialize; /// Data of an actual assertion in field-by-field recursive comparison mode. diff --git a/src/recursive_comparison/path/mod.rs b/src/recursive_comparison/path/mod.rs index db4c9a4..e6fa764 100644 --- a/src/recursive_comparison/path/mod.rs +++ b/src/recursive_comparison/path/mod.rs @@ -2,13 +2,15 @@ //! //! [`Value`]: crate::recursive_comparison::value::Value -use crate::std::borrow::Cow; -use crate::std::borrow::ToOwned; -use crate::std::fmt; -use crate::std::fmt::{Debug, Display}; -use crate::std::string::String; -use crate::std::vec; -use crate::std::vec::Vec; +use crate::std::{ + borrow::Cow, + borrow::ToOwned, + fmt, + fmt::{Debug, Display}, + string::String, + vec, + vec::Vec, +}; /// Defines a path to a field in a struct, tuple, or enum variant. /// diff --git a/src/recursive_comparison/path/tests.rs b/src/recursive_comparison/path/tests.rs index e6147bc..8e4c1c4 100644 --- a/src/recursive_comparison/path/tests.rs +++ b/src/recursive_comparison/path/tests.rs @@ -1,6 +1,5 @@ use super::*; -use crate::std::format; -use crate::std::string::ToString; +use crate::std::{format, string::ToString}; #[test] fn path_from_empty_str() { diff --git a/src/recursive_comparison/serialize/mod.rs b/src/recursive_comparison/serialize/mod.rs index d432947..fcc3bdd 100644 --- a/src/recursive_comparison/serialize/mod.rs +++ b/src/recursive_comparison/serialize/mod.rs @@ -2,12 +2,14 @@ use crate::recursive_comparison::value::{Field, Number}; use crate::recursive_comparison::value::{Map, Value}; -use crate::std::borrow::Cow; -use crate::std::error::Error as StdError; -use crate::std::fmt::{self, Display}; -use crate::std::string::{String, ToString}; -use crate::std::vec; -use crate::std::vec::Vec; +use crate::std::{ + borrow::Cow, + error::Error as StdError, + fmt::{self, Display}, + string::{String, ToString}, + vec, + vec::Vec, +}; use serde_core::ser::Error as SerdeError; use serde_core::{Serialize, Serializer, ser}; diff --git a/src/recursive_comparison/value/map/mod.rs b/src/recursive_comparison/value/map/mod.rs index 5d1cd32..144c809 100644 --- a/src/recursive_comparison/value/map/mod.rs +++ b/src/recursive_comparison/value/map/mod.rs @@ -2,11 +2,13 @@ //! structure used for recursive comparison. use super::Value; -use crate::std::borrow::{Borrow, Cow}; -use crate::std::cmp::Ordering; -use crate::std::fmt::{self, Debug}; -use crate::std::format; -use crate::std::hash::{Hash, Hasher}; +use crate::std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + fmt::{self, Debug}, + format, + hash::{Hash, Hasher}, +}; use indexmap::IndexMap; use rapidhash::quality::RandomState; diff --git a/src/recursive_comparison/value/mod.rs b/src/recursive_comparison/value/mod.rs index 249be4a..6450b02 100644 --- a/src/recursive_comparison/value/mod.rs +++ b/src/recursive_comparison/value/mod.rs @@ -7,11 +7,13 @@ mod number; pub mod proptest_support; use crate::recursive_comparison::path::Path; -use crate::std::borrow::Cow; -use crate::std::fmt::{self, Debug}; -use crate::std::string::{String, ToString}; -use crate::std::vec::Vec; -use crate::std::{format, vec}; +use crate::std::{ + borrow::Cow, + fmt::{self, Debug}, + string::{String, ToString}, + vec::Vec, + {format, vec}, +}; pub use map::Map; pub use number::{F32, F64, Number}; diff --git a/src/recursive_comparison/value/number/mod.rs b/src/recursive_comparison/value/number/mod.rs index 945a1ae..9425656 100644 --- a/src/recursive_comparison/value/number/mod.rs +++ b/src/recursive_comparison/value/number/mod.rs @@ -3,8 +3,11 @@ //! //! [`Value`]: crate::recursive_comparison::value::Value -use crate::std::borrow::Cow; -use crate::std::fmt::{self, Debug, Display}; +use crate::std::{ + borrow::Cow, + fmt, + fmt::{Debug, Display}, +}; /// Represents a number in the [`Value`] data structure used for recursive /// comparison. diff --git a/src/recursive_comparison/value/number/tests.rs b/src/recursive_comparison/value/number/tests.rs index c171eda..493f02e 100644 --- a/src/recursive_comparison/value/number/tests.rs +++ b/src/recursive_comparison/value/number/tests.rs @@ -1,8 +1,6 @@ use super::*; use crate::recursive_comparison::value::proptest_support::*; -use crate::std::cmp::Ordering; -use crate::std::format; -use crate::std::hash::BuildHasher; +use crate::std::{cmp::Ordering, format, hash::BuildHasher}; use hashbrown::DefaultHashBuilder; use proptest::prelude::*; diff --git a/src/recursive_comparison/value/proptest_support.rs b/src/recursive_comparison/value/proptest_support.rs index 7036e3a..ffa0e10 100644 --- a/src/recursive_comparison/value/proptest_support.rs +++ b/src/recursive_comparison/value/proptest_support.rs @@ -4,8 +4,7 @@ //! generating arbitrary values of type [`Value`] and [`Number`]. use crate::recursive_comparison::value::{F32, F64, Number, Value}; -use crate::std::string::String; -use crate::std::vec; +use crate::std::{string::String, vec}; use proptest::prelude::*; /// Returns a [`Strategy`] for generating arbitrary values of type [`Value`]. diff --git a/src/result/mod.rs b/src/result/mod.rs index 7a9436a..32b8614 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -58,7 +58,9 @@ where Ok(value) => value, Err(error) => { let error = Represented::from((&error, &representation)); - panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") + crate::panic::trigger_panic(format!( + "expected the subject to be `Ok(_)`, but was `Err({error:?})`" + )) }, }) .represented_by(representation) @@ -69,7 +71,9 @@ where self.mapping(|subject| match subject { Ok(value) => { let value = Represented::from((&value, &representation)); - panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") + crate::panic::trigger_panic(format!( + "expected the subject to be `Err(_)`, but was `Ok({value:?})`" + )) }, Err(error) => error, }) @@ -90,7 +94,9 @@ where Ok(value) => value, Err(error) => { let error = Represented::from((error, &representation)); - panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") + crate::panic::trigger_panic(format!( + "expected the subject to be `Ok(_)`, but was `Err({error:?})`" + )) }, }) .represented_by(representation) @@ -101,7 +107,9 @@ where self.mapping(|subject| match subject { Ok(value) => { let value = Represented::from((value, &representation)); - panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") + crate::panic::trigger_panic(format!( + "expected the subject to be `Err(_)`, but was `Ok({value:?})`" + )) }, Err(error) => error, }) @@ -172,9 +180,9 @@ where Err(error) => Err(error.to_string()), }; self.mapping(|_result| match subject { - Ok(value) => panic!( + Ok(value) => crate::panic::trigger_panic(format!( r"expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" - ), + )), Err(error) => error, }) .expecting(is_equal_to(expected)) @@ -200,9 +208,9 @@ where Err(error) => Err(error.to_string()), }; self.mapping(|_result| match subject { - Ok(value) => panic!( + Ok(value) => crate::panic::trigger_panic(format!( r"expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" - ), + )), Err(error) => error, }) .expecting(is_equal_to(expected)) diff --git a/src/slice/tests.rs b/src/slice/tests.rs index c1ecb30..49f1705 100644 --- a/src/slice/tests.rs +++ b/src/slice/tests.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use crate::std::slice; use crate::std::{ + slice, string::{String, ToString}, vec, vec::Vec, diff --git a/src/spec/mod.rs b/src/spec/mod.rs index df44bb3..73560a7 100644 --- a/src/spec/mod.rs +++ b/src/spec/mod.rs @@ -6,18 +6,20 @@ use crate::derived_spec::DerivedSpec; use crate::expectations::satisfies; #[cfg(feature = "recursive")] use crate::recursive_comparison::RecursiveComparison; -use crate::std::any; -use crate::std::borrow::{Borrow, Cow, ToOwned}; -use crate::std::boxed::Box; -use crate::std::cmp::Ordering; -use crate::std::error::Error as StdError; -use crate::std::fmt::{self, Debug, Display}; -use crate::std::format; -use crate::std::ops::Deref; -use crate::std::slice; -use crate::std::string::{String, ToString}; -use crate::std::vec; -use crate::std::vec::Vec; +use crate::std::{ + any, + borrow::{Borrow, Cow, ToOwned}, + boxed::Box, + cmp::Ordering, + error::Error as StdError, + fmt::{self, Debug, Display}, + format, + ops::Deref, + slice, + string::{String, ToString}, + vec, + vec::Vec, +}; #[cfg(feature = "panic")] use crate::std::{cell::RefCell, rc::Rc}; @@ -1175,11 +1177,13 @@ pub trait DoFail { /// Fails the assertion with the given [`AssertFailure`]s according to the /// current failing strategy of the `Spec` or other implementing /// spec-like struct. + #[track_caller] fn do_fail_with(&mut self, failures: impl IntoIterator); /// Fails the assertion with the given failure message according to the /// current failing strategy of the `Spec` or other implementing /// spec-like struct. + #[track_caller] fn do_fail_with_message(&mut self, message: impl Into); } @@ -1653,7 +1657,7 @@ impl FailingStrategy for PanicOnFail { .map(ToString::to_string) .collect::>() .join("\n"); - panic!("{}", message); + crate::panic::trigger_panic(message); } } diff --git a/src/spec/tests.rs b/src/spec/tests.rs index d9cd0e0..2ae12c5 100644 --- a/src/spec/tests.rs +++ b/src/spec/tests.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "std")] +use crate::assert_panic_message; use crate::prelude::*; use crate::spec::{AssertFailure, Expression, OwnedLocation}; #[cfg(feature = "colored")] @@ -149,16 +151,17 @@ fn assert_that_macro_with_borrowed_str_subject() { assert_that!(input_string).is_equal_to("adipiscing rebum amet iusto"); } +#[cfg(feature = "std")] #[test] -#[should_panic( - expected = "expected ultimate_answer to be equal to 42\n but was: 51\n expected: 42\n" -)] fn assert_that_macro_is_equal_to_with_integers_fails() { let ultimate_answer = 51; - assert_that!(ultimate_answer) - .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) - .is_equal_to(42); + assert_panic_message!( + assert_that!(ultimate_answer) + .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) + .is_equal_to(42), + "expected ultimate_answer to be equal to 42\n but was: 51\n expected: 42\n" + ); } #[test] @@ -241,67 +244,70 @@ fn soft_assertions_with_chained_assertion_methods() { .soft_panic(); } +#[cfg(feature = "std")] #[test] -#[should_panic = "expected subject to contain \"unimportant\"\n \ - but was: \"the answer to all important questions is 42\"\n \ - expected: \"unimportant\"\n\ - \n\ - expected subject to have at most a length of 41\n \ - but was: 43\n \ - expected: <= 41\n\ -"] fn soft_assertions_panic_once_with_multiple_failure_messages() { let subject = "the answer to all important questions is 42".to_string(); - verify_that(subject) - .contains("unimportant") - .has_at_most_length(41) - .soft_panic(); -} - -#[cfg(feature = "colored")] -#[test] -fn and_called_on_spec_does_nothing() { - let subject = "the answer to all important questions is 42".to_string(); - - let original_spec = verify_that(subject) - .named("answer") - .with_diff_format(DIFF_FORMAT_RED_BLUE) - .is_empty(); - let original_spec_type = type_name_of_val(&original_spec); - let original_subject = original_spec.subject().clone(); - let original_diff_format = original_spec.diff_format().clone(); - let original_failures = original_spec.failures(); - assert!(!original_failures.is_empty()); - - let returned_spec = original_spec.and(); - - assert_eq!(type_name_of_val(&returned_spec), original_spec_type); - assert_eq!(returned_spec.subject(), &original_subject); - assert_eq!(returned_spec.diff_format(), &original_diff_format); - assert_eq!(returned_spec.failures(), original_failures); + assert_panic_message!( + verify_that(subject) + .contains("unimportant") + .has_at_most_length(41) + .soft_panic(), + "expected subject to contain \"unimportant\"\n \ + but was: \"the answer to all important questions is 42\"\n \ + expected: \"unimportant\"\n\ + \n\ + expected subject to have at most a length of 41\n \ + but was: 43\n \ + expected: <= 41\n" + ); } #[cfg(feature = "colored")] mod colored { - use crate::prelude::*; + use super::*; + #[cfg(feature = "std")] #[test] - #[should_panic = "expected subject to contain \"unimportant\"\n \ - but was: \"\u{1b}[31mthe answer to all important questions is 42\u{1b}[0m\"\n \ - expected: \"\u{1b}[32munimportant\u{1b}[0m\"\n\ - \n\ - expected subject to have at most a length of 41\n \ - but was: \u{1b}[31m43\u{1b}[0m\n \ - expected: <= \u{1b}[32m41\u{1b}[0m\n\ -"] fn soft_assertions_panic_message_contains_highlighted_diffs() { let subject = "the answer to all important questions is 42"; - verify_that(subject) - .with_configured_diff_format() - .contains("unimportant") - .has_at_most_length(41) - .soft_panic(); + assert_panic_message!( + verify_that(subject) + .with_configured_diff_format() + .contains("unimportant") + .has_at_most_length(41) + .soft_panic(), + "expected subject to contain \"unimportant\"\n \ + but was: \"\u{1b}[31mthe answer to all important questions is 42\u{1b}[0m\"\n \ + expected: \"\u{1b}[32munimportant\u{1b}[0m\"\n\ + \n\ + expected subject to have at most a length of 41\n \ + but was: \u{1b}[31m43\u{1b}[0m\n \ + expected: <= \u{1b}[32m41\u{1b}[0m\n" + ); + } + + #[test] + fn and_called_on_spec_does_nothing() { + let subject = "the answer to all important questions is 42".to_string(); + + let original_spec = verify_that(subject) + .named("answer") + .with_diff_format(DIFF_FORMAT_RED_BLUE) + .is_empty(); + let original_spec_type = type_name_of_val(&original_spec); + let original_subject = original_spec.subject().clone(); + let original_diff_format = original_spec.diff_format().clone(); + let original_failures = original_spec.failures(); + assert!(!original_failures.is_empty()); + + let returned_spec = original_spec.and(); + + assert_eq!(type_name_of_val(&returned_spec), original_spec_type); + assert_eq!(returned_spec.subject(), &original_subject); + assert_eq!(returned_spec.diff_format(), &original_diff_format); + assert_eq!(returned_spec.failures(), original_failures); } } diff --git a/src/string/mod.rs b/src/string/mod.rs index 7757998..ec79b8b 100644 --- a/src/string/mod.rs +++ b/src/string/mod.rs @@ -770,8 +770,7 @@ mod regex { DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, Spec, }; - use crate::std::format; - use crate::std::string::String; + use crate::std::{format, string::String}; impl AssertStringMatches for Spec<'_, S, D, R> where diff --git a/tests/location_in_panic_message.rs b/tests/location_in_panic_message.rs new file mode 100644 index 0000000..09cdb37 --- /dev/null +++ b/tests/location_in_panic_message.rs @@ -0,0 +1,42 @@ +//! Tests that assure that the location in the panic message of the various +//! assertions is correct. +#![allow(unused_crate_dependencies)] + +#[cfg(feature = "std")] +mod with_std { + use asserting::assert_panic_location; + use asserting::prelude::*; + + #[test] + fn failing_location_of_is_equal_to() { + assert_panic_location!(assert_that!(2).is_equal_to(1)); + } + + #[test] + fn failing_location_of_is_zero() { + assert_panic_location!(assert_that!(2).is_zero()); + } + + #[test] + fn failing_location_of_is_none() { + assert_panic_location!(assert_that!(Some(42)).is_none()); + } + + #[test] + fn failing_location_of_is_err() { + assert_panic_location!(assert_that!(Ok::<_, String>(42)).is_err()); + } +} + +#[cfg(not(feature = "std"))] +mod with_no_std { + use asserting::prelude::*; + + #[test] + #[should_panic = "expected 2 to be equal to 1\n but was: 2\n expected: 1\n"] + fn a_failing_test_panics_in_no_std_environment() { + assert_that!(2) + .with_diff_format(DIFF_FORMAT_NO_HIGHLIGHT) + .is_equal_to(1); + } +}