proptest/strategy/
filter.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};
11
12use crate::strategy::traits::*;
13use crate::test_runner::*;
14
15/// `Strategy` and `ValueTree` filter adaptor.
16///
17/// See `Strategy::prop_filter()`.
18#[must_use = "strategies do nothing unless used"]
19pub struct Filter<S, F> {
20    pub(super) source: S,
21    pub(super) whence: Reason,
22    pub(super) fun: Arc<F>,
23}
24
25impl<S, F> Filter<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 Filter<S, F> {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        f.debug_struct("Filter")
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 Filter<S, F> {
46    fn clone(&self) -> Self {
47        Filter {
48            source: self.source.clone(),
49            whence: "unused".into(),
50            fun: Arc::clone(&self.fun),
51        }
52    }
53}
54
55impl<S: Strategy, F: Fn(&S::Value) -> bool> Strategy for Filter<S, F> {
56    type Tree = Filter<S::Tree, F>;
57    type Value = S::Value;
58
59    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
60        loop {
61            let val = self.source.new_tree(runner)?;
62            if !(self.fun)(&val.current()) {
63                runner.reject_local(self.whence.clone())?;
64            } else {
65                return Ok(Filter {
66                    source: val,
67                    whence: self.whence.clone(),
68                    fun: Arc::clone(&self.fun),
69                });
70            }
71        }
72    }
73}
74
75impl<S: ValueTree, F: Fn(&S::Value) -> bool> Filter<S, F> {
76    fn ensure_acceptable(&mut self) {
77        while !(self.fun)(&self.source.current()) {
78            if !self.source.complicate() {
79                panic!(
80                    "Unable to complicate filtered strategy \
81                     back into acceptable value"
82                );
83            }
84        }
85    }
86}
87
88impl<S: ValueTree, F: Fn(&S::Value) -> bool> ValueTree for Filter<S, F> {
89    type Value = S::Value;
90
91    fn current(&self) -> S::Value {
92        self.source.current()
93    }
94
95    fn simplify(&mut self) -> bool {
96        if self.source.simplify() {
97            self.ensure_acceptable();
98            true
99        } else {
100            false
101        }
102    }
103
104    fn complicate(&mut self) -> bool {
105        if self.source.complicate() {
106            self.ensure_acceptable();
107            true
108        } else {
109            false
110        }
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117
118    #[test]
119    fn test_filter() {
120        let input = (0..256).prop_filter("%3", |&v| 0 == v % 3);
121
122        for _ in 0..256 {
123            let mut runner = TestRunner::default();
124            let mut case = input.new_tree(&mut runner).unwrap();
125
126            assert!(0 == case.current() % 3);
127
128            while case.simplify() {
129                assert!(0 == case.current() % 3);
130            }
131            assert!(0 == case.current() % 3);
132        }
133    }
134
135    #[test]
136    fn test_filter_sanity() {
137        check_strategy_sanity(
138            (0..256).prop_filter("!%5", |&v| 0 != v % 5),
139            Some(CheckStrategySanityOptions {
140                // Due to internal rejection sampling, `simplify()` can
141                // converge back to what `complicate()` would do.
142                strict_complicate_after_simplify: false,
143                ..CheckStrategySanityOptions::default()
144            }),
145        );
146    }
147}