use core::fmt::{Debug, Display};
use crate::AssertInfo;
#[cfg(feature = "std")]
#[must_use = "assertions do not fire unless returned from a test"]
pub trait Assertion: Debug + Display + std::process::Termination {
fn test(&self) -> bool;
fn should_fail(self) -> ShouldFail<Self>
where
Self: Sized,
{
ShouldFail(self)
}
#[allow(
clippy::panic,
reason = "I agree with you clippy, but some people don't care about panics."
)]
fn unwrap(&self) {
assert!(self.test(), "failed assertion was unwrapped:\n{self}");
}
}
#[cfg(not(feature = "std"))]
#[must_use = "assertions do not fire unless returned from a test"]
pub trait Assertion: Debug + Display {
fn test(&self) -> bool;
fn should_fail(self) -> ShouldFail<Self>
where
Self: Sized,
{
ShouldFail(self)
}
#[allow(
clippy::panic,
reason = "I agree with you clippy, but some people don't care about panics."
)]
fn unwrap(&self) {
assert!(self.test(), "failed assertion was unwrapped:\n{self}");
}
}
#[must_use = "assertions do not fire unless returned from a test"]
pub struct BinaryAssertion<T, U> {
lhs: T,
rhs: U,
test_result: bool,
test_repr: &'static str,
info: AssertInfo,
}
impl<T, U> Display for BinaryAssertion<T, U>
where
T: Debug,
U: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self {
lhs,
rhs,
test_result: _,
test_repr,
info: AssertInfo { file, line, column },
} = self;
write!(
f,
"assertion starting at {file}:{line}:{column} failed: `{test_repr}`\n lhs: {lhs:?}\n rhs: {rhs:?}"
)
}
}
impl<T, U> Debug for BinaryAssertion<T, U>
where
T: Debug,
U: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
impl<T, U> BinaryAssertion<T, U> {
pub(crate) const fn new(
lhs: T,
rhs: U,
test_result: bool,
test_repr: &'static str,
info: AssertInfo,
) -> Self {
Self {
lhs,
rhs,
test_result,
test_repr,
info,
}
}
}
#[cfg(feature = "std")]
impl<T, U> std::process::Termination for BinaryAssertion<T, U>
where
T: Debug,
U: Debug,
{
fn report(self) -> std::process::ExitCode {
if self.test() {
std::process::ExitCode::SUCCESS
} else {
println!("{self}");
std::process::ExitCode::FAILURE
}
}
}
impl<T, U> Assertion for BinaryAssertion<T, U>
where
T: Debug,
U: Debug,
{
fn test(&self) -> bool {
self.test_result
}
}
#[must_use = "assertions do not fire unless returned from a test"]
pub struct UnaryAssertion<T> {
this: T,
test_result: bool,
test_repr: &'static str,
info: AssertInfo,
}
impl<T> Display for UnaryAssertion<T>
where
T: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self {
this,
test_result: _,
test_repr,
info: AssertInfo { file, line, column },
} = self;
write!(
f,
"assertion starting at {file}:{line}:{column} failed: `{test_repr}`\nthis: {this:?}"
)
}
}
impl<T> Debug for UnaryAssertion<T>
where
T: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
impl<T> UnaryAssertion<T> {
pub(crate) const fn new(
this: T,
test_result: bool,
test_repr: &'static str,
info: AssertInfo,
) -> Self {
Self {
this,
test_result,
test_repr,
info,
}
}
}
#[cfg(feature = "std")]
impl<T> std::process::Termination for UnaryAssertion<T>
where
T: Debug,
{
fn report(self) -> std::process::ExitCode {
if self.test() {
std::process::ExitCode::SUCCESS
} else {
println!("{self}");
std::process::ExitCode::FAILURE
}
}
}
impl<T> Assertion for UnaryAssertion<T>
where
T: Debug,
{
fn test(&self) -> bool {
self.test_result
}
}
#[must_use = "assertions do not fire unless returned from a test"]
pub struct Succeed {
info: AssertInfo,
}
impl Display for Succeed {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let AssertInfo { file, line, column } = self.info;
write!(
f,
"assertion starting at {file}:{line}:{column} was forced success"
)
}
}
impl Debug for Succeed {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
impl Succeed {
#[doc(hidden)]
pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
Self {
info: AssertInfo { file, line, column },
}
}
}
#[cfg(feature = "std")]
impl std::process::Termination for Succeed {
fn report(self) -> std::process::ExitCode {
std::process::ExitCode::SUCCESS
}
}
impl Assertion for Succeed {
fn test(&self) -> bool {
true
}
}
#[macro_export]
macro_rules! succeed {
() => {
$crate::assertion::Succeed::manual_constructor(
::core::file!(),
::core::line!(),
::core::column!(),
)
};
}
#[must_use = "assertions do not fire unless returned from a test"]
pub struct Fail {
info: AssertInfo,
}
impl Display for Fail {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let AssertInfo { file, line, column } = self.info;
write!(
f,
"assertion starting at {file}:{line}:{column} was forced fail"
)
}
}
impl Debug for Fail {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
impl Fail {
#[doc(hidden)]
pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
Self {
info: AssertInfo { file, line, column },
}
}
}
#[cfg(feature = "std")]
impl std::process::Termination for Fail {
fn report(self) -> std::process::ExitCode {
std::process::ExitCode::FAILURE
}
}
impl Assertion for Fail {
fn test(&self) -> bool {
false
}
}
#[macro_export]
macro_rules! fail {
() => {
$crate::assertion::Fail::manual_constructor(
::core::file!(),
::core::line!(),
::core::column!(),
)
};
}
pub struct ShouldFail<A: Assertion>(A);
impl<A: Assertion> Display for ShouldFail<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let inner = &self.0;
write!(f, "assertion expected to fail succeeded:\n{inner}")
}
}
#[cfg(feature = "std")]
impl<A: Assertion> std::process::Termination for ShouldFail<A> {
fn report(self) -> std::process::ExitCode {
if self.0.test() {
println!("{self}");
std::process::ExitCode::FAILURE
} else {
std::process::ExitCode::SUCCESS
}
}
}
impl<A: Assertion> Assertion for ShouldFail<A> {
fn test(&self) -> bool {
!self.0.test()
}
}
impl<A: Assertion> Debug for ShouldFail<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(feature = "alloc")]
pub use multi_dyn_assertion::MultiDynAssertion;
#[cfg(feature = "alloc")]
mod multi_dyn_assertion {
use super::{Assertion, Debug, Display};
extern crate alloc;
use alloc::boxed::Box;
pub struct MultiDynAssertion<const N: usize>([Box<dyn Assertion>; N]);
impl<const N: usize> Display for MultiDynAssertion<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "assertion failures in a multi-assert:")?;
for (i, assertion) in self.0.iter().enumerate() {
if assertion.test() {
write!(f, "\n{i}. [ok]")?;
} else {
writeln!(f, "\n{i}. {assertion}")?;
}
}
Ok(())
}
}
impl<const N: usize> MultiDynAssertion<N> {
#[must_use]
#[doc(hidden)]
pub fn new(assertions: [Box<dyn Assertion>; N]) -> Self {
Self(assertions)
}
}
#[cfg(feature = "std")]
impl<const N: usize> std::process::Termination for MultiDynAssertion<N> {
fn report(self) -> std::process::ExitCode {
if self.test() {
std::process::ExitCode::SUCCESS
} else {
println!("{self}");
std::process::ExitCode::FAILURE
}
}
}
impl<const N: usize> Assertion for MultiDynAssertion<N> {
fn test(&self) -> bool {
self.0.iter().all(|a| a.test())
}
}
impl<const N: usize> Debug for MultiDynAssertion<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
}
pub struct MultiAssertion<A: Assertion, const N: usize>([A; N]);
impl<A: Assertion, const N: usize> Display for MultiAssertion<A, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "assertion failures in a multi-assert:")?;
for (i, assertion) in self.0.iter().enumerate() {
if assertion.test() {
write!(f, "\n{i}. [ok]")?;
} else {
writeln!(f, "\n{i}. {assertion}")?;
}
}
Ok(())
}
}
impl<A: Assertion, const N: usize> MultiAssertion<A, N> {
#[must_use]
pub const fn new(assertions: [A; N]) -> Self {
Self(assertions)
}
}
#[cfg(feature = "std")]
impl<A: Assertion, const N: usize> std::process::Termination for MultiAssertion<A, N> {
fn report(self) -> std::process::ExitCode {
if self.test() {
std::process::ExitCode::SUCCESS
} else {
println!("{self}");
std::process::ExitCode::FAILURE
}
}
}
impl<A: Assertion, const N: usize> Assertion for MultiAssertion<A, N> {
fn test(&self) -> bool {
self.0.iter().all(Assertion::test)
}
}
impl<A: Assertion, const N: usize> Debug for MultiAssertion<A, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
#[macro_export]
macro_rules! multi_assert {
[#[dyn] $($this:expr),+ $(,)?] => {
$crate::assertion::MultiDynAssertion::new([
$(Box::new($this),)+
])
};
[$($this:expr),+ $(,)?] => {
$crate::assertion::MultiAssertion::new([
$($this,)+
])
};
}
#[cfg(not(feature = "alloc"))]
#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
#[macro_export]
macro_rules! multi_assert {
[#[dyn] $($this:expr),+ $(,)?] => {
compile_error!("cannot use #[dyn] if the no_alloc feature is enabled")
};
[$($this:expr),+ $(,)?] => {
$crate::assertion::MultiAssertion::new([
$($this,)+
])
};
}