use arbitrary::*;
use crate::*;
pub(crate) const SATISFY_ATTEMPTS: usize = 7;
pub trait Bounds<'a>: std::fmt::Debug + Clone + PartialEq + Arbitrary<'a> {}
impl<'a, T> Bounds<'a> for T where T: std::fmt::Debug + Clone + PartialEq + Arbitrary<'a> {}
pub type BoxFact<'a, T> = Box<dyn 'a + Fact<'a, T>>;
pub type FactsRef<'a, T> = Vec<BoxFact<'a, T>>;
pub type Facts<T> = FactsRef<'static, T>;
pub trait Fact<'a, T>
where
T: Bounds<'a>,
{
fn check(&self, obj: &T) -> Check {
let mut g = Generator::checker();
Check::from_mutation(self.mutate(obj.clone(), &mut g))
}
fn mutate(&self, obj: T, g: &mut Generator<'a>) -> Mutation<T>;
fn advance(&mut self, obj: &T);
fn satisfy(&mut self, mut obj: T, g: &mut Generator<'a>) -> ContrafactResult<T> {
let mut last_failure: Vec<String> = vec![];
for _i in 0..SATISFY_ATTEMPTS {
obj = self.mutate(obj, g).unwrap();
if let Err(errs) = self.check(&obj).result()? {
last_failure = errs;
} else {
return Ok(obj);
}
}
panic!(
"Could not satisfy a constraint even after {} attemps. Last check failure: {:?}",
SATISFY_ATTEMPTS, last_failure
);
}
fn build_fallible(&mut self, g: &mut Generator<'a>) -> ContrafactResult<T> {
let obj = T::arbitrary(g).unwrap();
self.satisfy(obj, g)
}
fn build(&mut self, g: &mut Generator<'a>) -> T {
self.build_fallible(g).unwrap()
}
}
impl<'a, T, F> Fact<'a, T> for Box<F>
where
T: Bounds<'a>,
F: Fact<'a, T> + ?Sized,
{
#[tracing::instrument(skip(self, g))]
fn mutate(&self, obj: T, g: &mut Generator<'a>) -> Mutation<T> {
(*self).as_ref().mutate(obj, g)
}
#[tracing::instrument(skip(self))]
fn advance(&mut self, obj: &T) {
(*self).as_mut().advance(obj)
}
}
impl<'a, T, F> Fact<'a, T> for &mut [F]
where
T: Bounds<'a>,
F: Fact<'a, T>,
{
#[tracing::instrument(skip(self))]
fn check(&self, obj: &T) -> Check {
collect_checks(self, obj)
}
#[tracing::instrument(skip(self, g))]
fn mutate(&self, mut obj: T, g: &mut Generator<'a>) -> Mutation<T> {
for f in self.iter() {
obj = f.mutate(obj, g)?;
}
Ok(obj)
}
#[tracing::instrument(skip(self))]
fn advance(&mut self, obj: &T) {
for f in self.iter_mut() {
f.advance(obj)
}
}
}
impl<'a, T, F> Fact<'a, T> for Vec<F>
where
T: Bounds<'a>,
F: Fact<'a, T>,
{
#[tracing::instrument(skip(self))]
fn check(&self, obj: &T) -> Check {
collect_checks(self.as_slice(), obj)
}
#[tracing::instrument(skip(self, g))]
fn mutate(&self, mut obj: T, g: &mut Generator<'a>) -> Mutation<T> {
for f in self.iter() {
obj = f.mutate(obj, g)?;
}
Ok(obj)
}
#[tracing::instrument(skip(self))]
fn advance(&mut self, obj: &T) {
for f in self.iter_mut() {
f.advance(obj)
}
}
}
fn collect_checks<'a, T, F>(facts: &[F], obj: &T) -> Check
where
T: Bounds<'a>,
F: Fact<'a, T>,
{
let checks = facts
.iter()
.enumerate()
.map(|(i, f)| {
Ok(f.check(obj)
.failures()?
.iter()
.map(|e| format!("fact {}: {}", i, e))
.collect())
})
.collect::<ContrafactResult<Vec<Vec<Failure>>>>()
.map(|fs| fs.into_iter().flatten().collect());
Check::from_result(checks)
}