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    struct ShapedGenericWrapper<T> {
72        item: T,
73    }
74
75    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
76    struct ShapedUsesGenericWrapper {
77        wrapper: ShapedGenericWrapper<bool>,
78    }
79
80    type ShapedPayloadAlias = ShapedPayload<bool>;
81
82    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
83    struct ShapedAliasField {
84        payload: ShapedPayloadAlias,
85    }
86
87    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
88    struct ShapedOpaqueForeignPath {
89        set: std::collections::BTreeSet<u8>,
90    }
91
92    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
93    struct ShapedProjection<T> {
94        item: <Vec<T> as IntoIterator>::Item,
95    }
96
97    #[derive(Clone, Debug, Eq, PartialEq, Pbt)]
98    enum ShapedTree {
99        Leaf,
100        Branch {
101            left: Box<Self>,
102            value: usize,
103            flags: Vec<bool>,
104            right: Box<Self>,
105        },
106    }
107
108    #[test]
109    fn instantiability_logic() {
110        search::assert_eq(N_CASES, |pi: &PartiallyInstantiable| {
111            (pi.clone(), PartiallyInstantiable::Instantiable)
112        });
113    }
114
115    #[test]
116    fn search_and_minimize() {
117        let maybe_witness: Option<Foo> =
118            search::witness(N_CASES, |foo: &Foo| foo.bus_factor() >= 3);
119        assert_eq!(
120            maybe_witness,
121            Some(Foo::Baz {
122                a: 0,
123                b: 0,
124                c: vec![Foo::Bar, Foo::Bar, Foo::Bar],
125            }),
126        );
127    }
128
129    #[test]
130    fn sigma() {
131        search::assert(N_CASES, |u: &NonAnswer| **u != 42);
132    }
133
134    #[test]
135    fn empty_enum_is_supported() {
136        let maybe_witness: Option<Uninhabited> = search::witness(N_CASES, |_| true);
137        assert_eq!(maybe_witness, None);
138    }
139
140    #[test]
141    fn shaped_empty_enums_have_vacuous_eliminators() {
142        let absurd: fn(&ShapedNever) -> usize = |never| never.elim(());
143        let _ = absurd;
144    }
145
146    #[test]
147    fn shaped_generic_fields_do_not_need_recursion() {
148        let empty = <ShapedPayload<bool> as ShapedPayloadShaped>::empty(ShapedPayloadEmpty);
149        let empty_len = empty.elim(
150            (),
151            |(), ShapedPayloadEmpty| 0_usize,
152            |(), ShapedPayloadBits(bits)| bits.len(),
153        );
154        let payload =
155            <ShapedPayload<bool> as ShapedPayloadShaped>::bits(ShapedPayloadBits(vec![true]));
156        let len = payload.elim(
157            (),
158            |(), ShapedPayloadEmpty| 0_usize,
159            |(), ShapedPayloadBits(bits)| bits.len(),
160        );
161
162        assert_eq!(empty_len, 0);
163        assert_eq!(len, 1);
164    }
165
166    #[test]
167    fn shaped_custom_generic_field_paths_are_opaque_slots() {
168        let value = ShapedUsesGenericWrapper {
169            wrapper: ShapedGenericWrapper { item: true },
170        };
171        let selected = value.elim((), |(), ShapedUsesGenericWrapperShape { wrapper }| {
172            wrapper.item
173        });
174
175        assert!(selected);
176    }
177
178    #[test]
179    fn shaped_qualified_projection_fields_are_opaque_slots() {
180        let value = ShapedProjection::<u8> { item: 9 };
181        let selected = value.elim((), |(), ShapedProjectionShape { item }| *item);
182
183        assert_eq!(selected, 9);
184    }
185
186    #[test]
187    fn shaped_unknown_paths_are_opaque_slots() {
188        let alias = ShapedAliasField {
189            payload: ShapedPayload::Empty,
190        };
191        let is_empty = alias.elim((), |(), ShapedAliasFieldShape { payload }| {
192            matches!(*payload, ShapedPayload::Empty)
193        });
194        let foreign = ShapedOpaqueForeignPath {
195            set: [7_u8].into_iter().collect(),
196        };
197        let contains = foreign.elim((), |(), ShapedOpaqueForeignPathShape { set }| {
198            set.contains(&7)
199        });
200
201        assert!(is_empty);
202        assert!(contains);
203    }
204
205    #[test]
206    fn shaped_macro_generates_field_wise_eliminator() {
207        let tree = <ShapedTree as ShapedTreeShaped>::branch(ShapedTreeBranch {
208            left: Box::new(ShapedTree::Leaf),
209            value: 7,
210            flags: vec![true, false],
211            right: Box::new(ShapedTree::Leaf),
212        });
213
214        let selected = tree.elim(
215            5,
216            |_, ShapedTreeLeaf| 0,
217            |state, branch| {
218                assert!(matches!(**branch.left, ShapedTree::Leaf));
219                assert!(matches!(**branch.right, ShapedTree::Leaf));
220                assert_eq!(branch.flags.as_slice(), &[true, false]);
221                state + *branch.value
222            },
223        );
224
225        assert_eq!(selected, 12);
226    }
227}