Skip to main content

pbt_tests/
lib.rs

1use {
2    core::convert::Infallible,
3    pbt::{
4        Pbt,
5        sigma::{Predicate, Sigma},
6    },
7};
8
9#[non_exhaustive]
10#[derive(Clone, Debug, Eq, PartialEq, Pbt)]
11pub enum Foo {
12    Bar,
13    Baz { a: u64, b: u64, c: Vec<Foo> },
14}
15
16#[derive(Clone, Debug, Eq, PartialEq, Pbt)]
17pub enum PartiallyInstantiable {
18    Instantiable,
19    Uninstantiable(Infallible),
20}
21
22#[derive(Clone, Debug, Eq, PartialEq, Pbt)]
23pub enum Uninhabited {}
24
25pub type NonAnswer = Sigma<u8, NotTheAnswer>;
26
27pub enum NotTheAnswer {}
28
29impl Foo {
30    #[inline]
31    #[must_use]
32    pub fn bus_factor(&self) -> usize {
33        match *self {
34            Self::Bar => 0,
35            Self::Baz { ref c, .. } => c.len(),
36        }
37    }
38}
39
40impl Predicate<u8> for NotTheAnswer {
41    type Error = String;
42
43    #[inline]
44    fn check(candidate: &u8) -> Result<(), Self::Error> {
45        if *candidate == 42 {
46            Err(format!(
47                "The Answer to the Ultimate Question of Life, the Universe, and Everything is {candidate}",
48            ))
49        } else {
50            Ok(())
51        }
52    }
53}
54
55#[cfg(test)]
56mod test {
57    use {super::*, pbt::search, pretty_assertions::assert_eq};
58
59    const N_CASES: usize = 1_000;
60
61    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
62    enum ShapedNever {}
63
64    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
65    enum ShapedPayload<T> {
66        Empty,
67        Bits(Vec<T>),
68    }
69
70    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
71    enum ShapedTree {
72        Leaf,
73        Branch {
74            left: Box<Self>,
75            value: usize,
76            flags: Vec<bool>,
77            right: Box<Self>,
78        },
79    }
80
81    #[test]
82    fn instantiability_logic() {
83        search::assert_eq(N_CASES, |pi: &PartiallyInstantiable| {
84            (pi.clone(), PartiallyInstantiable::Instantiable)
85        });
86    }
87
88    #[test]
89    fn search_and_minimize() {
90        let maybe_witness: Option<Foo> =
91            search::witness(N_CASES, |foo: &Foo| foo.bus_factor() >= 3);
92        assert_eq!(
93            maybe_witness,
94            Some(Foo::Baz {
95                a: 0,
96                b: 0,
97                c: vec![Foo::Bar, Foo::Bar, Foo::Bar],
98            }),
99        );
100    }
101
102    #[test]
103    fn sigma() {
104        search::assert(N_CASES, |u: &NonAnswer| **u != 42);
105    }
106
107    #[test]
108    fn empty_enum_is_supported() {
109        let maybe_witness: Option<Uninhabited> = search::witness(N_CASES, |_| true);
110        assert_eq!(maybe_witness, None);
111    }
112
113    #[test]
114    fn shaped_empty_enums_have_vacuous_eliminators() {
115        let absurd: fn(&ShapedNever) -> usize = |never| never.elim(());
116        let _ = absurd;
117    }
118
119    #[test]
120    fn shaped_generic_fields_do_not_need_recursion() {
121        let empty = <ShapedPayload<bool> as ShapedPayloadShaped>::empty(ShapedPayloadEmpty);
122        let empty_len = empty.elim(
123            (),
124            |(), ShapedPayloadEmpty| 0_usize,
125            |(), ShapedPayloadBits(bits)| bits.len(),
126        );
127        let payload =
128            <ShapedPayload<bool> as ShapedPayloadShaped>::bits(ShapedPayloadBits(vec![true]));
129        let len = payload.elim(
130            (),
131            |(), ShapedPayloadEmpty| 0_usize,
132            |(), ShapedPayloadBits(bits)| bits.len(),
133        );
134
135        assert_eq!(empty_len, 0);
136        assert_eq!(len, 1);
137    }
138
139    #[test]
140    fn shaped_macro_generates_field_wise_eliminator() {
141        let tree = <ShapedTree as ShapedTreeShaped>::branch(ShapedTreeBranch {
142            left: Box::new(ShapedTree::Leaf),
143            value: 7,
144            flags: vec![true, false],
145            right: Box::new(ShapedTree::Leaf),
146        });
147
148        let selected = tree.elim(
149            5,
150            |_, ShapedTreeLeaf| 0,
151            |state, branch| {
152                assert!(matches!(**branch.left, ShapedTree::Leaf));
153                assert!(matches!(**branch.right, ShapedTree::Leaf));
154                assert_eq!(branch.flags.as_slice(), &[true, false]);
155                state + *branch.value
156            },
157        );
158
159        assert_eq!(selected, 12);
160    }
161}