#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::wrong_self_convention)]
extern crate alloc;
extern crate core;
use actual::Actual;
use alloc::{borrow::ToOwned, boxed::Box, format, string::String, vec::Vec};
use core::{
any::{Any, type_name},
cell::RefCell,
fmt::{Debug, Formatter},
future::Future,
marker::PhantomData,
mem::needs_drop,
panic::{RefUnwindSafe, UnwindSafe},
};
use details::WithDetail;
use failure::Fallible;
use mode::{Capture, Mode, Panic};
use tracking::{AssertionTracking, NumberOfAssertions};
pub mod actual;
#[doc(hidden)]
pub mod assert_that_macro;
pub mod assertions;
pub mod cmp;
pub mod condition;
mod conversion;
pub mod details;
pub mod failure;
pub mod mode;
pub mod tracking;
pub mod util;
pub mod prelude {
#[cfg(feature = "derive")]
pub use assertr_derive::AssertrEq;
pub use crate::AssertThat;
#[cfg(feature = "fluent")]
pub use crate::IntoAssertContext;
pub use crate::any;
#[allow(deprecated)]
pub use crate::assert_that;
#[allow(deprecated)]
pub use crate::assert_that_owned;
#[cfg(feature = "std")]
pub use crate::assert_that_panic_by;
#[cfg(feature = "std")]
pub use crate::assert_that_panic_by_async;
pub use crate::assert_that_type;
pub use crate::assertions::HasLength;
pub use crate::assertions::alloc::prelude::*;
pub use crate::assertions::condition::ConditionAssertions;
pub use crate::assertions::condition::IterableConditionAssertions;
pub use crate::assertions::core::prelude::*;
#[cfg(feature = "http")]
pub use crate::assertions::http::prelude::*;
#[cfg(feature = "jiff")]
pub use crate::assertions::jiff::prelude::*;
#[cfg(feature = "num")]
pub use crate::assertions::num::NumAssertions;
#[cfg(feature = "program")]
pub use crate::assertions::program::Program;
#[cfg(feature = "program")]
pub use crate::assertions::program::ProgramAssertions;
#[cfg(feature = "program")]
pub use crate::assertions::program::ProgramAssertionsRequiringPanicMode;
#[cfg(feature = "reqwest")]
pub use crate::assertions::reqwest::prelude::*;
#[cfg(feature = "rootcause")]
pub use crate::assertions::rootcause::prelude::*;
#[cfg(feature = "std")]
pub use crate::assertions::std::prelude::*;
#[cfg(feature = "tokio")]
pub use crate::assertions::tokio::prelude::*;
pub use crate::condition::Condition;
#[cfg(feature = "serde")]
pub use crate::conversion::json;
#[cfg(feature = "serde")]
pub use crate::conversion::toml;
pub use crate::eq;
pub use crate::mode::Mode;
}
pub struct PanicValue(Box<dyn Any>);
#[deprecated(
since = "0.4.4",
note = "Use the `assert_that!()` macro or the fluent `.must()` / `.verify()` entry points instead."
)]
#[track_caller]
#[must_use]
pub fn assert_that<T>(actual: &T) -> AssertThat<'_, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(actual))
}
#[deprecated(
since = "0.4.4",
note = "Use the `assert_that!()` macro or the fluent `.must_owned()` / `.verify_owned()` entry points instead."
)]
#[track_caller]
#[must_use]
pub fn assert_that_owned<'t, T>(actual: T) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Owned(actual))
}
#[macro_export]
macro_rules! assert_that {
($e:expr) => {
$crate::assert_that_macro::Wrap {
inner: $crate::assert_that_macro::Fallback(core::cell::Cell::new(Some($e))),
}
.into_assert_that()
};
}
#[cfg(feature = "fluent")]
pub trait IntoAssertContext<'t, T> {
#[must_use]
fn must(&'t self) -> AssertThat<'t, T, Panic>;
#[must_use]
fn must_owned(self) -> AssertThat<'t, T, Panic>;
#[must_use]
fn verify(&'t self) -> AssertThat<'t, T, Capture>;
#[must_use]
fn verify_owned(self) -> AssertThat<'t, T, Capture>;
}
#[cfg(feature = "fluent")]
impl<'t, T> IntoAssertContext<'t, T> for T {
fn must(&'t self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(self))
}
fn must_owned(self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Owned(self))
}
fn verify(&'t self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Borrowed(self))
}
fn verify_owned(self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Owned(self))
}
}
#[cfg(feature = "fluent")]
impl<'t, T> IntoAssertContext<'t, T> for &'t T {
fn must(&'t self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(self))
}
fn must_owned(self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(self))
}
fn verify(&'t self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Borrowed(self))
}
fn verify_owned(self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Borrowed(self))
}
}
#[cfg(feature = "fluent")]
impl<'t, T> IntoAssertContext<'t, T> for &'t mut T {
fn must(&'t self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(self))
}
fn must_owned(self) -> AssertThat<'t, T, Panic> {
AssertThat::new_panicking(Actual::Borrowed(self))
}
fn verify(&'t self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Borrowed(self))
}
fn verify_owned(self) -> AssertThat<'t, T, Capture> {
AssertThat::new_capturing(Actual::Borrowed(self))
}
}
#[track_caller]
#[must_use]
#[cfg(feature = "std")]
#[allow(deprecated)]
pub fn assert_that_panic_by<'t, R>(
fun: impl FnOnce() -> R + 't,
) -> AssertThat<'t, PanicValue, Panic> {
use crate::prelude::FnOnceAssertions;
assert_that_owned(fun).panics()
}
#[must_use]
#[cfg(feature = "std")]
#[allow(deprecated)]
pub async fn assert_that_panic_by_async<'t, F, Fut, R>(fun: F) -> AssertThat<'t, PanicValue, Panic>
where
F: FnOnce() -> Fut + 't,
Fut: Future<Output = R> + UnwindSafe,
{
use crate::prelude::AsyncFnOnceAssertions;
assert_that_owned(fun).panics_async().await
}
pub struct Type<T> {
phantom: PhantomData<T>,
}
impl<T> Type<T> {
#[must_use]
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
#[must_use]
pub fn get_type_name(&self) -> &'static str {
type_name::<T>()
}
#[must_use]
pub fn needs_drop(&self) -> bool {
needs_drop::<T>()
}
#[must_use]
pub fn size(&self) -> usize {
size_of::<T>()
}
}
impl<T> Default for Type<T> {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn assert_that_type<T>() -> AssertThat<'static, Type<T>, Panic> {
AssertThat::new_panicking(Actual::Owned(Type::<T>::new()))
}
pub struct AssertThat<'t, T, M: Mode> {
parent: Option<&'t dyn DynAssertThat>,
actual: Actual<'t, T>,
subject_name: Option<String>,
detail_messages: RefCell<Vec<String>>,
print_location: bool,
number_of_assertions: RefCell<NumberOfAssertions>,
failures: RefCell<Vec<String>>,
mode: RefCell<M>,
}
pub(crate) trait DynAssertThat:
Fallible + WithDetail + AssertionTracking + UnwindSafe + RefUnwindSafe
{
}
impl<T, M: Mode> DynAssertThat for AssertThat<'_, T, M> {}
impl<T, M: Mode> UnwindSafe for AssertThat<'_, T, M> {}
impl<T, M: Mode> RefUnwindSafe for AssertThat<'_, T, M> {}
impl<'t, T> AssertThat<'t, T, Panic> {
#[track_caller]
pub(crate) const fn new_panicking(actual: Actual<'t, T>) -> Self {
AssertThat {
parent: None,
actual,
subject_name: None,
detail_messages: RefCell::new(Vec::new()),
print_location: true,
number_of_assertions: RefCell::new(NumberOfAssertions::new()),
failures: RefCell::new(Vec::new()),
mode: RefCell::new(Panic::DEFAULT),
}
}
}
#[cfg(feature = "fluent")]
impl<'t, T> AssertThat<'t, T, Capture> {
#[track_caller]
pub(crate) const fn new_capturing(actual: Actual<'t, T>) -> Self {
AssertThat {
parent: None,
actual,
subject_name: None,
detail_messages: RefCell::new(Vec::new()),
print_location: true,
number_of_assertions: RefCell::new(NumberOfAssertions::new()),
failures: RefCell::new(Vec::new()),
mode: RefCell::new(Capture::DEFAULT),
}
}
}
impl<T> AssertThat<'_, T, Capture> {
#[must_use]
pub fn capture_failures(mut self) -> Vec<String> {
self.take_failures()
}
#[must_use]
pub fn take_failures(&mut self) -> Vec<String> {
let mut mode = self.mode.borrow_mut();
assert!(
!mode.captured,
"You can only capture the assertion failures once!"
);
mode.captured = true;
self.failures.take()
}
}
impl<'t, T, M: Mode> AssertThat<'t, T, M> {
pub fn actual(&self) -> &T {
self.actual.borrowed()
}
pub(crate) fn replace_actual_with<'u, U>(
self,
new_actual: Actual<'u, U>,
) -> (Actual<'t, T>, AssertThat<'u, U, M>)
where
't: 'u,
{
let previous_actual: Actual<'t, T> = self.actual;
let mapped = AssertThat {
parent: self.parent,
actual: new_actual,
subject_name: self.subject_name, detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: self.mode,
};
(previous_actual, mapped)
}
pub fn map<U>(
self,
mapper: impl FnOnce(Actual<T>) -> Actual<U>,
) -> AssertThat<'t, U, M> {
AssertThat {
parent: self.parent,
actual: mapper(self.actual),
subject_name: self.subject_name, detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: self.mode,
}
}
pub fn map_owned<U>(
self,
mapper: impl FnOnce(<T as ToOwned>::Owned) -> U,
) -> AssertThat<'t, U, M>
where
T: ToOwned,
{
AssertThat {
parent: self.parent,
actual: Actual::Owned(mapper(self.actual.borrowed().to_owned())),
subject_name: self.subject_name, detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: self.mode,
}
}
pub async fn map_async<U: 't, Fut>(
self,
mapper: impl FnOnce(Actual<T>) -> Fut,
) -> AssertThat<'t, U, M>
where
Fut: Future<Output = U>,
{
AssertThat {
parent: self.parent,
actual: mapper(self.actual).await.into(),
subject_name: self.subject_name, detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: self.mode,
}
}
pub fn derive<'u, U: 'u>(&'t self, mapper: impl FnOnce(&'t T) -> U) -> AssertThat<'u, U, M>
where
't: 'u,
{
let mut mode = self.mode.replace(M::default());
mode.set_derived();
AssertThat {
parent: Some(self),
actual: Actual::Owned(mapper(self.actual())),
subject_name: None, detail_messages: RefCell::new(Vec::new()),
print_location: self.print_location,
number_of_assertions: RefCell::new(NumberOfAssertions::new()),
failures: RefCell::new(Vec::new()),
mode: RefCell::new(mode),
}
}
pub async fn derive_async<'u, U: 'u, Fut: Future<Output = U>>(
&'t self,
mapper: impl FnOnce(&'t T) -> Fut,
) -> AssertThat<'u, U, M>
where
't: 'u,
{
let mut mode = self.mode.replace(M::default());
mode.set_derived();
AssertThat {
parent: Some(self),
actual: Actual::Owned(mapper(self.actual()).await),
subject_name: None, detail_messages: RefCell::new(Vec::new()),
print_location: self.print_location,
number_of_assertions: RefCell::new(NumberOfAssertions::new()),
failures: RefCell::new(Vec::new()),
mode: RefCell::new(mode),
}
}
#[allow(clippy::return_self_not_must_use)]
#[allow(clippy::return_self_not_must_use)]
pub fn satisfies<U, F, A>(self, mapper: F, assertions: A) -> Self
where
for<'a> F: FnOnce(&'a T) -> U,
for<'a> A: FnOnce(AssertThat<'a, U, M>),
{
assertions(self.derive(mapper));
self
}
#[cfg(feature = "fluent")]
#[allow(clippy::return_self_not_must_use)]
pub fn satisfy<U, F, A>(self, mapper: F, assertions: A) -> Self
where
for<'a> F: FnOnce(&'a T) -> U,
for<'a> A: FnOnce(AssertThat<'a, U, M>),
{
self.satisfies(mapper, assertions)
}
#[allow(clippy::return_self_not_must_use)]
pub fn satisfies_ref<U: ?Sized, F, A>(self, mapper: F, assertions: A) -> Self
where
for<'a> F: FnOnce(&'a T) -> &'a U,
for<'a> A: FnOnce(AssertThat<'a, &'a U, M>),
{
assertions(self.derive(mapper));
self
}
#[allow(dead_code)]
#[must_use]
pub fn with_subject_name(mut self, subject_name: impl Into<String>) -> Self {
self.subject_name = Some(subject_name.into());
self
}
#[allow(dead_code)]
#[must_use]
pub fn with_location(mut self, value: bool) -> Self {
self.print_location = value;
self
}
}
impl<T, M: Mode> AssertThat<'_, T, M> {
#[inline]
#[allow(clippy::return_self_not_must_use)]
pub fn and(self) -> Self {
self
}
}
impl<'t, T> AssertThat<'t, T, Panic> {
#[allow(dead_code)]
pub fn with_capture(self) -> AssertThat<'t, T, Capture> {
AssertThat {
parent: self.parent,
actual: self.actual,
subject_name: self.subject_name,
detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: RefCell::new(Capture {
derived: false,
captured: false,
}),
}
}
}
impl<'t, T> AssertThat<'t, T, Capture> {
#[allow(deprecated)]
pub fn without_capture(mut self) -> AssertThat<'t, T, Panic> {
use crate::assertions::core::length::LengthAssertions;
assert_that_owned(self.take_failures())
.with_location(self.print_location)
.with_subject_name("Assertion failures")
.with_detail_message(
"You cannot unwrap the inner value if assertion failures were already recorded!",
)
.is_empty();
AssertThat {
parent: self.parent,
actual: self.actual,
subject_name: self.subject_name,
detail_messages: self.detail_messages,
print_location: self.print_location,
number_of_assertions: self.number_of_assertions,
failures: self.failures,
mode: RefCell::new(Panic {
derived: self.mode.borrow().derived,
}),
}
}
}
impl<T> AssertThat<'_, T, Panic> {
#[track_caller]
pub fn unwrap_inner(self) -> T {
self.actual.unwrap_owned()
}
}
impl<T> AssertThat<'_, T, Capture> {
#[track_caller]
pub fn unwrap_inner(self) -> T {
let panicking = self.without_capture();
panicking.actual.unwrap_owned()
}
}
pub struct Differences {
differences: Vec<String>,
}
impl Default for Differences {
fn default() -> Self {
Self::new()
}
}
impl Differences {
#[must_use]
pub fn new() -> Self {
Self {
differences: Vec::new(),
}
}
}
impl Debug for Differences {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_list()
.entries(self.differences.iter().map(|it| details::DisplayString(it)))
.finish()
}
}
pub struct EqContext {
differences: Differences,
}
impl Default for EqContext {
fn default() -> Self {
Self::new()
}
}
impl EqContext {
#[must_use]
pub fn new() -> Self {
Self {
differences: Differences::default(),
}
}
pub fn add_difference(&mut self, difference: String) {
self.differences.differences.push(difference);
}
pub fn add_field_difference(
&mut self,
field_name: &str,
expected: impl Debug,
actual: impl Debug,
) {
self.differences.differences.push(format!(
"\"{field_name}\": expected {expected:#?}, but was {actual:#?}"
));
}
}
pub trait AssertrPartialEq<Rhs: ?Sized = Self> {
#[must_use]
fn eq(&self, other: &Rhs, ctx: Option<&mut EqContext>) -> bool;
#[must_use]
fn ne(&self, other: &Rhs, ctx: Option<&mut EqContext>) -> bool {
!self.eq(other, ctx)
}
}
impl<Rhs: ?Sized, T: PartialEq<Rhs>> AssertrPartialEq<Rhs> for T {
fn eq(&self, other: &Rhs, _ctx: Option<&mut EqContext>) -> bool {
PartialEq::eq(self, other)
}
fn ne(&self, other: &Rhs, _ctx: Option<&mut EqContext>) -> bool {
PartialEq::ne(self, other)
}
}
impl<T1: AssertrPartialEq<T2>, T2> AssertrPartialEq<[T2]> for [T1] {
fn eq(&self, other: &[T2], mut ctx: Option<&mut EqContext>) -> bool {
self.len() == other.len()
&& self.iter().enumerate().all(|(i, t1)| {
other
.get(i)
.is_some_and(|t2| AssertrPartialEq::eq(t1, t2, ctx.as_deref_mut()))
})
}
fn ne(&self, other: &[T2], ctx: Option<&mut EqContext>) -> bool {
!Self::eq(self, other, ctx)
}
}
#[derive(Default)]
pub enum Eq<T> {
#[default]
Any,
Eq(T),
}
pub fn eq<T>(v: T) -> Eq<T> {
Eq::Eq(v)
}
#[must_use]
pub fn any<T>() -> Eq<T> {
Eq::Any
}
impl<T: Debug> Debug for Eq<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Eq::Any => f.write_str("Eq::Any"),
Eq::Eq(v) => f.write_fmt(format_args!("Eq::Eq({v:?})")),
}
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use alloc::format;
use indoc::formatdoc;
use crate::prelude::*;
#[test]
fn with_capture_yields_failures_and_does_not_panic() {
let failures = 42
.verify()
.with_location(false)
.be_greater_than(100)
.be_equal_to(1)
.capture_failures();
assert_that!(failures.as_slice())
.has_length(2)
.contains_exactly([
formatdoc! {"
-------- assertr --------
Actual: 42
is not greater than
Expected: 100
-------- assertr --------
"},
formatdoc! {"
-------- assertr --------
Expected: 1
Actual: 42
-------- assertr --------
"},
]);
}
#[test]
fn dropping_a_capturing_assert_panics_when_failures_occurred_which_were_not_captured() {
let assert = 42.verify().with_location(false).be_equal_to(43);
assert_that_panic_by(move || drop(assert))
.has_type::<&str>()
.is_equal_to("You dropped an `assert_that(..)` value, on which `.with_capture()` was called, without actually capturing the assertion failures using `.capture_failures()`!");
}
#[test]
fn without_capture_switches_to_panic_mode() {
let assert_capturing = assert_that_owned(42)
.with_location(false)
.with_capture()
.is_equal_to(42);
let _assert_panicking = assert_capturing.without_capture();
}
#[test]
fn without_capture_panics_when_assertion_failures_were_already_recorded() {
let assert_capturing = assert_that_owned(42)
.with_location(false)
.with_capture()
.is_equal_to(43);
assert_that_panic_by(move || assert_capturing.without_capture())
.has_type::<String>()
.is_equal_to(formatdoc! {r#"
-------- assertr --------
Subject: Assertion failures
Actual: alloc::vec::Vec<alloc::string::String> [
"-------- assertr --------\nExpected: 43\n\n Actual: 42\n-------- assertr --------\n",
]
was expected to be empty, but it is not!
Details: [
You cannot unwrap the inner value if assertion failures were already recorded!,
]
-------- assertr --------
"#});
}
mod unwrap_inner {
use crate::prelude::*;
use indoc::formatdoc;
#[test]
fn panics_on_borrowed_value_in_panic_mode() {
let value = String::from("foo");
let assert = assert_that(&value)
.with_location(false)
.is_equal_to("foo");
assert_that_panic_by(move || assert.unwrap_inner())
.has_type::<&str>()
.is_equal_to(formatdoc! {r#"Cannot `unwrap_owned` a borrowed value."#});
}
#[test]
fn panics_on_borrowed_value_in_capture_mode() {
let value = String::from("foo");
let assert = assert_that(&value)
.with_location(false)
.with_capture()
.is_equal_to("foo");
assert_that_panic_by(move || assert.unwrap_inner())
.has_type::<&str>()
.is_equal_to(formatdoc! {r#"Cannot `unwrap_owned` a borrowed value."#});
}
#[test]
fn succeeds_on_owned_value_in_panic_mode() {
let assert = assert_that_owned(42)
.with_location(false)
.is_equal_to(42);
let actual = assert.unwrap_inner();
actual.must().be_equal_to(42);
}
#[test]
fn succeeds_on_owned_value_in_capture_mode_when_no_failures_were_recorded() {
let assert = assert_that_owned(42)
.with_location(false)
.with_capture()
.has_display_value("42");
let actual = assert.unwrap_inner();
actual.must().be_equal_to(42);
}
#[test]
fn panics_on_owned_value_in_capture_mode_when_failures_were_recorded() {
let assert = assert_that_owned(42)
.with_location(false)
.with_capture()
.is_equal_to(43);
assert_that_panic_by(move || assert.unwrap_inner())
.has_type::<String>()
.is_equal_to(formatdoc! {r#"
-------- assertr --------
Subject: Assertion failures
Actual: alloc::vec::Vec<alloc::string::String> [
"-------- assertr --------\nExpected: 43\n\n Actual: 42\n-------- assertr --------\n",
]
was expected to be empty, but it is not!
Details: [
You cannot unwrap the inner value if assertion failures were already recorded!,
]
-------- assertr --------
"#});
}
}
#[cfg(feature = "fluent")]
#[test]
fn allows_fluent_entry_into_assertion_context() {
42.must().be_equal_to(42);
42.must_owned().be_equal_to(42);
42.verify()
.be_equal_to(42)
.capture_failures()
.must()
.be_empty();
42.verify_owned()
.be_equal_to(42)
.capture_failures()
.must()
.be_empty();
assert_that(&42).is_equal_to(42);
assert_that_owned(42).is_equal_to(42);
let failures = assert_that(&42)
.with_capture()
.is_equal_to(42)
.capture_failures();
assert_that!(failures).is_empty();
let failures = assert_that_owned(42)
.with_capture()
.is_equal_to(42)
.capture_failures();
assert_that!(failures).is_empty();
assert_that!(&42).is_equal_to(42);
assert_that!(42).is_equal_to(42);
}
}