use std::fmt::Debug;
use proptest::{prelude::*, test_runner::TestCaseResult};
use crate::collector::{Collector, CollectorBase};
pub trait CollectorTester<T> {
type Output<'a>;
#[allow(clippy::type_complexity)] fn collector_test_parts(
&mut self,
) -> CollectorTestParts<
impl Iterator<Item = T>,
impl Collector<T, Output = Self::Output<'_>>,
impl FnMut(Self::Output<'_>, &mut dyn Iterator<Item = T>) -> Result<(), PredError>,
>;
}
pub struct CollectorTestParts<I, C, P>
where
I: Iterator,
C: Collector<I::Item>,
P: FnMut(C::Output, &mut dyn Iterator<Item = I::Item>) -> Result<(), PredError>,
{
pub iter: I,
pub collector: C,
pub should_break: bool,
pub pred: P,
}
#[derive(Debug)]
pub enum PredError {
IncorrectOutput,
IncorrectIterConsumption,
}
impl PredError {
fn of_method(self, name: &'static str) -> OfMethod {
OfMethod {
name,
pred_error: self,
}
}
}
struct OfMethod {
name: &'static str,
pred_error: PredError,
}
impl From<OfMethod> for TestCaseError {
fn from(OfMethod { name, pred_error }: OfMethod) -> Self {
Self::Fail(format!("`{name}()` is implemented incorrectly: {pred_error:?}").into())
}
}
pub trait CollectorTesterExt<T>: CollectorTester<T> {
fn test_collector(&mut self) -> TestCaseResult {
test_collector_part(self, None::<std::iter::Empty<T>>)
}
fn test_collector_may_fused(
&mut self,
items_for_fuse_test: impl IntoIterator<Item = T, IntoIter: Clone>,
) -> TestCaseResult {
test_collector_part(self, Some(items_for_fuse_test.into_iter()))
}
}
impl<CT, T> CollectorTesterExt<T> for CT where CT: CollectorTester<T> {}
pub struct BasicCollectorTester<ItFac, ClFac, SbPred, Pred, I, C>
where
I: Iterator,
C: Collector<I::Item>,
ItFac: FnMut() -> I,
ClFac: FnMut() -> C,
SbPred: FnMut(I) -> bool,
Pred: FnMut(I, C::Output, &mut dyn Iterator<Item = I::Item>) -> Result<(), PredError>,
{
pub iter_factory: ItFac,
pub collector_factory: ClFac,
pub should_break_pred: SbPred,
pub pred: Pred,
}
impl<ItFac, ClFac, SbPred, Pred, I, C> CollectorTester<I::Item>
for BasicCollectorTester<ItFac, ClFac, SbPred, Pred, I, C>
where
I: Iterator + Clone,
C: Collector<I::Item>,
ItFac: FnMut() -> I,
ClFac: FnMut() -> C,
SbPred: FnMut(I) -> bool,
Pred: FnMut(I, C::Output, &mut dyn Iterator<Item = I::Item>) -> Result<(), PredError>,
{
type Output<'a> = C::Output;
fn collector_test_parts(
&mut self,
) -> CollectorTestParts<
impl Iterator<Item = I::Item>,
impl Collector<I::Item, Output = Self::Output<'_>>,
impl FnMut(Self::Output<'_>, &mut dyn Iterator<Item = I::Item>) -> Result<(), PredError>,
> {
CollectorTestParts {
iter: (self.iter_factory)(),
collector: (self.collector_factory)(),
should_break: (self.should_break_pred)((self.iter_factory)()),
pred: |output, it| (self.pred)((self.iter_factory)(), output, it),
}
}
}
fn test_collector_part<T>(
tester: &mut (impl CollectorTester<T> + ?Sized),
items_for_fuse_test: Option<impl Iterator<Item = T> + Clone>,
) -> TestCaseResult {
{
let mut test_parts = tester.collector_test_parts();
let has_stopped = (|| {
test_parts.collector.break_hint()?;
test_parts
.iter
.try_for_each(|item| test_parts.collector.collect(item))
})()
.is_break();
prop_assert_eq!(
has_stopped,
test_parts.should_break,
"`collect()` didn't break correctly"
);
if has_stopped && let Some(items) = items_for_fuse_test.clone() {
for item in items {
prop_assert!(
test_parts.collector.collect(item).is_break(),
"`collect()` isn't actually fused"
);
}
}
(test_parts.pred)(test_parts.collector.finish(), &mut test_parts.iter)
.map_err(|e| e.of_method("collect"))?;
}
{
let mut test_parts = tester.collector_test_parts();
let has_stopped = test_parts
.collector
.collect_many(&mut test_parts.iter)
.is_break();
prop_assert_eq!(
has_stopped,
test_parts.should_break,
"`collect_many()` didn't break correctly"
);
if has_stopped && let Some(items) = items_for_fuse_test {
prop_assert!(
test_parts.collector.collect_many(items).is_break(),
"`collect_many()` isn't actually fused"
);
}
(test_parts.pred)(test_parts.collector.finish(), &mut test_parts.iter)
.map_err(|e| e.of_method("collect_many"))?;
}
{
let mut test_parts = tester.collector_test_parts();
(test_parts.pred)(
test_parts
.collector
.collect_then_finish(&mut test_parts.iter),
&mut test_parts.iter,
)
.map_err(|e| e.of_method("collect_then_finish"))?;
}
Ok(())
}