use std::fmt::Debug;
use proptest::{prelude::*, test_runner::TestCaseResult};
use crate::collector::{Collector, CollectorBase};
pub trait CollectorTester {
type Item<'a>
where
Self: 'a;
type Output<'a>
where
Self: 'a;
#[allow(clippy::type_complexity)] fn collector_test_parts<'a>(
&'a mut self,
) -> CollectorTestParts<
impl Iterator<Item = Self::Item<'a>>,
impl Collector<Self::Item<'a>, Output = Self::Output<'a>>,
impl FnMut(Self::Output<'a>, &mut dyn Iterator<Item = Self::Item<'a>>) -> Result<(), PredError>,
impl Iterator<Item = Self::Item<'a>>,
>;
}
pub struct CollectorTestParts<I, C, P, IF>
where
I: Iterator,
C: Collector<I::Item>,
P: FnMut(C::Output, &mut dyn Iterator<Item = I::Item>) -> Result<(), PredError>,
IF: Iterator<Item = I::Item>,
{
pub iter: I,
pub collector: C,
pub should_break: bool,
pub pred: P,
pub iter_for_fuse_test: Option<IF>,
}
#[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: CollectorTester {
fn test_collector(&mut self) -> TestCaseResult {
test_collector_part(self)
}
}
impl<CT> CollectorTesterExt for CT where CT: CollectorTester {}
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
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 Item<'a>
= I::Item
where
ItFac: 'a,
ClFac: 'a,
SbPred: 'a,
Pred: 'a,
I: 'a,
C: 'a;
type Output<'a>
= C::Output
where
ItFac: 'a,
ClFac: 'a,
SbPred: 'a,
Pred: 'a,
I: 'a,
C: 'a;
fn collector_test_parts<'a>(
&'a mut self,
) -> CollectorTestParts<
impl Iterator<Item = Self::Item<'a>>,
impl Collector<Self::Item<'a>, Output = Self::Output<'a>>,
impl FnMut(Self::Output<'a>, &mut dyn Iterator<Item = Self::Item<'a>>) -> Result<(), PredError>,
impl Iterator<Item = Self::Item<'a>>,
> {
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),
iter_for_fuse_test: None::<std::iter::Empty<I::Item>>,
}
}
}
pub fn none_iter_for_fuse_test<T>() -> Option<impl Iterator<Item = T>> {
None::<std::iter::Empty<T>>
}
fn test_collector_part<CT>(tester: &mut CT) -> TestCaseResult
where
CT: CollectorTester + ?Sized,
{
{
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) = test_parts.iter_for_fuse_test {
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) = test_parts.iter_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(())
}