model/
lib.rs

1//! simple model-checking tools for rust.
2//!
3//! aims to reduce the boiler plate required
4//! to write model-based and linearizability tests.
5//!
6//! it can find linearizability violations in
7//! implementations without having any knowledge
8//! of their inner-workings, by running sequential
9//! operations on different commands and then
10//! trying to find a sequential ordering that results
11//! in the same return values.
12//!
13//! **important**: the crate makes use of
14//! [proptest](https://crates.io/crates/proptest) via
15//! macros. ensure that you are using the same version
16//! of `proptest` that `model` lists in `Cargo.toml`,
17//! otherwise mismatched API change will manifest as
18//! strange compile-time errors hidden in macros.
19//!
20//! model-based testing:
21//!
22//! ```
23//! #[macro_use]
24//! extern crate model;
25//!
26//! use std::sync::atomic::{AtomicUsize, Ordering};
27//!
28//!# fn main() {
29//! model! {
30//!     Model => let m = AtomicUsize::new(0),
31//!     Implementation => let mut i: usize = 0,
32//!     Add(usize)(v in 0usize..4) => {
33//!         let expected = m.fetch_add(v, Ordering::SeqCst) + v;
34//!         i += v;
35//!         assert_eq!(expected, i);
36//!     },
37//!     Set(usize)(v in 0usize..4) => {
38//!         m.store(v, Ordering::SeqCst);
39//!         i = v;
40//!     },
41//!     Eq(usize)(v in 0usize..4) => {
42//!         let expected = m.load(Ordering::SeqCst) == v;
43//!         let actual = i == v;
44//!         assert_eq!(expected, actual);
45//!     },
46//!     Cas((usize, usize))((old, new) in (0usize..4, 0usize..4)) => {
47//!         let expected =
48//!             m.compare_and_swap(old, new, Ordering::SeqCst);
49//!         let actual = if i == old {
50//!             i = new;
51//!             old
52//!         } else {
53//!             i
54//!         };
55//!         assert_eq!(expected, actual);
56//!     }
57//! }
58//!# }
59//! ```
60//!
61//! linearizability testing:
62//!
63//! ```no_run
64//! #[macro_use]
65//! extern crate model;
66//!
67//! use std::sync::atomic::{AtomicUsize, Ordering};
68//!
69//!# fn main() {
70//! linearizable! {
71//!     Implementation => let i = model::Shared::new(AtomicUsize::new(0)),
72//!     BuggyAdd(usize)(v in 0usize..4) -> usize {
73//!         let current = i.load(Ordering::SeqCst);
74//!         std::thread::yield_now();
75//!         i.store(current + v, Ordering::SeqCst);
76//!         current + v
77//!     },
78//!     Set(usize)(v in 0usize..4) -> () {
79//!         i.store(v, Ordering::SeqCst)
80//!     },
81//!     BuggyCas((usize, usize))((old, new) in (0usize..4, 0usize..4)) -> usize {
82//!         let current = i.load(Ordering::SeqCst);
83//!         std::thread::yield_now();
84//!         if current == old {
85//!             i.store(new, Ordering::SeqCst);
86//!             new
87//!         } else {
88//!             current
89//!         }
90//!     }
91//! }
92//!# }
93//! ```
94extern crate permutohedron;
95
96pub extern crate proptest as pt;
97#[doc(hidden)]
98pub use pt::*;
99
100pub struct Shared<T>(*mut T);
101
102impl<T> Clone for Shared<T> {
103    fn clone(&self) -> Shared<T> {
104        *self
105    }
106}
107
108impl<T> Copy for Shared<T> {}
109
110unsafe impl<T> Sync for Shared<T> {}
111
112unsafe impl<T> Send for Shared<T> {}
113
114impl<T> std::ops::Deref for Shared<T> {
115    type Target = T;
116
117    fn deref(&self) -> &T {
118        unsafe { &*self.0 }
119    }
120}
121
122impl<T> Shared<T> {
123    pub fn new(inner: T) -> Shared<T> {
124        Shared(Box::into_raw(Box::new(inner)))
125    }
126}
127
128#[macro_export]
129macro_rules! model {
130    (
131        Model => $model:stmt,
132        Implementation => $implementation:stmt,
133        $($op:ident ($($type:ty),*) ($parm:pat in $strategy:expr) => $body:expr),*
134    ) => {
135        model! {
136            Config => $crate::default_config(file!()),
137            Model => $model,
138            Implementation => $implementation,
139            $($op ($($type),*) ($parm in $strategy) => $body),*
140        }
141    };
142    (
143        Config => $config:expr,
144        Model => $model:stmt,
145        Implementation => $implementation:stmt,
146        $($op:ident ($($type:ty),*) ($parm:pat in $strategy:expr) => $body:expr),*
147    ) => {
148        use $crate::pt::collection::vec as prop_vec;
149        use $crate::pt::prelude::*;
150        use $crate::pt::test_runner::TestRunner;
151
152        #[derive(Debug)]
153        enum Op {
154            $(
155                $op($($type),*)
156            ),*
157        }
158
159        fn arb() -> BoxedStrategy<Vec<Op>> {
160            prop_vec(
161                prop_oneof![
162                    $(
163                        $strategy.prop_map(Op::$op)
164                    ),*
165                ],
166                0..40,
167            ).boxed()
168        }
169
170        let config = $config;
171        let mut runner = TestRunner::new(config);
172
173        match runner.run(&arb(), |ops| {
174            $model;
175            $implementation;
176
177            for op in ops {
178                match op {
179                    $(Op::$op($parm) => $body),*
180                };
181            };
182            Ok(())
183        }) {
184            Ok(_) => {}
185            Err(e) =>  panic!("{}\n{}", e, runner),
186        }
187    }
188}
189
190#[macro_export]
191macro_rules! linearizable {
192    (
193        Implementation => $implementation:stmt,
194        $($op:ident ($($type:ty),*) ($parm:pat in $strategy:expr) -> $ret:ty $body:block),*
195    ) => {
196        linearizable! {
197            Config => $crate::default_config(file!()),
198            Implementation => $implementation,
199            $($op ($($type),*) ($parm in $strategy) -> $ret $body),*
200        }
201    };
202    (
203        Config => $config:expr,
204        Implementation => $implementation:stmt,
205        $($op:ident ($($type:ty),*) ($parm:pat in $strategy:expr) -> $ret:ty $body:block),*
206    ) => {
207        use $crate::pt::collection::vec as prop_vec;
208        use $crate::pt::prelude::*;
209        use $crate::pt::test_runner::TestRunner;
210
211        use std::ops::Deref as StdDeref;
212
213        #[derive(Debug, Clone)]
214        enum Op {
215            $(
216                $op($($type),*)
217            ),*
218        }
219
220        #[derive(Debug, PartialEq)]
221        enum Ret {
222            $(
223                $op($ret)
224            ),*
225        }
226
227        fn arb() -> BoxedStrategy<(usize, Vec<Op>)> {
228            prop_vec(
229                prop_oneof![
230                    $(
231                        $strategy.prop_map(Op::$op)
232                    ),*
233                ],
234                1..4,
235            )
236                .prop_flat_map(|ops| (0..ops.len(), Just(ops)))
237                .boxed()
238        }
239
240        let config = $config;
241        let mut runner = TestRunner::new(config);
242
243        match runner.run(&arb(), |(split_point, ref ops)| {
244            $implementation;
245
246            let ops1 = ops[0..split_point].to_vec();
247            let ops2 = ops[split_point..].to_vec();
248
249            let t1 = std::thread::spawn(move || {
250                    let mut ret = vec![];
251                    for op in ops1 {
252                        ret.push(match op {
253                            $(
254                                Op::$op($parm) => Ret::$op($body)
255                            ),*
256                        });
257                    }
258                    ret
259                });
260
261            let t2 = std::thread::spawn(move || {
262                    let mut ret = vec![];
263                    for op in ops2 {
264                        ret.push(match op {
265                            $(
266                                Op::$op($parm) => Ret::$op($body)
267                            ),*
268                        });
269                    }
270                    ret
271                });
272
273            let r1 = t1.join().expect("thread should not panic");
274            let r2 = t2.join().expect("thread should not panic");
275
276            // try to find sequential walk through ops
277            let o1 = ops[0..split_point].to_vec();
278            let o2 = ops[split_point..].to_vec();
279
280            let calls1: Vec<(Op, Ret)> = o1.into_iter().zip(r1.into_iter()).collect();
281            let calls2: Vec<(Op, Ret)> = o2.into_iter().zip(r2.into_iter()).collect();
282            let mut indexes = vec![0; calls1.len()];
283            indexes.resize(calls1.len() + calls2.len(), 1);
284            let calls = vec![calls1, calls2];
285
286            let mut linearizable = false;
287
288            let call_permutations = $crate::permutohedron_heap(&mut indexes);
289
290            'outer: for walk in call_permutations {
291                $implementation;
292
293                let mut indexes = vec![0, 0];
294                // println!("trying walk {:?}", walk);
295
296                for idx in walk {
297                    let (ref op, ref expected) = calls[idx][indexes[idx]];
298                    indexes[idx] += 1;
299
300                    match *op {
301                        $(
302                            Op::$op($parm) => {
303                                let ret = Ret::$op($body);
304                                if ret != *expected {
305                                    continue 'outer;
306                                }
307                            }
308                        ),*
309                    }
310                }
311
312                linearizable = true;
313                break;
314            }
315
316            assert!(linearizable, "operations are not linearizable: {:?}", calls);
317
318            Ok(())
319        }) {
320            Ok(_) => {}
321            Err(e) =>  panic!("{}\n{}", e, runner),
322        }
323    };
324}
325
326pub fn permutohedron_heap<'a, Data, T>(
327    orig: &'a mut Data,
328) -> permutohedron::Heap<'a, Data, T>
329where
330    Data: 'a + Sized + AsMut<[T]>,
331    T: 'a,
332{
333    permutohedron::Heap::new(orig)
334}
335
336/// Provide a default config with number of cases respecting the `PROPTEST_CASES` env variable.
337use pt::test_runner::Config;
338
339pub fn default_config(file: &'static str) -> Config {
340    let cases = std::env::var("PROPTEST_CASES")
341        .ok()
342        .and_then(|val| val.parse().ok())
343        .unwrap_or(1000);
344    Config::with_cases(cases).clone_with_source_file(file)
345}