1use std::fmt::Debug;
4
5#[derive(Debug, Clone)]
7pub enum PropertyResult {
8 Passed,
10 Failed {
12 counterexample: String,
14 tests_run: usize,
16 },
17 Skipped {
19 reason: String,
21 },
22}
23
24impl PropertyResult {
25 pub fn is_passed(&self) -> bool {
27 matches!(self, Self::Passed)
28 }
29
30 pub fn is_failed(&self) -> bool {
32 matches!(self, Self::Failed { .. })
33 }
34}
35
36pub trait Property<I> {
38 fn name(&self) -> &str;
40
41 fn check(&self, input: &I) -> bool;
43
44 fn describe_failure(&self, input: &I) -> String;
46}
47
48pub struct PropertyTest<I, G> {
50 property: Box<dyn Property<I>>,
52 generator: G,
54 max_iterations: usize,
56 seed: u64,
58}
59
60impl<I, G> PropertyTest<I, G>
61where
62 G: Iterator<Item = I>,
63 I: Debug,
64{
65 pub fn new(property: impl Property<I> + 'static, generator: G) -> Self {
67 Self {
68 property: Box::new(property),
69 generator,
70 max_iterations: 100,
71 seed: 0,
72 }
73 }
74
75 pub fn with_iterations(mut self, n: usize) -> Self {
77 self.max_iterations = n;
78 self
79 }
80
81 pub fn with_seed(mut self, seed: u64) -> Self {
83 self.seed = seed;
84 self
85 }
86
87 pub fn run(self) -> PropertyResult {
89 for (i, input) in self.generator.take(self.max_iterations).enumerate() {
90 if !self.property.check(&input) {
91 return PropertyResult::Failed {
92 counterexample: self.property.describe_failure(&input),
93 tests_run: i + 1,
94 };
95 }
96 }
97 PropertyResult::Passed
98 }
99}
100
101pub struct AppendOnlyProperty;
103
104impl Property<(Vec<u8>, Vec<u8>)> for AppendOnlyProperty {
105 fn name(&self) -> &str {
106 "append_only"
107 }
108
109 fn check(&self, (before, after): &(Vec<u8>, Vec<u8>)) -> bool {
110 after.starts_with(before)
112 }
113
114 fn describe_failure(&self, (before, after): &(Vec<u8>, Vec<u8>)) -> String {
115 format!(
116 "append-only violated: before={} bytes, after={} bytes, prefix mismatch",
117 before.len(),
118 after.len()
119 )
120 }
121}
122
123pub struct IdempotentProperty<F> {
125 operation: F,
127}
128
129impl<F, I, O> Property<I> for IdempotentProperty<F>
130where
131 F: Fn(&I) -> O,
132 O: PartialEq + Debug,
133 I: Clone + Debug,
134{
135 fn name(&self) -> &str {
136 "idempotent"
137 }
138
139 fn check(&self, input: &I) -> bool {
140 let first = (self.operation)(input);
141 let second = (self.operation)(input);
142 first == second
143 }
144
145 fn describe_failure(&self, input: &I) -> String {
146 format!("idempotent violated for input: {:?}", input)
147 }
148}
149
150pub struct CommutativeProperty<F> {
152 operation: F,
154}
155
156impl<F, I, O> Property<(I, I)> for CommutativeProperty<F>
157where
158 F: Fn(&I, &I) -> O,
159 O: PartialEq + Debug,
160 I: Clone + Debug,
161{
162 fn name(&self) -> &str {
163 "commutative"
164 }
165
166 fn check(&self, (a, b): &(I, I)) -> bool {
167 let ab = (self.operation)(a, b);
168 let ba = (self.operation)(b, a);
169 ab == ba
170 }
171
172 fn describe_failure(&self, (a, b): &(I, I)) -> String {
173 format!("commutative violated for: ({:?}, {:?})", a, b)
174 }
175}
176
177pub struct AssociativeProperty<F> {
179 operation: F,
181}
182
183impl<F, I> Property<(I, I, I)> for AssociativeProperty<F>
184where
185 F: Fn(&I, &I) -> I,
186 I: PartialEq + Debug + Clone,
187{
188 fn name(&self) -> &str {
189 "associative"
190 }
191
192 fn check(&self, (a, b, c): &(I, I, I)) -> bool {
193 let ab = (self.operation)(a, b);
194 let ab_c = (self.operation)(&ab, c);
195
196 let bc = (self.operation)(b, c);
197 let a_bc = (self.operation)(a, &bc);
198
199 ab_c == a_bc
200 }
201
202 fn describe_failure(&self, (a, b, c): &(I, I, I)) -> String {
203 format!("associative violated for: ({:?}, {:?}, {:?})", a, b, c)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_append_only_property() {
213 let prop = AppendOnlyProperty;
214
215 assert!(prop.check(&(vec![1, 2, 3], vec![1, 2, 3, 4, 5])));
217
218 assert!(!prop.check(&(vec![1, 2, 3], vec![1, 2])));
220
221 assert!(!prop.check(&(vec![1, 2, 3], vec![1, 9, 3, 4])));
223 }
224
225 #[test]
226 fn test_property_test_runner() {
227 let inputs = vec![
228 (vec![1u8], vec![1u8, 2]),
229 (vec![1u8, 2], vec![1u8, 2, 3]),
230 (vec![], vec![1u8]),
231 ];
232
233 let result = PropertyTest::new(AppendOnlyProperty, inputs.into_iter())
234 .with_iterations(10)
235 .run();
236
237 assert!(result.is_passed());
238 }
239
240 #[test]
241 fn test_property_failure() {
242 let inputs = vec![
243 (vec![1u8, 2, 3], vec![1u8, 2]), ];
245
246 let result = PropertyTest::new(AppendOnlyProperty, inputs.into_iter()).run();
247
248 assert!(result.is_failed());
249 }
250}