From e2e3af173375860fde989b9a4b2c68df4f67f026 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 15 Jun 2025 20:24:38 +0200 Subject: [PATCH 1/6] refactor: move impl of expectation combinator for `Not` to new module `expectation_combinators` --- src/expectation_combinators/mod.rs | 25 +++++++++++++++++++++++++ src/expectation_combinators/tests.rs | 0 src/expectations.rs | 25 ------------------------- src/lib.rs | 1 + 4 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 src/expectation_combinators/mod.rs create mode 100644 src/expectation_combinators/tests.rs diff --git a/src/expectation_combinators/mod.rs b/src/expectation_combinators/mod.rs new file mode 100644 index 0000000..3042e69 --- /dev/null +++ b/src/expectation_combinators/mod.rs @@ -0,0 +1,25 @@ +use crate::expectations::Not; +use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; +use crate::std::string::String; + +impl Expectation for Not +where + E: Invertible + Expectation, +{ + fn test(&mut self, subject: &S) -> bool { + !self.0.test(subject) + } + + fn message( + &self, + expression: &Expression<'_>, + actual: &S, + inverted: bool, + format: &DiffFormat, + ) -> String { + self.0.message(expression, actual, !inverted, format) + } +} + +#[cfg(test)] +mod tests; diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/expectations.rs b/src/expectations.rs index d129616..1f6fe47 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -632,28 +632,3 @@ mod panic { pub actual_message: Option>, } } - -mod combinators { - use crate::expectations::Not; - use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; - use crate::std::string::String; - - impl Expectation for Not - where - E: Invertible + Expectation, - { - fn test(&mut self, subject: &S) -> bool { - !self.0.test(subject) - } - - fn message( - &self, - expression: &Expression<'_>, - actual: &S, - inverted: bool, - format: &DiffFormat, - ) -> String { - self.0.message(expression, actual, !inverted, format) - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 9e8b4f5..5afdd52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -682,6 +682,7 @@ mod collection; mod env; mod equality; mod error; +mod expectation_combinators; mod float; mod integer; mod iterator; From faeda77c483921f52486d8c450cc801bf24bf826 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Wed, 18 Jun 2025 03:54:29 +0200 Subject: [PATCH 2/6] feat: implement `All` expectation combinator --- src/expectation_combinators/mod.rs | 105 +++++++++++- src/expectation_combinators/tests.rs | 232 +++++++++++++++++++++++++++ src/expectations.rs | 113 ++++++++++++- src/prelude.rs | 2 +- 4 files changed, 448 insertions(+), 4 deletions(-) diff --git a/src/expectation_combinators/mod.rs b/src/expectation_combinators/mod.rs index 3042e69..bc3183c 100644 --- a/src/expectation_combinators/mod.rs +++ b/src/expectation_combinators/mod.rs @@ -1,7 +1,67 @@ -use crate::expectations::Not; +use crate::expectations::{All, IntoRec, Not, Rec}; use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; use crate::std::string::String; +impl Expectation for Rec +where + E: Expectation, +{ + fn test(&mut self, subject: &S) -> bool { + let result = self.expectation.test(subject); + self.result = Some(result); + result + } + + fn message( + &self, + expression: &Expression<'_>, + actual: &S, + inverted: bool, + format: &DiffFormat, + ) -> String { + if self.is_failure() { + self.expectation + .message(expression, actual, inverted, format) + + "\n" + } else { + String::new() + } + } +} + +impl From for Rec { + fn from(expectation: E) -> Self { + Self::new(expectation) + } +} + +macro_rules! impl_into_rec_for_tuple { + ( $( $tp_name:ident )+ ) => { + #[allow(non_snake_case)] + impl<$($tp_name: Into>),+> IntoRec for ($($tp_name,)+) { + type Output = ($(Rec<$tp_name>,)+); + + fn into_rec(self) -> Self::Output { + let ($($tp_name,)+) = self; + ($($tp_name.into(),)+) + } + } + }; +} + +impl_into_rec_for_tuple! { A1 } +impl_into_rec_for_tuple! { A1 A2 } +impl_into_rec_for_tuple! { A1 A2 A3 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } +impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } + impl Expectation for Not where E: Invertible + Expectation, @@ -21,5 +81,48 @@ where } } +macro_rules! impl_expectation_for_tuple_combinator { + ( $combinator:ident: $( $tp_name:ident )+ ) => { + #[allow(non_snake_case)] + impl),+> Expectation for $combinator<($(Rec<$tp_name>,)+)> { + fn test(&mut self, subject: &S) -> bool { + let ($($tp_name,)+) = &mut self.0; + $( + let $tp_name = $tp_name.test(subject); + )+ + $( $tp_name )&&+ + } + + fn message( + &self, + expression: &Expression<'_>, + actual: &S, + inverted: bool, + format: &DiffFormat, + ) -> String { + let ($($tp_name,)+) = &self.0; + let mut message = String::new(); + $( + message.push_str(&$tp_name.message(expression, actual, inverted, format)); + )+ + message + } + } + }; +} + +impl_expectation_for_tuple_combinator! { All: A1 } +impl_expectation_for_tuple_combinator! { All: A1 A2 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } +impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } + #[cfg(test)] mod tests; diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs index e69de29..bcb496d 100644 --- a/src/expectation_combinators/tests.rs +++ b/src/expectation_combinators/tests.rs @@ -0,0 +1,232 @@ +use crate::expectations::{ + IsBetween, IsGreaterThan, IsLessThan, IsNegative, IsOne, IsPositive, IsZero, +}; +use crate::prelude::*; +use crate::spec::{Expectation, Expression}; + +#[test] +fn newly_created_rec_combinator_is_neither_success_nor_failure() { + let rec = rec(IsZero); + + assert_that(rec.is_success()).is_false(); + assert_that(rec.is_failure()).is_false(); +} + +#[test] +fn rec_combinator_is_success_after_test_method_has_been_called() { + let mut rec = rec(IsZero); + + rec.test(&0); + + assert_that(rec.is_success()).is_true(); +} + +#[test] +fn rec_combinator_is_failure_after_test_method_has_been_called() { + let mut rec = rec(IsNegative); + + rec.test(&1); + + assert_that(rec.is_failure()).is_true(); +} + +#[test] +fn rec_combinator_returns_empty_message_if_test_is_successful() { + let mut rec = rec(IsGreaterThan { expected: 10 }); + + rec.test(&12); + let message = rec.message( + &Expression::from("foo"), + &12, + false, + &DIFF_FORMAT_NO_HIGHLIGHT, + ); + + assert_that(message).is_empty(); +} + +#[test] +fn rec_combinator_returns_failure_message_if_test_is_failure() { + let mut rec = rec(IsOne); + + rec.test(&12); + let message = rec.message( + &Expression::from("foo"), + &12, + false, + &DIFF_FORMAT_NO_HIGHLIGHT, + ); + + assert_that(message).is_equal_to("expected foo to be one\n but was: 12\n expected: 1\n"); +} + +#[test] +fn all_combinator_asserts_2_expectations() { + let subject = 42; + + assert_that(subject).expecting(all((IsPositive, Not(IsZero)))); +} + +#[test] +fn verify_all_combinator_asserts_2_expectations_fails() { + let subject = 42; + + let failures = verify_that(subject) + .expecting(all((IsNegative, IsZero))) + .display_failures(); + + assert_eq!( + failures, + &["assertion failed: expected subject to be negative\n \ + but was: 42\n \ + expected: < 0\n\ + expected subject to be zero\n \ + but was: 42\n \ + expected: 0\n\ + \n"] + ); +} + +#[test] +fn all_combinator_asserts_3_expectations() { + let subject = -42; + + assert_that(subject).expecting(all(( + IsNegative, + Not(IsZero), + IsBetween { min: -43, max: -42 }, + ))); +} + +#[test] +fn verify_all_combinator_asserts_3_expectations_fails() { + let subject = -42; + + let failures = verify_that(subject) + .expecting(all(( + IsPositive, + Not(IsZero), + IsBetween { min: 41, max: 43 }, + ))) + .display_failures(); + + assert_eq!( + failures, + &["assertion failed: expected subject to be positive\n \ + but was: -42\n \ + expected: > 0\n\ + expected subject to be between 41 and 43\n \ + but was: -42\n \ + expected: 41 <= x <= 43\n\ + \n"] + ); +} + +#[test] +fn all_combinator_asserts_4_expectations() { + let subject = -42; + + assert_that(subject).expecting(all(( + IsNegative, + Not(IsZero), + IsLessThan { expected: 2 }, + IsBetween { min: -43, max: -42 }, + ))); +} + +#[test] +fn verify_all_combinator_asserts_4_expectations_fails() { + let subject = -42; + + let failures = verify_that(subject) + .expecting(all(( + IsPositive, + Not(IsZero), + IsGreaterThan { expected: 2 }, + IsBetween { min: 41, max: 43 }, + ))) + .display_failures(); + + assert_eq!( + failures, + &["assertion failed: expected subject to be positive\n \ + but was: -42\n \ + expected: > 0\n\ + expected subject to be greater than 2\n \ + but was: -42\n \ + expected: > 2\n\ + expected subject to be between 41 and 43\n \ + but was: -42\n \ + expected: 41 <= x <= 43\n\ + \n"] + ); +} + +#[test] +fn all_combinator_asserts_5_expectations() { + let subject = 0; + + assert_that(subject).expecting(all((IsZero, IsZero, IsZero, IsZero, IsZero))); +} + +#[test] +fn all_combinator_asserts_6_expectations() { + let subject = 0; + + assert_that(subject).expecting(all((IsZero, IsZero, IsZero, IsZero, IsZero, IsZero))); +} + +#[test] +fn all_combinator_asserts_7_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + ))); +} + +#[test] +fn all_combinator_asserts_8_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + ))); +} + +#[test] +fn all_combinator_asserts_9_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + ))); +} + +#[test] +fn all_combinator_asserts_10_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + ))); +} + +#[test] +fn all_combinator_asserts_11_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + ))); +} + +#[test] +fn all_combinator_asserts_12_expectations() { + let subject = 0; + + assert_that(subject).expecting(all(( + IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, IsZero, + IsZero, + ))); +} diff --git a/src/expectations.rs b/src/expectations.rs index 1f6fe47..f3c16dd 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -7,13 +7,13 @@ use crate::std::marker::PhantomData; use crate::std::{string::String, vec::Vec}; use hashbrown::HashSet; -/// Combinator for expectations that inverts the contained expectation. +/// A combinator expectation that inverts the wrapped expectation. /// /// This combinator can only be used with expectations that implement the /// [`Invertible`] trait (additional to the [`Expectation`] trait). /// /// Most of the expectations provided by this crate do implement the -/// [`Invertible`] trait and thus can be used with the [`Not`] combinator. +/// [`Invertible`] trait and thus can be used with the `Not` combinator. /// /// # Examples /// @@ -33,6 +33,115 @@ use hashbrown::HashSet; #[must_use] pub struct Not(pub E); +/// Creates an [`All`] expectation combinator from a tuple of expectations. +/// +/// # Examples +/// +/// ``` +/// use asserting::expectations::{IsAtMost, IsPositive}; +/// use asserting::prelude::*; +/// +/// let custom_expectation = all((IsPositive, IsAtMost { expected: 99 })); +/// +/// assert_that(42).expecting(custom_expectation); +/// ``` +pub fn all(expectations: A) -> All +where + A: IntoRec, +{ + All(expectations.into_rec()) +} + +/// A combinator expectation that verifies that all containing expectations are +/// met. +/// +/// Use the function [`all()`] to construct an `All` combinator for a tuple of +/// expectations. +#[must_use] +pub struct All(pub E); + +/// Creates a [`Rec`] expectation combinator that wraps the given expectation. +/// +/// This is a convenience function that is equivalent to `Rec::new()`. +pub fn rec(expectations: E) -> Rec { + Rec::new(expectations) +} + +/// A combinator expectation that memorizes ("records") the result of the +/// wrapped expectation. +/// +/// Use the function [`rec()`] to conveniently wrap an expectation into the +/// `Rec` combinator. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// use asserting::expectations::{IsNegative, rec}; +/// use asserting::spec::Expectation; +/// +/// // the result of new `Rec` is neither `success` nor `failure` +/// let mut expectation = rec(IsNegative); +/// assert_that(expectation.is_failure()).is_false(); +/// assert_that(expectation.is_success()).is_false(); +/// +/// // once the `test` method has been called, the result can be queried at a +/// // later time. +/// _ = expectation.test(&-42); // returns true +/// assert_that(expectation.is_success()).is_true(); +/// assert_that(expectation.is_failure()).is_false(); +/// +/// // once the `test` method has been called, the result can be queried at a +/// // later time. +/// _= expectation.test(&42); // returns false +/// assert_that(expectation.is_success()).is_false(); +/// assert_that(expectation.is_failure()).is_true(); +/// ``` +pub struct Rec { + pub expectation: E, + pub result: Option, +} + +impl Rec { + /// Creates a new ìnstance of `Rec` that wraps the given expectation. + pub fn new(expectation: E) -> Self { + Self { + expectation, + result: None, + } + } + + /// Returns true if the `test` method has been called and the result of the + /// wrapped expectation was true ("success") and false otherwise. + pub fn is_success(&self) -> bool { + self.result.is_some_and(|r| r) + } + + /// Returns true if the `test` method has been called and the result of the + /// wrapped expectation was false ("failure") and false otherwise. + pub fn is_failure(&self) -> bool { + self.result.is_some_and(|r| !r) + } +} + +/// Trait to convert a type into another type that wraps the contained +/// expectation(s) into `Rec`(s). +/// +/// If this type contains multiple expectations like `Vec` or +/// tuples of expectations, each expectation should be wrapped into its own +/// `Rec`. +pub trait IntoRec { + /// The result type with the expectation(s) wrapped into [`Rec`](s). + type Output; + + /// Wraps an expectation of this type into [`Rec`]. + /// + /// If this type contains multiple expectations like `Vec Self::Output; +} + #[must_use] pub struct Predicate { pub predicate: F, diff --git a/src/prelude.rs b/src/prelude.rs index e4eda76..c8f3055 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +18,7 @@ pub use super::{ assert_that, assertions::*, colored::{DEFAULT_DIFF_FORMAT, DIFF_FORMAT_NO_HIGHLIGHT}, - expectations::Not, + expectations::{all, rec, All, Not, Rec}, properties::*, spec::{assert_that, verify_that, CollectFailures, Location, PanicOnFail}, verify_that, From b560e395332cabd875ac83e8e2cdf7d5bb899e78 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Wed, 18 Jun 2025 05:06:51 +0200 Subject: [PATCH 3/6] feat: implement `Any` expectation combinator --- src/char/tests.rs | 4 +- src/expectation_combinators/mod.rs | 75 +++++-- src/expectation_combinators/tests.rs | 300 ++++++++++++++++++++++++++- src/expectations.rs | 28 ++- src/prelude.rs | 2 +- 5 files changed, 388 insertions(+), 21 deletions(-) diff --git a/src/char/tests.rs b/src/char/tests.rs index 1ef7fef..cf7276c 100644 --- a/src/char/tests.rs +++ b/src/char/tests.rs @@ -395,14 +395,14 @@ fn verify_borrowed_char_is_whitespace_fails() { proptest! { #[test] fn asserting_ascii_and_lowercase_is_equivalent_to_ascii_lowercase_method( - chr in any::(), + chr in prop::arbitrary::any::(), ) { prop_assert_eq!(chr.is_ascii() && chr.is_lowercase(), chr.is_ascii_lowercase()); } #[test] fn asserting_ascii_and_uppercase_is_equivalent_to_ascii_uppercase_method( - chr in any::(), + chr in prop::arbitrary::any::(), ) { prop_assert_eq!(chr.is_ascii() && chr.is_uppercase(), chr.is_ascii_uppercase()); } diff --git a/src/expectation_combinators/mod.rs b/src/expectation_combinators/mod.rs index bc3183c..cfdbf8a 100644 --- a/src/expectation_combinators/mod.rs +++ b/src/expectation_combinators/mod.rs @@ -1,4 +1,4 @@ -use crate::expectations::{All, IntoRec, Not, Rec}; +use crate::expectations::{All, Any, IntoRec, Not, Rec}; use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; use crate::std::string::String; @@ -81,10 +81,10 @@ where } } -macro_rules! impl_expectation_for_tuple_combinator { - ( $combinator:ident: $( $tp_name:ident )+ ) => { +macro_rules! impl_expectation_for_all_combinator { + ( $( $tp_name:ident )+ ) => { #[allow(non_snake_case)] - impl),+> Expectation for $combinator<($(Rec<$tp_name>,)+)> { + impl),+> Expectation for All<($(Rec<$tp_name>,)+)> { fn test(&mut self, subject: &S) -> bool { let ($($tp_name,)+) = &mut self.0; $( @@ -111,18 +111,61 @@ macro_rules! impl_expectation_for_tuple_combinator { }; } -impl_expectation_for_tuple_combinator! { All: A1 } -impl_expectation_for_tuple_combinator! { All: A1 A2 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } -impl_expectation_for_tuple_combinator! { All: A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } +impl_expectation_for_all_combinator! { A1 } +impl_expectation_for_all_combinator! { A1 A2 } +impl_expectation_for_all_combinator! { A1 A2 A3 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } +impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } + +macro_rules! impl_expectation_for_any_combinator { + ( $( $tp_name:ident )+ ) => { + #[allow(non_snake_case)] + impl),+> Expectation for Any<($(Rec<$tp_name>,)+)> { + fn test(&mut self, subject: &S) -> bool { + let ($($tp_name,)+) = &mut self.0; + $( + let $tp_name = $tp_name.test(subject); + )+ + $( $tp_name )||+ + } + + fn message( + &self, + expression: &Expression<'_>, + actual: &S, + inverted: bool, + format: &DiffFormat, + ) -> String { + let ($($tp_name,)+) = &self.0; + let mut message = String::new(); + $( + message.push_str(&$tp_name.message(expression, actual, inverted, format)); + )+ + message + } + } + }; +} + +impl_expectation_for_any_combinator! { A1 } +impl_expectation_for_any_combinator! { A1 A2 } +impl_expectation_for_any_combinator! { A1 A2 A3 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } +impl_expectation_for_any_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } #[cfg(test)] mod tests; diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs index bcb496d..1888e80 100644 --- a/src/expectation_combinators/tests.rs +++ b/src/expectation_combinators/tests.rs @@ -1,5 +1,6 @@ use crate::expectations::{ - IsBetween, IsGreaterThan, IsLessThan, IsNegative, IsOne, IsPositive, IsZero, + IsBetween, IsEmpty, IsGreaterThan, IsLessThan, IsNegative, IsOne, IsPositive, IsZero, + StringContains, StringContainsAnyOf, }; use crate::prelude::*; use crate::spec::{Expectation, Expression}; @@ -230,3 +231,300 @@ fn all_combinator_asserts_12_expectations() { IsZero, ))); } + +#[test] +fn any_combinator_asserts_1_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any((StringContainsAnyOf { + expected: ['a', 'b', 'c'], + },))); +} + +#[test] +fn verify_any_combinator_asserts_1_expectations_fails() { + let subject = "nulla elit fugiat reprehenderit"; + + let failures = verify_that(subject) + .expecting(any((StringContains { + expected: "fugiaty", + },))) + .display_failures(); + + assert_eq!( + failures, + &[ + "assertion failed: expected subject to contain \"fugiaty\"\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: \"fugiaty\"\n\ + \n" + ] + ); +} + +#[test] +fn any_combinator_asserts_2_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + IsEmpty, + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + ))); +} + +#[test] +fn verify_any_combinator_asserts_2_expectations_fails() { + let subject = "nulla elit fugiat reprehenderit"; + + let failures = verify_that(subject) + .expecting(any(( + StringContains { + expected: "fugiaty", + }, + StringContains { expected: "ellit" }, + ))) + .display_failures(); + + assert_eq!( + failures, + &[ + "assertion failed: expected subject to contain \"fugiaty\"\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: \"fugiaty\"\n\ + expected subject to contain \"ellit\"\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: \"ellit\"\n\ + \n" + ] + ); +} + +#[test] +fn any_combinator_asserts_3_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + IsEmpty, + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + ))); +} + +#[test] +fn verify_any_combinator_asserts_3_expectations_fails() { + let subject = "nulla elit fugiat reprehenderit"; + + let failures = verify_that(subject) + .expecting(any(( + StringContains { + expected: "fugiaty", + }, + StringContains { expected: "ellit" }, + StringContainsAnyOf { + expected: ['x', 'y', 'z'], + }, + ))) + .display_failures(); + + assert_eq!( + failures, + &[ + "assertion failed: expected subject to contain \"fugiaty\"\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: \"fugiaty\"\n\ + expected subject to contain \"ellit\"\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: \"ellit\"\n\ + expected subject to contain any of ['x', 'y', 'z']\n \ + but was: \"nulla elit fugiat reprehenderit\"\n \ + expected: ['x', 'y', 'z']\n\ + \n" + ] + ); +} + +#[test] +fn any_combinator_asserts_4_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + ))); +} + +#[test] +fn any_combinator_asserts_5_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + ))); +} + +#[test] +fn any_combinator_asserts_6_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + ))); +} + +#[test] +fn any_combinator_asserts_7_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + ))); +} + +#[test] +fn any_combinator_asserts_8_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + StringContains { expected: 'g' }, + ))); +} + +#[test] +fn any_combinator_asserts_9_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + StringContains { expected: 'g' }, + StringContains { expected: 'h' }, + ))); +} + +#[test] +fn any_combinator_asserts_10_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + StringContains { expected: 'g' }, + StringContains { expected: 'h' }, + StringContains { expected: 'i' }, + ))); +} + +#[test] +fn any_combinator_asserts_11_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + StringContains { expected: 'g' }, + StringContains { expected: 'h' }, + StringContains { expected: 'i' }, + StringContains { expected: 'j' }, + ))); +} + +#[test] +fn any_combinator_asserts_12_expectations() { + let subject = "nulla elit fugiat reprehenderit"; + + assert_that(subject).expecting(any(( + Not(IsEmpty), + StringContainsAnyOf { + expected: ['a', 'b', 'c'], + }, + StringContains { + expected: "unfugiaty", + }, + StringContains { expected: "elit" }, + StringContains { expected: 'd' }, + StringContains { expected: 'e' }, + StringContains { expected: 'f' }, + StringContains { expected: 'g' }, + StringContains { expected: 'h' }, + StringContains { expected: 'i' }, + StringContains { expected: 'j' }, + StringContains { expected: 'k' }, + ))); +} diff --git a/src/expectations.rs b/src/expectations.rs index f3c16dd..10c8d05 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -67,6 +67,32 @@ pub fn rec(expectations: E) -> Rec { Rec::new(expectations) } +/// Creates an [`Any`] expectation combinator from a tuple of expectations. +/// +/// # Examples +/// +/// ``` +/// use asserting::expectations::{IsEmpty, StringContains}; +/// use asserting::prelude::*; +/// +/// let custom_expectation = any((Not(IsEmpty), StringContains { expected: "unfugiaty" })); +/// +/// assert_that("elit fugiat dolores").expecting(custom_expectation); +/// ``` +pub fn any(expectations: A) -> Any +where + A: IntoRec, +{ + Any(expectations.into_rec()) +} + +/// A combinator expectation that verifies that any containing expectation is +/// met. +/// +/// Use the function [`any()`] to construct an `Any` combinator for a tuple of +/// expectations. +pub struct Any(pub E); + /// A combinator expectation that memorizes ("records") the result of the /// wrapped expectation. /// @@ -131,7 +157,7 @@ impl Rec { /// tuples of expectations, each expectation should be wrapped into its own /// `Rec`. pub trait IntoRec { - /// The result type with the expectation(s) wrapped into [`Rec`](s). + /// The result type with the expectation(s) wrapped into [`Rec`]. type Output; /// Wraps an expectation of this type into [`Rec`]. diff --git a/src/prelude.rs b/src/prelude.rs index c8f3055..ef78dd9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +18,7 @@ pub use super::{ assert_that, assertions::*, colored::{DEFAULT_DIFF_FORMAT, DIFF_FORMAT_NO_HIGHLIGHT}, - expectations::{all, rec, All, Not, Rec}, + expectations::{all, any, rec, All, Any, Not, Rec}, properties::*, spec::{assert_that, verify_that, CollectFailures, Location, PanicOnFail}, verify_that, From e054cdc4def1d836446da9dd6ea35e73b89774e3 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Wed, 18 Jun 2025 05:19:54 +0200 Subject: [PATCH 4/6] feat: provide function `not()` to construct a `Not` combinator --- src/expectation_combinators/tests.rs | 28 +++++++++++------------ src/expectations.rs | 34 +++++++++++++++++----------- src/prelude.rs | 2 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs index 1888e80..1969f78 100644 --- a/src/expectation_combinators/tests.rs +++ b/src/expectation_combinators/tests.rs @@ -65,7 +65,7 @@ fn rec_combinator_returns_failure_message_if_test_is_failure() { fn all_combinator_asserts_2_expectations() { let subject = 42; - assert_that(subject).expecting(all((IsPositive, Not(IsZero)))); + assert_that(subject).expecting(all((IsPositive, not(IsZero)))); } #[test] @@ -94,7 +94,7 @@ fn all_combinator_asserts_3_expectations() { assert_that(subject).expecting(all(( IsNegative, - Not(IsZero), + not(IsZero), IsBetween { min: -43, max: -42 }, ))); } @@ -106,7 +106,7 @@ fn verify_all_combinator_asserts_3_expectations_fails() { let failures = verify_that(subject) .expecting(all(( IsPositive, - Not(IsZero), + not(IsZero), IsBetween { min: 41, max: 43 }, ))) .display_failures(); @@ -129,7 +129,7 @@ fn all_combinator_asserts_4_expectations() { assert_that(subject).expecting(all(( IsNegative, - Not(IsZero), + not(IsZero), IsLessThan { expected: 2 }, IsBetween { min: -43, max: -42 }, ))); @@ -142,7 +142,7 @@ fn verify_all_combinator_asserts_4_expectations_fails() { let failures = verify_that(subject) .expecting(all(( IsPositive, - Not(IsZero), + not(IsZero), IsGreaterThan { expected: 2 }, IsBetween { min: 41, max: 43 }, ))) @@ -354,7 +354,7 @@ fn any_combinator_asserts_4_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -370,7 +370,7 @@ fn any_combinator_asserts_5_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -387,7 +387,7 @@ fn any_combinator_asserts_6_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -405,7 +405,7 @@ fn any_combinator_asserts_7_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -424,7 +424,7 @@ fn any_combinator_asserts_8_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -444,7 +444,7 @@ fn any_combinator_asserts_9_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -465,7 +465,7 @@ fn any_combinator_asserts_10_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -487,7 +487,7 @@ fn any_combinator_asserts_11_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, @@ -510,7 +510,7 @@ fn any_combinator_asserts_12_expectations() { let subject = "nulla elit fugiat reprehenderit"; assert_that(subject).expecting(any(( - Not(IsEmpty), + not(IsEmpty), StringContainsAnyOf { expected: ['a', 'b', 'c'], }, diff --git a/src/expectations.rs b/src/expectations.rs index 10c8d05..01b34dd 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -7,13 +7,7 @@ use crate::std::marker::PhantomData; use crate::std::{string::String, vec::Vec}; use hashbrown::HashSet; -/// A combinator expectation that inverts the wrapped expectation. -/// -/// This combinator can only be used with expectations that implement the -/// [`Invertible`] trait (additional to the [`Expectation`] trait). -/// -/// Most of the expectations provided by this crate do implement the -/// [`Invertible`] trait and thus can be used with the `Not` combinator. +/// Creates a [`Not`] expectation combinator wrapping the given expectation. /// /// # Examples /// @@ -21,12 +15,26 @@ use hashbrown::HashSet; /// use asserting::expectations::{HasLength, IsEmpty, IsEqualTo, IsNegative, StringContains}; /// use asserting::prelude::*; /// -/// assert_that!(41).expecting(Not(IsEqualTo { expected: 42 })); -/// assert_that!([1, 2, 3]).expecting(Not(IsEmpty)); -/// assert_that!(37.9).expecting(Not(IsNegative)); -/// assert_that!([1, 2, 3]).expecting(Not(HasLength { expected_length: 4 })); -/// assert_that!("almost").expecting(Not(StringContains { expected: "entire" })); +/// assert_that!(41).expecting(not(IsEqualTo { expected: 42 })); +/// assert_that!([1, 2, 3]).expecting(not(IsEmpty)); +/// assert_that!(37.9).expecting(not(IsNegative)); +/// assert_that!([1, 2, 3]).expecting(not(HasLength { expected_length: 4 })); +/// assert_that!("almost").expecting(not(StringContains { expected: "entire" })); /// ``` +pub fn not(expectation: E) -> Not { + Not(expectation) +} + +/// A combinator expectation that inverts the wrapped expectation. +/// +/// This combinator can only be used with expectations that implement the +/// [`Invertible`] trait (additional to the [`Expectation`] trait). +/// +/// Most of the expectations provided by this crate do implement the +/// [`Invertible`] trait and thus can be used with the `Not` combinator. +/// +/// Use the function [`not()`] to construct a `Not` combinator containing the +/// given expectation. /// /// [`Expectation`]: crate::spec::Expectation /// [`Invertible`]: crate::spec::Invertible @@ -75,7 +83,7 @@ pub fn rec(expectations: E) -> Rec { /// use asserting::expectations::{IsEmpty, StringContains}; /// use asserting::prelude::*; /// -/// let custom_expectation = any((Not(IsEmpty), StringContains { expected: "unfugiaty" })); +/// let custom_expectation = any((not(IsEmpty), StringContains { expected: "unfugiaty" })); /// /// assert_that("elit fugiat dolores").expecting(custom_expectation); /// ``` diff --git a/src/prelude.rs b/src/prelude.rs index ef78dd9..fbf0790 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +18,7 @@ pub use super::{ assert_that, assertions::*, colored::{DEFAULT_DIFF_FORMAT, DIFF_FORMAT_NO_HIGHLIGHT}, - expectations::{all, any, rec, All, Any, Not, Rec}, + expectations::{all, any, not, rec}, properties::*, spec::{assert_that, verify_that, CollectFailures, Location, PanicOnFail}, verify_that, From 3e17db640e303698045ca45b8632be3a402895fc Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 21 Jun 2025 06:34:36 +0200 Subject: [PATCH 5/6] refactor: assert both `is_success` and `is_failure` in tests for `Rec` --- src/expectation_combinators/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs index 1969f78..29a9bc4 100644 --- a/src/expectation_combinators/tests.rs +++ b/src/expectation_combinators/tests.rs @@ -20,6 +20,7 @@ fn rec_combinator_is_success_after_test_method_has_been_called() { rec.test(&0); assert_that(rec.is_success()).is_true(); + assert_that(rec.is_failure()).is_false(); } #[test] @@ -29,6 +30,7 @@ fn rec_combinator_is_failure_after_test_method_has_been_called() { rec.test(&1); assert_that(rec.is_failure()).is_true(); + assert_that(rec.is_success()).is_false(); } #[test] From 6f6c1a6e278084d6c8e824dfa8e0aceec3a37bd4 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 21 Jun 2025 06:56:57 +0200 Subject: [PATCH 6/6] refactor: annotate `Rec` with `#[must_use]` --- src/expectations.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/expectations.rs b/src/expectations.rs index 01b34dd..d113d0e 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -131,6 +131,7 @@ pub struct Any(pub E); /// assert_that(expectation.is_success()).is_false(); /// assert_that(expectation.is_failure()).is_true(); /// ``` +#[must_use] pub struct Rec { pub expectation: E, pub result: Option,