proptest/
result.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 combining delegate strategies into `std::Result`s.
11//!
12//! That is, the strategies here are for producing `Ok` _and_ `Err` cases. To
13//! simply adapt a strategy producing `T` into `Result<T, something>` which is
14//! always `Ok`, you can do something like `base_strategy.prop_map(Ok)` to
15//! simply wrap the generated values.
16//!
17//! Note that there are two nearly identical APIs for doing this, termed "maybe
18//! ok" and "maybe err". The difference between the two is in how they shrink;
19//! "maybe ok" treats `Ok` as the special case and shrinks to `Err`;
20//! conversely, "maybe err" treats `Err` as the special case and shrinks to
21//! `Ok`. Which to use largely depends on the code being tested; if the code
22//! typically handles errors by immediately bailing out and doing nothing else,
23//! "maybe ok" is likely more suitable, as shrinking will cause the code to
24//! take simpler paths. On the other hand, functions that need to make a
25//! complicated or fragile "back out" process on error are better tested with
26//! "maybe err" since the success case results in an easier to understand code
27//! path.
28
29#![cfg_attr(clippy, allow(expl_impl_clone_on_copy))]
30
31use core::fmt;
32use core::marker::PhantomData;
33
34use crate::std_facade::Arc;
35use crate::strategy::*;
36use crate::test_runner::*;
37
38// Re-export the type for easier usage.
39pub use crate::option::{prob, Probability};
40
41struct WrapOk<T, E>(PhantomData<T>, PhantomData<E>);
42impl<T, E> Clone for WrapOk<T, E> {
43    fn clone(&self) -> Self {
44        *self
45    }
46}
47impl<T, E> Copy for WrapOk<T, E> {}
48impl<T, E> fmt::Debug for WrapOk<T, E> {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        write!(f, "WrapOk")
51    }
52}
53impl<T: fmt::Debug, E: fmt::Debug> statics::MapFn<T> for WrapOk<T, E> {
54    type Output = Result<T, E>;
55    fn apply(&self, t: T) -> Result<T, E> {
56        Ok(t)
57    }
58}
59struct WrapErr<T, E>(PhantomData<T>, PhantomData<E>);
60impl<T, E> Clone for WrapErr<T, E> {
61    fn clone(&self) -> Self {
62        *self
63    }
64}
65impl<T, E> Copy for WrapErr<T, E> {}
66impl<T, E> fmt::Debug for WrapErr<T, E> {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        write!(f, "WrapErr")
69    }
70}
71impl<T: fmt::Debug, E: fmt::Debug> statics::MapFn<E> for WrapErr<T, E> {
72    type Output = Result<T, E>;
73    fn apply(&self, e: E) -> Result<T, E> {
74        Err(e)
75    }
76}
77
78type MapErr<T, E> =
79    statics::Map<E, WrapErr<<T as Strategy>::Value, <E as Strategy>::Value>>;
80type MapOk<T, E> =
81    statics::Map<T, WrapOk<<T as Strategy>::Value, <E as Strategy>::Value>>;
82
83opaque_strategy_wrapper! {
84    /// Strategy which generates `Result`s using `Ok` and `Err` values from two
85    /// delegate strategies.
86    ///
87    /// Shrinks to `Err`.
88    #[derive(Clone)]
89    pub struct MaybeOk[<T, E>][where T : Strategy, E : Strategy]
90        (TupleUnion<(WA<MapErr<T, E>>, WA<MapOk<T, E>>)>)
91        -> MaybeOkValueTree<T, E>;
92    /// `ValueTree` type corresponding to `MaybeOk`.
93    pub struct MaybeOkValueTree[<T, E>][where T : Strategy, E : Strategy]
94        (TupleUnionValueTree<(
95            LazyValueTree<statics::Map<E, WrapErr<T::Value, E::Value>>>,
96            Option<LazyValueTree<statics::Map<T, WrapOk<T::Value, E::Value>>>>,
97        )>)
98        -> Result<T::Value, E::Value>;
99}
100
101opaque_strategy_wrapper! {
102    /// Strategy which generates `Result`s using `Ok` and `Err` values from two
103    /// delegate strategies.
104    ///
105    /// Shrinks to `Ok`.
106    #[derive(Clone)]
107    pub struct MaybeErr[<T, E>][where T : Strategy, E : Strategy]
108        (TupleUnion<(WA<MapOk<T, E>>, WA<MapErr<T, E>>)>)
109        -> MaybeErrValueTree<T, E>;
110    /// `ValueTree` type corresponding to `MaybeErr`.
111    pub struct MaybeErrValueTree[<T, E>][where T : Strategy, E : Strategy]
112        (TupleUnionValueTree<(
113            LazyValueTree<statics::Map<T, WrapOk<T::Value, E::Value>>>,
114            Option<LazyValueTree<statics::Map<E, WrapErr<T::Value, E::Value>>>>,
115        )>)
116        -> Result<T::Value, E::Value>;
117}
118
119// These need to exist for the same reason as the one on `OptionStrategy`
120impl<T: Strategy + fmt::Debug, E: Strategy + fmt::Debug> fmt::Debug
121    for MaybeOk<T, E>
122{
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        write!(f, "MaybeOk({:?})", self.0)
125    }
126}
127impl<T: Strategy + fmt::Debug, E: Strategy + fmt::Debug> fmt::Debug
128    for MaybeErr<T, E>
129{
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        write!(f, "MaybeErr({:?})", self.0)
132    }
133}
134
135impl<T: Strategy, E: Strategy> Clone for MaybeOkValueTree<T, E>
136where
137    T::Tree: Clone,
138    E::Tree: Clone,
139{
140    fn clone(&self) -> Self {
141        MaybeOkValueTree(self.0.clone())
142    }
143}
144
145impl<T: Strategy, E: Strategy> fmt::Debug for MaybeOkValueTree<T, E>
146where
147    T::Tree: fmt::Debug,
148    E::Tree: fmt::Debug,
149{
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        write!(f, "MaybeOkValueTree({:?})", self.0)
152    }
153}
154
155impl<T: Strategy, E: Strategy> Clone for MaybeErrValueTree<T, E>
156where
157    T::Tree: Clone,
158    E::Tree: Clone,
159{
160    fn clone(&self) -> Self {
161        MaybeErrValueTree(self.0.clone())
162    }
163}
164
165impl<T: Strategy, E: Strategy> fmt::Debug for MaybeErrValueTree<T, E>
166where
167    T::Tree: fmt::Debug,
168    E::Tree: fmt::Debug,
169{
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        write!(f, "MaybeErrValueTree({:?})", self.0)
172    }
173}
174
175/// Create a strategy for `Result`s where `Ok` values are taken from `t` and
176/// `Err` values are taken from `e`.
177///
178/// `Ok` and `Err` are chosen with equal probability.
179///
180/// Generated values shrink to `Err`.
181pub fn maybe_ok<T: Strategy, E: Strategy>(t: T, e: E) -> MaybeOk<T, E> {
182    maybe_ok_weighted(0.5, t, e)
183}
184
185/// Create a strategy for `Result`s where `Ok` values are taken from `t` and
186/// `Err` values are taken from `e`.
187///
188/// `probability_of_ok` is the probability (between 0.0 and 1.0, exclusive)
189/// that `Ok` is initially chosen.
190///
191/// Generated values shrink to `Err`.
192pub fn maybe_ok_weighted<T: Strategy, E: Strategy>(
193    probability_of_ok: impl Into<Probability>,
194    t: T,
195    e: E,
196) -> MaybeOk<T, E> {
197    let prob = probability_of_ok.into().into();
198    let (ok_weight, err_weight) = float_to_weight(prob);
199
200    MaybeOk(TupleUnion::new((
201        (
202            err_weight,
203            Arc::new(statics::Map::new(e, WrapErr(PhantomData, PhantomData))),
204        ),
205        (
206            ok_weight,
207            Arc::new(statics::Map::new(t, WrapOk(PhantomData, PhantomData))),
208        ),
209    )))
210}
211
212/// Create a strategy for `Result`s where `Ok` values are taken from `t` and
213/// `Err` values are taken from `e`.
214///
215/// `Ok` and `Err` are chosen with equal probability.
216///
217/// Generated values shrink to `Ok`.
218pub fn maybe_err<T: Strategy, E: Strategy>(t: T, e: E) -> MaybeErr<T, E> {
219    maybe_err_weighted(0.5, t, e)
220}
221
222/// Create a strategy for `Result`s where `Ok` values are taken from `t` and
223/// `Err` values are taken from `e`.
224///
225/// `probability_of_ok` is the probability (between 0.0 and 1.0, exclusive)
226/// that `Err` is initially chosen.
227///
228/// Generated values shrink to `Ok`.
229pub fn maybe_err_weighted<T: Strategy, E: Strategy>(
230    probability_of_err: impl Into<Probability>,
231    t: T,
232    e: E,
233) -> MaybeErr<T, E> {
234    let prob = probability_of_err.into().into();
235    let (err_weight, ok_weight) = float_to_weight(prob);
236
237    MaybeErr(TupleUnion::new((
238        (
239            ok_weight,
240            Arc::new(statics::Map::new(t, WrapOk(PhantomData, PhantomData))),
241        ),
242        (
243            err_weight,
244            Arc::new(statics::Map::new(e, WrapErr(PhantomData, PhantomData))),
245        ),
246    )))
247}
248
249#[cfg(test)]
250mod test {
251    use super::*;
252
253    fn count_ok_of_1000(s: impl Strategy<Value = Result<(), ()>>) -> u32 {
254        let mut runner = TestRunner::deterministic();
255        let mut count = 0;
256        for _ in 0..1000 {
257            count += s.new_tree(&mut runner).unwrap().current().is_ok() as u32;
258        }
259
260        count
261    }
262
263    #[test]
264    fn probability_defaults_to_0p5() {
265        let count = count_ok_of_1000(maybe_err(Just(()), Just(())));
266        assert!(count > 400 && count < 600);
267        let count = count_ok_of_1000(maybe_ok(Just(()), Just(())));
268        assert!(count > 400 && count < 600);
269    }
270
271    #[test]
272    fn probability_handled_correctly() {
273        let count =
274            count_ok_of_1000(maybe_err_weighted(0.1, Just(()), Just(())));
275        assert!(count > 800 && count < 950);
276
277        let count =
278            count_ok_of_1000(maybe_err_weighted(0.9, Just(()), Just(())));
279        assert!(count > 50 && count < 150);
280
281        let count =
282            count_ok_of_1000(maybe_ok_weighted(0.9, Just(()), Just(())));
283        assert!(count > 800 && count < 950);
284
285        let count =
286            count_ok_of_1000(maybe_ok_weighted(0.1, Just(()), Just(())));
287        assert!(count > 50 && count < 150);
288    }
289
290    #[test]
291    fn shrink_to_correct_case() {
292        let mut runner = TestRunner::default();
293        {
294            let input = maybe_err(Just(()), Just(()));
295            for _ in 0..64 {
296                let mut val = input.new_tree(&mut runner).unwrap();
297                if val.current().is_ok() {
298                    assert!(!val.simplify());
299                    assert!(val.current().is_ok());
300                } else {
301                    assert!(val.simplify());
302                    assert!(val.current().is_ok());
303                }
304            }
305        }
306        {
307            let input = maybe_ok(Just(()), Just(()));
308            for _ in 0..64 {
309                let mut val = input.new_tree(&mut runner).unwrap();
310                if val.current().is_err() {
311                    assert!(!val.simplify());
312                    assert!(val.current().is_err());
313                } else {
314                    assert!(val.simplify());
315                    assert!(val.current().is_err());
316                }
317            }
318        }
319    }
320
321    #[test]
322    fn test_sanity() {
323        check_strategy_sanity(maybe_ok(0i32..100i32, 0i32..100i32), None);
324        check_strategy_sanity(maybe_err(0i32..100i32, 0i32..100i32), None);
325    }
326}