Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,9 @@ for all iterators.
| contains_only_once | verify that an iterator/collection contains only the specified values in any order and each of them only once |
| single_element | verify that an iterator/collection contains exaclty one element and return a `Spec` for that one element |
| filtered_on | filter the elements of an iterator/collection on a condition and return a `Spec` that contains the filtered elements |
| any_satisfies | verify that any element of an iterator/collection satisfies a predicate |
| all_satisfy | verify that all elements of an iterator/collection satisfy a predicate |
| none_satisfies | verify that none of the elements of an iterator/collection satisfies a predicate |

for iterators that yield items in a well-defined order.

Expand Down
69 changes: 68 additions & 1 deletion src/assertions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3661,6 +3661,8 @@ pub trait AssertMapContainsValue<E> {
///
/// # Examples
///
/// Filtering elements:
///
/// ```
/// use asserting::prelude::*;
///
Expand All @@ -3670,6 +3672,21 @@ pub trait AssertMapContainsValue<E> {
/// let subject = [1, 2, 3, 4, 5];
/// assert_that!(subject).filtered_on(|e| e & 1 == 0).contains_exactly_in_any_order([2, 4]);
/// ```
///
/// Some elements satisfy a predicate:
///
/// ```
/// use asserting::prelude::*;
///
/// let subject = [1, 41, 43, 42, 5];
/// assert_that!(subject).any_satisfies(|e| *e == 42);
///
/// let subject = [43, 44, 45, 46, 47];
/// assert_that!(subject).all_satisfy(|e| *e > 42);
///
/// let subject = [42, 43, 44, 45, 46];
/// assert_that!(subject).none_satisfies(|e| *e < 42);
/// ```
pub trait AssertElements<'a, T, R> {
/// Verify that the iterator contains exactly one element and return a
/// [`Spec`] for that single element.
Expand Down Expand Up @@ -3706,7 +3723,57 @@ pub trait AssertElements<'a, T, R> {
/// .is_equal_to("three");
/// ```
#[track_caller]
fn filtered_on(self, condition: impl Fn(&T) -> bool) -> Spec<'a, Vec<T>, R>;
fn filtered_on<C>(self, condition: C) -> Spec<'a, Vec<T>, R>
where
C: FnMut(&T) -> bool;

/// Verify that any element of a collection or an iterator satisfies a given
/// predicate.
///
/// # Examples
///
/// ```
/// use asserting::prelude::*;
///
/// let subject = [1, 41, 43, 42, 5];
/// assert_that!(subject).any_satisfies(|e| *e == 42);
/// ```
#[track_caller]
fn any_satisfies<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool;

/// Verify that all elements of a collection or an iterator satisfy a given
/// predicate.
///
/// # Examples
///
/// ```
/// use asserting::prelude::*;
///
/// let subject = [43, 44, 45, 46, 47];
/// assert_that!(subject).all_satisfy(|e| *e > 42);
/// ```
#[track_caller]
fn all_satisfy<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool;

/// Verify that none of the elements of a collection or an iterator
/// satisfies a given predicate.
///
/// # Examples
///
/// ```
/// use asserting::prelude::*;
///
/// let subject = [42, 43, 44, 45, 46];
/// assert_that!(subject).none_satisfies(|e| *e < 42);
/// ```
#[track_caller]
fn none_satisfies<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool;
}

/// Filter assertions for elements of a collection or an iterator that yields
Expand Down
35 changes: 35 additions & 0 deletions src/expectations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,41 @@ pub struct HasAtLeastNumberOfElements {
pub expected_number_of_elements: usize,
}

pub fn any_satisfies<P>(predicate: P) -> AnySatisfies<P> {
AnySatisfies { predicate }
}

#[must_use]
pub struct AnySatisfies<P> {
pub predicate: P,
}

pub fn all_satisfy<P>(predicate: P) -> AllSatisfy<P> {
AllSatisfy {
predicate,
failing: HashSet::new(),
}
}

#[must_use]
pub struct AllSatisfy<P> {
pub predicate: P,
pub failing: HashSet<usize>,
}

pub fn none_satisfies<P>(predicate: P) -> NoneSatisfies<P> {
NoneSatisfies {
predicate,
failing: HashSet::new(),
}
}

#[must_use]
pub struct NoneSatisfies<P> {
pub predicate: P,
pub failing: HashSet<usize>,
}

/// Creates a [`MapContainsKey`] expectation.
pub fn map_contains_key<E>(expected_key: E) -> MapContainsKey<E> {
MapContainsKey { expected_key }
Expand Down
199 changes: 158 additions & 41 deletions src/iterator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ use crate::colored::{
mark_selected_items_in_collection, mark_unexpected, mark_unexpected_string,
};
use crate::expectations::{
has_at_least_number_of_elements, has_single_element, iterator_contains,
iterator_contains_all_in_order, iterator_contains_all_of, iterator_contains_any_of,
iterator_contains_exactly, iterator_contains_exactly_in_any_order, iterator_contains_only,
iterator_contains_only_once, iterator_contains_sequence, iterator_ends_with,
iterator_starts_with, not, HasAtLeastNumberOfElements, HasSingleElement, IteratorContains,
IteratorContainsAllInOrder, IteratorContainsAllOf, IteratorContainsAnyOf,
IteratorContainsExactly, IteratorContainsExactlyInAnyOrder, IteratorContainsOnly,
IteratorContainsOnlyOnce, IteratorContainsSequence, IteratorEndsWith, IteratorStartsWith,
all_satisfy, any_satisfies, has_at_least_number_of_elements, has_single_element,
iterator_contains, iterator_contains_all_in_order, iterator_contains_all_of,
iterator_contains_any_of, iterator_contains_exactly, iterator_contains_exactly_in_any_order,
iterator_contains_only, iterator_contains_only_once, iterator_contains_sequence,
iterator_ends_with, iterator_starts_with, none_satisfies, not, AllSatisfy, AnySatisfies,
HasAtLeastNumberOfElements, HasSingleElement, IteratorContains, IteratorContainsAllInOrder,
IteratorContainsAllOf, IteratorContainsAnyOf, IteratorContainsExactly,
IteratorContainsExactlyInAnyOrder, IteratorContainsOnly, IteratorContainsOnlyOnce,
IteratorContainsSequence, IteratorEndsWith, IteratorStartsWith, NoneSatisfies,
};
use crate::properties::DefinedOrderProperty;
use crate::spec::{
Expand Down Expand Up @@ -775,6 +776,57 @@ where
}
}

impl<'a, S, T, R> AssertElements<'a, T, R> for Spec<'a, S, R>
where
S: IntoIterator<Item = T>,
T: Debug,
R: FailingStrategy,
{
fn single_element(self) -> Spec<'a, T, R> {
let spec = self.mapping(Vec::from_iter).expecting(has_single_element());
if spec.has_failures() {
PanicOnFail.do_fail_with(&spec.failures());
unreachable!("Assertion failed and should have panicked! Please report a bug.")
}
spec.extracting(|mut collection| {
collection.pop().unwrap_or_else(|| {
unreachable!("Assertion failed and should have panicked! Please report a bug.")
})
})
}

fn filtered_on<C>(self, condition: C) -> Spec<'a, Vec<T>, R>
where
C: FnMut(&T) -> bool,
{
self.mapping(|subject| subject.into_iter().filter(condition).collect())
}

fn any_satisfies<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool,
{
self.mapping(Vec::from_iter)
.expecting(any_satisfies(predicate))
}

fn all_satisfy<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool,
{
self.mapping(Vec::from_iter)
.expecting(all_satisfy(predicate))
}

fn none_satisfies<P>(self, predicate: P) -> Spec<'a, Vec<T>, R>
where
P: FnMut(&T) -> bool,
{
self.mapping(Vec::from_iter)
.expecting(none_satisfies(predicate))
}
}

impl<T> Expectation<Vec<T>> for HasSingleElement
where
T: Debug,
Expand Down Expand Up @@ -804,36 +856,74 @@ where
}
}

impl<'a, S, T, R> AssertElements<'a, T, R> for Spec<'a, S, R>
impl<T, P> Expectation<Vec<T>> for AnySatisfies<P>
where
S: IntoIterator<Item = T>,
T: Debug,
R: FailingStrategy,
P: FnMut(&T) -> bool,
{
fn single_element(self) -> Spec<'a, T, R> {
let spec = self.mapping(Vec::from_iter).expecting(has_single_element());
if spec.has_failures() {
PanicOnFail.do_fail_with(&spec.failures());
unreachable!("Assertion failed and should have panicked! Please report a bug.")
fn test(&mut self, subject: &Vec<T>) -> bool {
subject.iter().any(|e| (self.predicate)(e))
}

fn message(
&self,
expression: &Expression<'_>,
actual: &Vec<T>,
_inverted: bool,
_format: &DiffFormat,
) -> String {
format!(
r"expected any element of {expression} to satisfy the predicate, but none did
actual: {actual:?}"
)
}
}

impl<T, P> Expectation<Vec<T>> for AllSatisfy<P>
where
T: Debug,
P: FnMut(&T) -> bool,
{
fn test(&mut self, subject: &Vec<T>) -> bool {
for (i, e) in subject.iter().enumerate() {
if !(self.predicate)(e) {
self.failing.insert(i);
}
}
spec.extracting(|mut collection| {
collection.pop().unwrap_or_else(|| {
unreachable!("Assertion failed and should have panicked! Please report a bug.")
})
})
self.failing.is_empty()
}

fn filtered_on(self, condition: impl Fn(&T) -> bool) -> Spec<'a, Vec<T>, R> {
self.mapping(|subject| subject.into_iter().filter(condition).collect())
fn message(
&self,
expression: &Expression<'_>,
actual: &Vec<T>,
_inverted: bool,
format: &DiffFormat,
) -> String {
let number_of_failing = self.failing.len();
let failing = collect_selected_values(&self.failing, actual);
let marked_actual =
mark_selected_items_in_collection(actual, &self.failing, format, mark_unexpected);
format!(
r"expected all elements of {expression} to satisfy the predicate, but {number_of_failing} did not
actual: {marked_actual}
failing: {failing:?}"
)
}
}

impl<T> Expectation<Vec<T>> for HasAtLeastNumberOfElements
impl<T, P> Expectation<Vec<T>> for NoneSatisfies<P>
where
T: Debug,
P: FnMut(&T) -> bool,
{
fn test(&mut self, subject: &Vec<T>) -> bool {
subject.len() >= self.expected_number_of_elements
for (i, e) in subject.iter().enumerate() {
if (self.predicate)(e) {
self.failing.insert(i);
}
}
self.failing.is_empty()
}

fn message(
Expand All @@ -843,23 +933,14 @@ where
_inverted: bool,
format: &DiffFormat,
) -> String {
let actual_length = actual.len();
let actual_elements = match actual_length {
0 => mark_unexpected_string("no elements", format),
1 => mark_unexpected_string("one element", format),
_ => mark_unexpected_string(&format!("{actual_length} elements"), format),
};
let expected_elements = match self.expected_number_of_elements {
0 => mark_missing_string("no elements", format),
1 => mark_missing_string("at least one element", format),
_ => mark_missing_string(
&format!("at least {} elements", self.expected_number_of_elements),
format,
),
};
let number_of_failing = self.failing.len();
let failing = collect_selected_values(&self.failing, actual);
let marked_actual =
mark_selected_items_in_collection(actual, &self.failing, format, mark_unexpected);
format!(
r"expected {expression} to have {expected_elements}, but has {actual_elements}
actual: {actual:?}"
r"expected none of the elements of {expression} to satisfy the predicate, but {number_of_failing} did
actual: {marked_actual}
failing: {failing:?}"
)
}
}
Expand Down Expand Up @@ -921,6 +1002,42 @@ where
}
}

impl<T> Expectation<Vec<T>> for HasAtLeastNumberOfElements
where
T: Debug,
{
fn test(&mut self, subject: &Vec<T>) -> bool {
subject.len() >= self.expected_number_of_elements
}

fn message(
&self,
expression: &Expression<'_>,
actual: &Vec<T>,
_inverted: bool,
format: &DiffFormat,
) -> String {
let actual_length = actual.len();
let actual_elements = match actual_length {
0 => mark_unexpected_string("no elements", format),
1 => mark_unexpected_string("one element", format),
_ => mark_unexpected_string(&format!("{actual_length} elements"), format),
};
let expected_elements = match self.expected_number_of_elements {
0 => mark_missing_string("no elements", format),
1 => mark_missing_string("at least one element", format),
_ => mark_missing_string(
&format!("at least {} elements", self.expected_number_of_elements),
format,
),
};
format!(
r"expected {expression} to have {expected_elements}, but has {actual_elements}
actual: {actual:?}"
)
}
}

pub fn collect_selected_values<'a, T>(indices: &HashSet<usize>, collection: &'a [T]) -> Vec<&'a T> {
collection
.iter()
Expand Down
Loading
Loading