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