proptest/strategy/
flatten.rs

1//-
2// Copyright 2017 Jason Lingle
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use crate::std_facade::{fmt, Arc};
11use core::mem;
12
13use crate::strategy::fuse::Fuse;
14use crate::strategy::traits::*;
15use crate::test_runner::*;
16
17/// Adaptor that flattens a `Strategy` which produces other `Strategy`s into a
18/// `Strategy` that picks one of those strategies and then picks values from
19/// it.
20#[derive(Debug, Clone, Copy)]
21#[must_use = "strategies do nothing unless used"]
22pub struct Flatten<S> {
23    source: S,
24}
25
26impl<S: Strategy> Flatten<S> {
27    /// Wrap `source` to flatten it.
28    pub fn new(source: S) -> Self {
29        Flatten { source }
30    }
31}
32
33impl<S: Strategy> Strategy for Flatten<S>
34where
35    S::Value: Strategy,
36{
37    type Tree = FlattenValueTree<S::Tree>;
38    type Value = <S::Value as Strategy>::Value;
39
40    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
41        let meta = self.source.new_tree(runner)?;
42        FlattenValueTree::new(runner, meta)
43    }
44}
45
46/// The `ValueTree` produced by `Flatten`.
47pub struct FlattenValueTree<S: ValueTree>
48where
49    S::Value: Strategy,
50{
51    meta: Fuse<S>,
52    current: Fuse<<S::Value as Strategy>::Tree>,
53    // The final value to produce after successive calls to complicate() on the
54    // underlying objects return false.
55    final_complication: Option<Fuse<<S::Value as Strategy>::Tree>>,
56    // When `simplify()` or `complicate()` causes a new `Strategy` to be
57    // chosen, we need to find a new failing input for that case. To do this,
58    // we implement `complicate()` by regenerating values up to a number of
59    // times corresponding to the maximum number of test cases. A `simplify()`
60    // which does not cause a new strategy to be chosen always resets
61    // `complicate_regen_remaining` to 0.
62    //
63    // This does unfortunately depart from the direct interpretation of
64    // simplify/complicate as binary search, but is still easier to think about
65    // than other implementations of higher-order strategies.
66    runner: TestRunner,
67    complicate_regen_remaining: u32,
68}
69
70impl<S: ValueTree> Clone for FlattenValueTree<S>
71where
72    S::Value: Strategy + Clone,
73    S: Clone,
74    <S::Value as Strategy>::Tree: Clone,
75{
76    fn clone(&self) -> Self {
77        FlattenValueTree {
78            meta: self.meta.clone(),
79            current: self.current.clone(),
80            final_complication: self.final_complication.clone(),
81            runner: self.runner.clone(),
82            complicate_regen_remaining: self.complicate_regen_remaining,
83        }
84    }
85}
86
87impl<S: ValueTree> fmt::Debug for FlattenValueTree<S>
88where
89    S::Value: Strategy,
90    S: fmt::Debug,
91    <S::Value as Strategy>::Tree: fmt::Debug,
92{
93    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94        f.debug_struct("FlattenValueTree")
95            .field("meta", &self.meta)
96            .field("current", &self.current)
97            .field("final_complication", &self.final_complication)
98            .field(
99                "complicate_regen_remaining",
100                &self.complicate_regen_remaining,
101            )
102            .finish()
103    }
104}
105
106impl<S: ValueTree> FlattenValueTree<S>
107where
108    S::Value: Strategy,
109{
110    fn new(runner: &mut TestRunner, meta: S) -> Result<Self, Reason> {
111        let current = meta.current().new_tree(runner)?;
112        Ok(FlattenValueTree {
113            meta: Fuse::new(meta),
114            current: Fuse::new(current),
115            final_complication: None,
116            runner: runner.partial_clone(),
117            complicate_regen_remaining: 0,
118        })
119    }
120}
121
122impl<S: ValueTree> ValueTree for FlattenValueTree<S>
123where
124    S::Value: Strategy,
125{
126    type Value = <S::Value as Strategy>::Value;
127
128    fn current(&self) -> Self::Value {
129        self.current.current()
130    }
131
132    fn simplify(&mut self) -> bool {
133        self.complicate_regen_remaining = 0;
134
135        if self.current.simplify() {
136            // Now that we've simplified the derivative value, we can't
137            // re-complicate the meta value unless it gets simplified again.
138            // We also mustn't complicate back to whatever's in
139            // `final_complication` since the new state of `self.current` is
140            // the most complicated state.
141            self.meta.disallow_complicate();
142            self.final_complication = None;
143            true
144        } else if !self.meta.simplify() {
145            false
146        } else if let Ok(v) = self.meta.current().new_tree(&mut self.runner) {
147            // Shift current into final_complication and `v` into
148            // `current`. We also need to prevent that value from
149            // complicating beyond the current point in the future
150            // since we're going to return `true` from `simplify()`
151            // ourselves.
152            self.current.disallow_complicate();
153            self.final_complication = Some(Fuse::new(v));
154            mem::swap(
155                self.final_complication.as_mut().unwrap(),
156                &mut self.current,
157            );
158            // Initially complicate by regenerating the chosen value.
159            self.complicate_regen_remaining = self.runner.config().cases;
160            true
161        } else {
162            false
163        }
164    }
165
166    fn complicate(&mut self) -> bool {
167        if self.complicate_regen_remaining > 0 {
168            if self.runner.flat_map_regen() {
169                self.complicate_regen_remaining -= 1;
170
171                if let Ok(v) = self.meta.current().new_tree(&mut self.runner) {
172                    self.current = Fuse::new(v);
173                    return true;
174                }
175            } else {
176                self.complicate_regen_remaining = 0;
177            }
178        }
179
180        if self.current.complicate() {
181            return true;
182        } else if self.meta.complicate() {
183            if let Ok(v) = self.meta.current().new_tree(&mut self.runner) {
184                self.complicate_regen_remaining = self.runner.config().cases;
185                self.current = Fuse::new(v);
186                return true;
187            }
188        }
189
190        if let Some(v) = self.final_complication.take() {
191            self.current = v;
192            true
193        } else {
194            false
195        }
196    }
197}
198
199/// Similar to `Flatten`, but does not shrink the input strategy.
200///
201/// See `Strategy::prop_ind_flat_map()` fore more details.
202#[derive(Clone, Copy, Debug)]
203pub struct IndFlatten<S>(pub(super) S);
204
205impl<S: Strategy> Strategy for IndFlatten<S>
206where
207    S::Value: Strategy,
208{
209    type Tree = <S::Value as Strategy>::Tree;
210    type Value = <S::Value as Strategy>::Value;
211
212    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
213        let inner = self.0.new_tree(runner)?;
214        inner.current().new_tree(runner)
215    }
216}
217
218/// Similar to `Map` plus `Flatten`, but does not shrink the input strategy and
219/// passes the original input through.
220///
221/// See `Strategy::prop_ind_flat_map2()` for more details.
222pub struct IndFlattenMap<S, F> {
223    pub(super) source: S,
224    pub(super) fun: Arc<F>,
225}
226
227impl<S: fmt::Debug, F> fmt::Debug for IndFlattenMap<S, F> {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        f.debug_struct("IndFlattenMap")
230            .field("source", &self.source)
231            .field("fun", &"<function>")
232            .finish()
233    }
234}
235
236impl<S: Clone, F> Clone for IndFlattenMap<S, F> {
237    fn clone(&self) -> Self {
238        IndFlattenMap {
239            source: self.source.clone(),
240            fun: Arc::clone(&self.fun),
241        }
242    }
243}
244
245impl<S: Strategy, R: Strategy, F: Fn(S::Value) -> R> Strategy
246    for IndFlattenMap<S, F>
247{
248    type Tree = crate::tuple::TupleValueTree<(S::Tree, R::Tree)>;
249    type Value = (S::Value, R::Value);
250
251    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
252        let left = self.source.new_tree(runner)?;
253        let right_source = (self.fun)(left.current());
254        let right = right_source.new_tree(runner)?;
255
256        Ok(crate::tuple::TupleValueTree::new((left, right)))
257    }
258}
259
260#[cfg(test)]
261mod test {
262    use super::*;
263
264    use std::u32;
265
266    use crate::strategy::just::Just;
267    use crate::test_runner::Config;
268
269    #[test]
270    fn test_flat_map() {
271        // Pick random integer A, then random integer B which is ±5 of A and
272        // assert that B <= A if A > 10000. Shrinking should always converge to
273        // A=10001, B=10002.
274        let input = (0..65536).prop_flat_map(|a| (Just(a), (a - 5..a + 5)));
275
276        let mut failures = 0;
277        let mut runner = TestRunner::new_with_rng(
278            Config {
279                max_shrink_iters: u32::MAX - 1,
280                ..Config::default()
281            },
282            TestRng::deterministic_rng(RngAlgorithm::default()),
283        );
284        for _ in 0..1000 {
285            let case = input.new_tree(&mut runner).unwrap();
286            let result = runner.run_one(case, |(a, b)| {
287                if a <= 10000 || b <= a {
288                    Ok(())
289                } else {
290                    Err(TestCaseError::fail("fail"))
291                }
292            });
293
294            match result {
295                Ok(_) => {}
296                Err(TestError::Fail(_, v)) => {
297                    failures += 1;
298                    assert_eq!((10001, 10002), v);
299                }
300                result => panic!("Unexpected result: {:?}", result),
301            }
302        }
303
304        assert!(failures > 250);
305    }
306
307    #[test]
308    fn test_flat_map_sanity() {
309        check_strategy_sanity(
310            (0..65536).prop_flat_map(|a| (Just(a), (a - 5..a + 5))),
311            None,
312        );
313    }
314
315    #[test]
316    fn flat_map_respects_regen_limit() {
317        use std::sync::atomic::{AtomicBool, Ordering};
318
319        let input = (0..65536)
320            .prop_flat_map(|_| 0..65536)
321            .prop_flat_map(|_| 0..65536)
322            .prop_flat_map(|_| 0..65536)
323            .prop_flat_map(|_| 0..65536)
324            .prop_flat_map(|_| 0..65536);
325
326        // Arteficially make the first case fail and all others pass, so that
327        // the regeneration logic futilely searches for another failing
328        // example and eventually gives up. Unfortunately, the test is sort of
329        // semi-decidable; if the limit *doesn't* work, the test just runs
330        // almost forever.
331        let pass = AtomicBool::new(false);
332        let mut runner = TestRunner::new(Config {
333            max_flat_map_regens: 1000,
334            ..Config::default()
335        });
336        let case = input.new_tree(&mut runner).unwrap();
337        let _ = runner.run_one(case, |_| {
338            // Only the first run fails, all others succeed
339            prop_assert!(pass.fetch_or(true, Ordering::SeqCst));
340            Ok(())
341        });
342    }
343
344    #[test]
345    fn test_ind_flat_map_sanity() {
346        check_strategy_sanity(
347            (0..65536).prop_ind_flat_map(|a| (Just(a), (a - 5..a + 5))),
348            None,
349        );
350    }
351
352    #[test]
353    fn test_ind_flat_map2_sanity() {
354        check_strategy_sanity(
355            (0..65536).prop_ind_flat_map2(|a| a - 5..a + 5),
356            None,
357        );
358    }
359}