proptest/
option.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
10//! Strategies for generating `std::Option` values.
11
12#![cfg_attr(clippy, allow(expl_impl_clone_on_copy))]
13
14use core::fmt;
15use core::marker::PhantomData;
16
17use crate::std_facade::Arc;
18use crate::strategy::*;
19use crate::test_runner::*;
20
21//==============================================================================
22// Probability
23//==============================================================================
24
25/// Creates a `Probability` from some value that is convertible into it.
26///
27/// # Panics
28///
29/// Panics if the converted to probability would lie
30/// outside interval `[0.0, 1.0]`. Consult the `Into` (or `From`)
31/// implementations for more details.
32pub fn prob(from: impl Into<Probability>) -> Probability {
33    from.into()
34}
35
36impl Default for Probability {
37    /// The default probability is 0.5, or 50% chance.
38    fn default() -> Self {
39        prob(0.5)
40    }
41}
42
43impl Probability {
44    /// Creates a `Probability` from a `f64`.
45    ///
46    /// # Panics
47    ///
48    /// Panics if the probability is outside interval `[0.0, 1.0]`.
49    pub fn new(prob: f64) -> Self {
50        assert!(prob >= 0.0 && prob <= 1.0);
51        Probability(prob)
52    }
53
54    // Don't rely on these existing internally:
55
56    /// Merges self together with some other argument producing a product
57    /// type expected by some implementations of `A: Arbitrary` in
58    /// `A::Parameters`. This can be more ergonomic to work with and may
59    /// help type inference.
60    pub fn with<X>(self, and: X) -> product_type![Self, X] {
61        product_pack![self, and]
62    }
63
64    /// Merges self together with some other argument generated with a
65    /// default value producing a product type expected by some
66    /// implementations of `A: Arbitrary` in `A::Parameters`.
67    /// This can be more ergonomic to work with and may help type inference.
68    pub fn lift<X: Default>(self) -> product_type![Self, X] {
69        self.with(Default::default())
70    }
71}
72
73impl From<f64> for Probability {
74    /// Creates a `Probability` from a `f64`.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the probability is outside interval `[0.0, 1.0]`.
79    fn from(prob: f64) -> Self {
80        Probability::new(prob)
81    }
82}
83
84impl From<Probability> for f64 {
85    fn from(p: Probability) -> Self {
86        p.0
87    }
88}
89
90/// A probability in the range `[0.0, 1.0]` with a default of `0.5`.
91#[derive(Clone, Copy, PartialEq, Debug)]
92pub struct Probability(f64);
93
94//==============================================================================
95// Strategies for Option
96//==============================================================================
97
98mapfn! {
99    [] fn WrapSome[<T : fmt::Debug>](t: T) -> Option<T> {
100        Some(t)
101    }
102}
103
104#[must_use = "strategies do nothing unless used"]
105struct NoneStrategy<T>(PhantomData<T>);
106impl<T> Clone for NoneStrategy<T> {
107    fn clone(&self) -> Self {
108        *self
109    }
110}
111impl<T> Copy for NoneStrategy<T> {}
112impl<T> fmt::Debug for NoneStrategy<T> {
113    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114        write!(f, "NoneStrategy")
115    }
116}
117impl<T: fmt::Debug> Strategy for NoneStrategy<T> {
118    type Tree = Self;
119    type Value = Option<T>;
120
121    fn new_tree(&self, _: &mut TestRunner) -> NewTree<Self> {
122        Ok(*self)
123    }
124}
125impl<T: fmt::Debug> ValueTree for NoneStrategy<T> {
126    type Value = Option<T>;
127
128    fn current(&self) -> Option<T> {
129        None
130    }
131    fn simplify(&mut self) -> bool {
132        false
133    }
134    fn complicate(&mut self) -> bool {
135        false
136    }
137}
138
139opaque_strategy_wrapper! {
140    /// Strategy which generates `Option` values whose inner `Some` values are
141    /// generated by another strategy.
142    ///
143    /// Constructed by other functions in this module.
144    #[derive(Clone)]
145    pub struct OptionStrategy[<T>][where T : Strategy]
146        (TupleUnion<(WA<NoneStrategy<T::Value>>,
147                     WA<statics::Map<T, WrapSome>>)>)
148        -> OptionValueTree<T>;
149    /// `ValueTree` type corresponding to `OptionStrategy`.
150    pub struct OptionValueTree[<T>][where T : Strategy]
151        (TupleUnionValueTree<(
152            LazyValueTree<NoneStrategy<T::Value>>,
153            Option<LazyValueTree<statics::Map<T, WrapSome>>>,
154        )>)
155        -> Option<T::Value>;
156}
157
158// XXX Unclear why this is necessary; #[derive(Debug)] *should* generate
159// exactly this, but for some reason it adds a `T::Value : Debug` constraint as
160// well.
161impl<T: Strategy + fmt::Debug> fmt::Debug for OptionStrategy<T> {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        write!(f, "OptionStrategy({:?})", self.0)
164    }
165}
166
167impl<T: Strategy> Clone for OptionValueTree<T>
168where
169    T::Tree: Clone,
170{
171    fn clone(&self) -> Self {
172        OptionValueTree(self.0.clone())
173    }
174}
175
176impl<T: Strategy> fmt::Debug for OptionValueTree<T>
177where
178    T::Tree: fmt::Debug,
179{
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        write!(f, "OptionValueTree({:?})", self.0)
182    }
183}
184
185/// Return a strategy producing `Optional` values wrapping values from the
186/// given delegate strategy.
187///
188/// `Some` values shrink to `None`.
189///
190/// `Some` and `None` are each chosen with 50% probability.
191pub fn of<T: Strategy>(t: T) -> OptionStrategy<T> {
192    weighted(Probability::default(), t)
193}
194
195/// Return a strategy producing `Optional` values wrapping values from the
196/// given delegate strategy.
197///
198/// `Some` values shrink to `None`.
199///
200/// `Some` is chosen with a probability given by `probability_of_some`, which
201/// must be between 0.0 and 1.0, both exclusive.
202pub fn weighted<T: Strategy>(
203    probability_of_some: impl Into<Probability>,
204    t: T,
205) -> OptionStrategy<T> {
206    let prob = probability_of_some.into().into();
207    let (weight_some, weight_none) = float_to_weight(prob);
208
209    OptionStrategy(TupleUnion::new((
210        (weight_none, Arc::new(NoneStrategy(PhantomData))),
211        (weight_some, Arc::new(statics::Map::new(t, WrapSome))),
212    )))
213}
214
215#[cfg(test)]
216mod test {
217    use super::*;
218
219    fn count_some_of_1000(s: OptionStrategy<Just<i32>>) -> u32 {
220        let mut runner = TestRunner::deterministic();
221        let mut count = 0;
222        for _ in 0..1000 {
223            count +=
224                s.new_tree(&mut runner).unwrap().current().is_some() as u32;
225        }
226
227        count
228    }
229
230    #[test]
231    fn probability_defaults_to_0p5() {
232        let count = count_some_of_1000(of(Just(42i32)));
233        assert!(count > 450 && count < 550);
234    }
235
236    #[test]
237    fn probability_handled_correctly() {
238        let count = count_some_of_1000(weighted(0.9, Just(42i32)));
239        assert!(count > 800 && count < 950);
240
241        let count = count_some_of_1000(weighted(0.1, Just(42i32)));
242        assert!(count > 50 && count < 150);
243    }
244
245    #[test]
246    fn test_sanity() {
247        check_strategy_sanity(of(0i32..1000i32), None);
248    }
249}