proptest/strategy/
filter_map.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, Cell};
11
12use crate::strategy::traits::*;
13use crate::test_runner::*;
14
15/// `Strategy` and `ValueTree` filter_map adaptor.
16///
17/// See `Strategy::prop_filter_map()`.
18#[must_use = "strategies do nothing unless used"]
19pub struct FilterMap<S, F> {
20    pub(super) source: S,
21    pub(super) whence: Reason,
22    pub(super) fun: Arc<F>,
23}
24
25impl<S, F> FilterMap<S, F> {
26    pub(super) fn new(source: S, whence: Reason, fun: F) -> Self {
27        Self {
28            source,
29            whence,
30            fun: Arc::new(fun),
31        }
32    }
33}
34
35impl<S: fmt::Debug, F> fmt::Debug for FilterMap<S, F> {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        f.debug_struct("FilterMap")
38            .field("source", &self.source)
39            .field("whence", &self.whence)
40            .field("fun", &"<function>")
41            .finish()
42    }
43}
44
45impl<S: Clone, F> Clone for FilterMap<S, F> {
46    fn clone(&self) -> Self {
47        Self {
48            source: self.source.clone(),
49            whence: self.whence.clone(),
50            fun: Arc::clone(&self.fun),
51        }
52    }
53}
54
55impl<S: Strategy, F: Fn(S::Value) -> Option<O>, O: fmt::Debug> Strategy
56    for FilterMap<S, F>
57{
58    type Tree = FilterMapValueTree<S::Tree, F, O>;
59    type Value = O;
60
61    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
62        loop {
63            let val = self.source.new_tree(runner)?;
64            if let Some(current) = (self.fun)(val.current()) {
65                return Ok(FilterMapValueTree::new(val, &self.fun, current));
66            } else {
67                runner.reject_local(self.whence.clone())?;
68            }
69        }
70    }
71}
72
73/// `ValueTree` corresponding to `FilterMap`.
74pub struct FilterMapValueTree<V, F, O> {
75    source: V,
76    current: Cell<Option<O>>,
77    fun: Arc<F>,
78}
79
80impl<V: Clone + ValueTree, F: Fn(V::Value) -> Option<O>, O> Clone
81    for FilterMapValueTree<V, F, O>
82{
83    fn clone(&self) -> Self {
84        Self::new(self.source.clone(), &self.fun, self.fresh_current())
85    }
86}
87
88impl<V: fmt::Debug, F, O> fmt::Debug for FilterMapValueTree<V, F, O> {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        f.debug_struct("FilterMapValueTree")
91            .field("source", &self.source)
92            .field("current", &"<current>")
93            .field("fun", &"<function>")
94            .finish()
95    }
96}
97
98impl<V: ValueTree, F: Fn(V::Value) -> Option<O>, O>
99    FilterMapValueTree<V, F, O>
100{
101    fn new(source: V, fun: &Arc<F>, current: O) -> Self {
102        Self {
103            source,
104            current: Cell::new(Some(current)),
105            fun: Arc::clone(fun),
106        }
107    }
108
109    fn fresh_current(&self) -> O {
110        (self.fun)(self.source.current())
111            .expect("internal logic error; this is a bug!")
112    }
113
114    fn ensure_acceptable(&mut self) {
115        loop {
116            if let Some(current) = (self.fun)(self.source.current()) {
117                // Found an acceptable element!
118                self.current = Cell::new(Some(current));
119                break;
120            } else if !self.source.complicate() {
121                panic!(
122                    "Unable to complicate filtered strategy \
123                     back into acceptable value"
124                );
125            }
126        }
127    }
128}
129
130impl<V: ValueTree, F: Fn(V::Value) -> Option<O>, O: fmt::Debug> ValueTree
131    for FilterMapValueTree<V, F, O>
132{
133    type Value = O;
134
135    fn current(&self) -> O {
136        // Optimization: we avoid the else branch in most success cases
137        // thereby avoiding to call the closure and the source tree.
138        if let Some(current) = self.current.replace(None) {
139            current
140        } else {
141            self.fresh_current()
142        }
143    }
144
145    fn simplify(&mut self) -> bool {
146        if self.source.simplify() {
147            self.ensure_acceptable();
148            true
149        } else {
150            false
151        }
152    }
153
154    fn complicate(&mut self) -> bool {
155        if self.source.complicate() {
156            self.ensure_acceptable();
157            true
158        } else {
159            false
160        }
161    }
162}
163
164#[cfg(test)]
165mod test {
166    use super::*;
167
168    #[test]
169    fn test_filter_map() {
170        let input = (0..256).prop_filter_map("%3 + 1", |v| {
171            if 0 == v % 3 {
172                Some(v + 1)
173            } else {
174                None
175            }
176        });
177
178        for _ in 0..256 {
179            let mut runner = TestRunner::default();
180            let mut case = input.new_tree(&mut runner).unwrap();
181
182            assert_eq!(0, (case.current() - 1) % 3);
183
184            while case.simplify() {
185                assert_eq!(0, (case.current() - 1) % 3);
186            }
187            assert_eq!(0, (case.current() - 1) % 3);
188        }
189    }
190
191    #[test]
192    fn test_filter_map_sanity() {
193        check_strategy_sanity(
194            (0..256).prop_filter_map("!%5 * 2", |v| {
195                if 0 != v % 5 {
196                    Some(v * 2)
197                } else {
198                    None
199                }
200            }),
201            Some(CheckStrategySanityOptions {
202                // Due to internal rejection sampling, `simplify()` can
203                // converge back to what `complicate()` would do.
204                strict_complicate_after_simplify: false,
205                ..CheckStrategySanityOptions::default()
206            }),
207        );
208    }
209}