1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use arbitrary::*;
use either::Either;

use crate::*;

/// The trait bounds for the target of a Fact
pub trait Target<'a>:
    'a + std::fmt::Debug + Clone + Send + Sync + PartialEq + Arbitrary<'a>
{
}
impl<'a, T> Target<'a> for T where
    T: 'a + std::fmt::Debug + Clone + Send + Sync + PartialEq + Arbitrary<'a>
{
}

/// The trait bounds for the State of a Fact
pub trait State: std::fmt::Debug + Clone + Send + Sync {}
impl<T> State for T where T: std::fmt::Debug + Clone + Send + Sync {}

/// A declarative representation of a constraint on some data, which can be
/// used to both make an assertion (check) or to mold some arbitrary existing
/// data into a shape which passes that same assertion (mutate)
pub trait Fact<'a, T>: Send + Sync + Clone + std::fmt::Debug
where
    T: Target<'a>,
{
    /// Change the label
    fn labeled(self, label: impl ToString) -> Self;

    /// Get the label of this fact
    fn label(&self) -> String;

    /// Assert that the constraint is satisfied for given data.
    ///
    /// If the mutation function is written properly, we get a check for free
    /// by using a special Generator which fails upon mutation. If this is for
    /// some reason unreasonable, a check function can be written by hand, but
    /// care must be taken to make sure it perfectly lines up with the mutation function.
    #[tracing::instrument(fields(fact_impl = "Fact"), skip(self))]
    fn check(mut self, t: &T) -> Check {
        let mut g = Generator::checker();
        Check::from_mutation(self.mutate(&mut g, t.clone()))
    }

    /// Apply a mutation which moves the t closer to satisfying the overall
    /// constraint.
    // #[tracing::instrument(skip(self, g))]
    fn mutate(&mut self, g: &mut Generator<'a>, t: T) -> Mutation<T>;

    /// Make this many attempts to satisfy a constraint before giving up and panicking.
    ///
    /// If you are combining highly contentious facts together and relying on randomness
    /// to find a solution, this limit may need to be higher. In general, you should try
    /// to write facts that don't interfere with each other so that the constraint can be
    /// met on the first attempt, or perhaps the second or third. If necessary, this can
    /// be raised to lean more on random search.
    fn satisfy_attempts(&self) -> usize {
        SATISFY_ATTEMPTS
    }

    /// Mutate a value such that it satisfies the constraint.
    /// If the constraint cannot be satisfied, panic.
    #[tracing::instrument(fields(fact_impl = "Fact"), skip(self, g))]
    fn satisfy(&mut self, g: &mut Generator<'a>, t: T) -> ContrafactResult<T> {
        tracing::trace!("satisfy");
        let mut last_failure: Vec<String> = vec![];
        let mut next = t.clone();
        for _i in 0..self.satisfy_attempts() {
            let mut m = self.clone();
            next = m.mutate(g, next).unwrap();
            if let Err(errs) = self.clone().check(&next).result()? {
                last_failure = errs;
            } else {
                *self = m;
                return Ok(next);
            }
        }
        panic!(
            "Could not satisfy a constraint even after {} attempts. Last check failure: {:?}",
            SATISFY_ATTEMPTS, last_failure
        );
    }

    #[tracing::instrument(fields(fact_impl = "Fact"), skip(self, g))]
    /// Build a new value such that it satisfies the constraint
    fn build_fallible(mut self, g: &mut Generator<'a>) -> ContrafactResult<T> {
        let t = T::arbitrary(g).unwrap();
        self.satisfy(g, t)
    }

    /// Build a new value such that it satisfies the constraint, panicking on error
    #[tracing::instrument(fields(fact_impl = "Fact"), skip(self, g))]
    fn build(self, g: &mut Generator<'a>) -> T {
        self.build_fallible(g).unwrap()
    }
}

impl<'a, T, F1, F2> Fact<'a, T> for Either<F1, F2>
where
    T: Target<'a>,
    F1: Fact<'a, T> + ?Sized,
    F2: Fact<'a, T> + ?Sized,
{
    #[tracing::instrument(fields(fact_impl = "Either"), skip(self, g))]
    fn mutate(&mut self, g: &mut Generator<'a>, t: T) -> Mutation<T> {
        match self {
            Either::Left(f) => f.mutate(g, t),
            Either::Right(f) => f.mutate(g, t),
        }
    }

    fn label(&self) -> String {
        match self {
            Either::Left(f) => f.label(),
            Either::Right(f) => f.label(),
        }
    }

    fn labeled(self, label: impl ToString) -> Self {
        match self {
            Either::Left(f) => Either::Left(f.labeled(label)),
            Either::Right(f) => Either::Right(f.labeled(label)),
        }
    }
}

#[tracing::instrument(skip(facts))]
fn collect_checks<'a, T, F>(facts: Vec<F>, t: &T) -> Check
where
    T: Target<'a>,
    F: Fact<'a, T>,
{
    let checks = facts
        .into_iter()
        .enumerate()
        .map(|(i, f)| {
            Ok(f.check(t)
                .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)
}