use core::{cell::Cell, num::NonZero};
use crate::{
accum::Accum,
error::{AccumVerifyError, SuiteError, Unequal},
side::Side,
strategy::Alternate,
};
pub trait Suite {
type Item;
type Error<'a>
where
Self: 'a;
fn accumulate_verify<
'a,
A: ?Sized + Accum<Item = Self::Item>,
I: Clone + DoubleEndedIterator<Item = Self::Item>,
>(
&'a mut self,
iter: &I,
accum: &'a mut A,
) -> Result<bool, SuiteError<'a, A, Self::Error<'a>>>;
fn run<
'a,
A: ?Sized + Accum<Item = Self::Item>,
I: Clone + DoubleEndedIterator<Item = Self::Item>,
>(
&'a mut self,
iter: &I,
accum: &'a mut A,
) -> Result<(), SuiteError<'a, A, Self::Error<'a>>> {
let cellf = Cell::from_mut(self);
let cell_accum = Cell::from_mut(accum);
unsafe { while (*cellf.as_ptr()).accumulate_verify(iter, &mut (*cell_accum.as_ptr()))? {} }
Ok(())
}
}
fn next_strategy(strategy: Alternate) -> Option<Alternate> {
if strategy.start == Side::Forward {
Some(strategy.swap())
} else {
strategy.shrink()
}
}
#[derive(Copy, Clone, Debug)]
pub struct Simple<'a, T> {
verifier: &'a [T],
strategy: Alternate,
}
impl<'a, T> Simple<'a, T> {
pub fn new(slice: &'a [T]) -> Simple<'a, T> {
let Some(period) = NonZero::new(slice.len()) else {
panic!("cannot create simple strategy with empty slice")
};
Simple {
verifier: slice,
strategy: Alternate {
start: Side::Forward,
period,
},
}
}
}
impl<'a, T: PartialEq> Suite for Simple<'a, T> {
type Item = T;
type Error<'b>
= Unequal<'b, T>
where
'a: 'b;
#[inline]
fn accumulate_verify<
'b,
A: ?Sized + Accum<Item = T>,
I: Clone + DoubleEndedIterator<Item = T>,
>(
&'b mut self,
iter: &I,
accum: &'b mut A,
) -> Result<bool, SuiteError<'b, A, Unequal<'b, T>>> {
let cell_strategy = Cell::from_mut(&mut self.strategy);
unsafe {
accum
.accumulate_verify(iter.clone(), &(*cell_strategy.as_ptr()), self.verifier)
.map_err(AccumVerifyError::erased_strategy)?;
}
if let Some(strategy) = next_strategy(cell_strategy.get()) {
cell_strategy.set(strategy);
Ok(true)
} else {
Ok(false)
}
}
}
#[cfg(any(doc, feature = "alloc"))]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
mod alloc {
use alloc::vec::Vec;
use core::{cell::Cell, num::NonZero};
use crate::{
accum::Accum,
error::{FailedStrategy, Inconsistent, SuiteError},
side::Side,
strategy::{Alternate, Always},
suite::{Suite, next_strategy},
};
pub(super) fn first_run<
'a,
A: ?Sized + Accum,
I: Clone + DoubleEndedIterator<Item = A::Item>,
>(
data: &mut Vec<A::Item>,
iter: &I,
accum: &'a mut A,
) -> Result<NonZero<usize>, SuiteError<'a, A, Inconsistent<'a, A::Item>>> {
let cell_accum = Cell::from_mut(accum);
let strategy = &Always(Side::Forward);
unsafe {
(*cell_accum.as_ptr())
.accumulate(iter.clone(), strategy)
.map_err(Into::into)?;
data.reserve((*cell_accum.as_ptr()).len());
while let Some(item) = (*cell_accum.as_ptr()).pop_forward() {
data.push(item);
}
}
data.reverse();
let Some(period) = NonZero::new(data.len()) else {
return Err(FailedStrategy::empty(strategy, Inconsistent::Empty)
.erased_strategy()
.into());
};
Ok(period)
}
#[derive(Clone, Debug)]
pub struct Consistent<T> {
data: Vec<T>,
strategy: Alternate,
}
impl<T> Consistent<T> {
#[inline]
pub fn new() -> Consistent<T> {
Consistent {
data: Vec::new(),
strategy: Alternate {
start: Side::Forward,
period: NonZero::new(1).unwrap(),
},
}
}
}
impl<T> Default for Consistent<T> {
#[inline]
fn default() -> Consistent<T> {
Consistent::new()
}
}
impl<T: PartialEq> Suite for Consistent<T> {
type Item = T;
type Error<'a>
= Inconsistent<'a, T>
where
T: 'a;
fn accumulate_verify<
'a,
A: ?Sized + crate::Accum<Item = T>,
I: Clone + DoubleEndedIterator<Item = T>,
>(
&'a mut self,
iter: &I,
accum: &'a mut A,
) -> Result<bool, SuiteError<'a, A, Inconsistent<'a, T>>> {
let cell_strategy = Cell::from_mut(&mut self.strategy);
if self.data.is_empty() {
let period = first_run(&mut self.data, iter, accum)?;
cell_strategy.set(Alternate {
start: Side::Backward,
period,
});
Ok(true)
} else {
unsafe {
accum
.accumulate_verify(iter.clone(), &(*cell_strategy.as_ptr()), &self.data[..])
.map_err(|err| err.map(Inconsistent::Unequal).erased_strategy())?;
}
if let Some(strategy) = next_strategy(cell_strategy.get()) {
cell_strategy.set(strategy);
Ok(true)
} else {
Ok(false)
}
}
}
}
}
#[cfg(any(doc, feature = "alloc"))]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
pub use self::alloc::Consistent;
#[cfg(any(doc, all(feature = "rand", feature = "std")))]
#[cfg_attr(feature = "nightly", doc(cfg(all(feature = "rand", feature = "std"))))]
mod rand_std {
use core::cell::Cell;
use crate::{
error::{Inconsistent, SuiteError},
strategy::Random,
suite::{Suite, alloc::first_run},
};
#[derive(Clone, Debug)]
pub struct RandomConsistent<T, R: rand::SeedableRng> {
iters_left: usize,
data: Vec<T>,
strategy: Random<R>,
}
impl<T, R: rand::SeedableRng> RandomConsistent<T, R> {
#[inline]
pub fn new(iters: usize) -> RandomConsistent<T, R> {
RandomConsistent {
iters_left: iters,
data: Vec::new(),
strategy: Random::random(0.5),
}
}
}
impl<T: PartialEq, R: rand::SeedableRng + rand::RngCore> Suite for RandomConsistent<T, R> {
type Item = T;
type Error<'a>
= Inconsistent<'a, T>
where
T: 'a,
R: 'a;
fn accumulate_verify<
'a,
A: ?Sized + crate::Accum<Item = T>,
I: Clone + DoubleEndedIterator<Item = T>,
>(
&'a mut self,
iter: &I,
accum: &'a mut A,
) -> Result<bool, SuiteError<'a, A, Inconsistent<'a, T>>> {
let cell_strategy = Cell::from_mut(&mut self.strategy);
if self.data.is_empty() {
let _ = first_run(&mut self.data, iter, accum)?;
Ok(true)
} else {
unsafe {
accum
.accumulate_verify(iter.clone(), &(*cell_strategy.as_ptr()), &self.data[..])
.map_err(|err| err.map(Inconsistent::Unequal).erased_strategy())?;
if self.iters_left > 0 {
(*cell_strategy.as_ptr()) = Random::random(0.5);
self.iters_left -= 1;
Ok(true)
} else {
Ok(false)
}
}
}
}
}
}
#[cfg(any(doc, all(feature = "rand", feature = "std")))]
#[cfg_attr(feature = "nightly", doc(cfg(all(feature = "rand", feature = "std"))))]
pub use self::rand_std::RandomConsistent;
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use {
crate::{accum::Unbounded, suite::Consistent},
core::iter,
};
use crate::{
accum::{Accum, Bounded},
suite::{Simple, Suite},
};
#[test]
#[cfg(feature = "alloc")]
fn inconsistent_empty() {
let mut suite = Consistent::new();
let mut accum = <Bounded<u32, 0>>::new();
let result = suite.run(&iter::empty(), &mut accum);
assert!(result.is_err());
let result = result.unwrap_err();
assert_eq!(
result.to_string(),
r#"failed strategy "always forward": [..] due to verification error: no data was returned by iterator"#
);
}
#[test]
#[cfg(feature = "alloc")]
fn consistent_weird_iterator() {
let mut suite = Consistent::new();
let mut accum = Unbounded::new();
let iter = (1..10).rev().flat_map(|x| 0..x);
let result = suite.run(&iter, &mut accum);
if let Err(err) = result {
panic!("{}", err);
}
}
#[test]
fn simple_weird_iterator() {
let mut suite = Simple::new(&[
0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0, ]);
let mut accum = <Bounded<u32, 45>>::new();
let iter = (1..10).rev().flat_map(|x| 0..x);
let result = suite.run(&iter, &mut accum);
if let Err(err) = result {
panic!("{}", err);
}
}
}