diff options
Diffstat (limited to 'vendor/proptest/src/result.rs')
-rw-r--r-- | vendor/proptest/src/result.rs | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/vendor/proptest/src/result.rs b/vendor/proptest/src/result.rs new file mode 100644 index 000000000..bdb163b35 --- /dev/null +++ b/vendor/proptest/src/result.rs @@ -0,0 +1,326 @@ +//- +// Copyright 2017 Jason Lingle +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Strategies for combining delegate strategies into `std::Result`s. +//! +//! That is, the strategies here are for producing `Ok` _and_ `Err` cases. To +//! simply adapt a strategy producing `T` into `Result<T, something>` which is +//! always `Ok`, you can do something like `base_strategy.prop_map(Ok)` to +//! simply wrap the generated values. +//! +//! Note that there are two nearly identical APIs for doing this, termed "maybe +//! ok" and "maybe err". The difference between the two is in how they shrink; +//! "maybe ok" treats `Ok` as the special case and shrinks to `Err`; +//! conversely, "maybe err" treats `Err` as the special case and shrinks to +//! `Ok`. Which to use largely depends on the code being tested; if the code +//! typically handles errors by immediately bailing out and doing nothing else, +//! "maybe ok" is likely more suitable, as shrinking will cause the code to +//! take simpler paths. On the other hand, functions that need to make a +//! complicated or fragile "back out" process on error are better tested with +//! "maybe err" since the success case results in an easier to understand code +//! path. + +#![cfg_attr(feature = "cargo-clippy", allow(expl_impl_clone_on_copy))] + +use core::fmt; +use core::marker::PhantomData; + +use crate::std_facade::Arc; +use crate::strategy::*; +use crate::test_runner::*; + +// Re-export the type for easier usage. +pub use crate::option::{prob, Probability}; + +struct WrapOk<T, E>(PhantomData<T>, PhantomData<E>); +impl<T, E> Clone for WrapOk<T, E> { + fn clone(&self) -> Self { + *self + } +} +impl<T, E> Copy for WrapOk<T, E> {} +impl<T, E> fmt::Debug for WrapOk<T, E> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "WrapOk") + } +} +impl<T: fmt::Debug, E: fmt::Debug> statics::MapFn<T> for WrapOk<T, E> { + type Output = Result<T, E>; + fn apply(&self, t: T) -> Result<T, E> { + Ok(t) + } +} +struct WrapErr<T, E>(PhantomData<T>, PhantomData<E>); +impl<T, E> Clone for WrapErr<T, E> { + fn clone(&self) -> Self { + *self + } +} +impl<T, E> Copy for WrapErr<T, E> {} +impl<T, E> fmt::Debug for WrapErr<T, E> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "WrapErr") + } +} +impl<T: fmt::Debug, E: fmt::Debug> statics::MapFn<E> for WrapErr<T, E> { + type Output = Result<T, E>; + fn apply(&self, e: E) -> Result<T, E> { + Err(e) + } +} + +type MapErr<T, E> = + statics::Map<E, WrapErr<<T as Strategy>::Value, <E as Strategy>::Value>>; +type MapOk<T, E> = + statics::Map<T, WrapOk<<T as Strategy>::Value, <E as Strategy>::Value>>; + +opaque_strategy_wrapper! { + /// Strategy which generates `Result`s using `Ok` and `Err` values from two + /// delegate strategies. + /// + /// Shrinks to `Err`. + #[derive(Clone)] + pub struct MaybeOk[<T, E>][where T : Strategy, E : Strategy] + (TupleUnion<(WA<MapErr<T, E>>, WA<MapOk<T, E>>)>) + -> MaybeOkValueTree<T, E>; + /// `ValueTree` type corresponding to `MaybeOk`. + pub struct MaybeOkValueTree[<T, E>][where T : Strategy, E : Strategy] + (TupleUnionValueTree<( + LazyValueTree<statics::Map<E, WrapErr<T::Value, E::Value>>>, + Option<LazyValueTree<statics::Map<T, WrapOk<T::Value, E::Value>>>>, + )>) + -> Result<T::Value, E::Value>; +} + +opaque_strategy_wrapper! { + /// Strategy which generates `Result`s using `Ok` and `Err` values from two + /// delegate strategies. + /// + /// Shrinks to `Ok`. + #[derive(Clone)] + pub struct MaybeErr[<T, E>][where T : Strategy, E : Strategy] + (TupleUnion<(WA<MapOk<T, E>>, WA<MapErr<T, E>>)>) + -> MaybeErrValueTree<T, E>; + /// `ValueTree` type corresponding to `MaybeErr`. + pub struct MaybeErrValueTree[<T, E>][where T : Strategy, E : Strategy] + (TupleUnionValueTree<( + LazyValueTree<statics::Map<T, WrapOk<T::Value, E::Value>>>, + Option<LazyValueTree<statics::Map<E, WrapErr<T::Value, E::Value>>>>, + )>) + -> Result<T::Value, E::Value>; +} + +// These need to exist for the same reason as the one on `OptionStrategy` +impl<T: Strategy + fmt::Debug, E: Strategy + fmt::Debug> fmt::Debug + for MaybeOk<T, E> +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MaybeOk({:?})", self.0) + } +} +impl<T: Strategy + fmt::Debug, E: Strategy + fmt::Debug> fmt::Debug + for MaybeErr<T, E> +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MaybeErr({:?})", self.0) + } +} + +impl<T: Strategy, E: Strategy> Clone for MaybeOkValueTree<T, E> +where + T::Tree: Clone, + E::Tree: Clone, +{ + fn clone(&self) -> Self { + MaybeOkValueTree(self.0.clone()) + } +} + +impl<T: Strategy, E: Strategy> fmt::Debug for MaybeOkValueTree<T, E> +where + T::Tree: fmt::Debug, + E::Tree: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MaybeOkValueTree({:?})", self.0) + } +} + +impl<T: Strategy, E: Strategy> Clone for MaybeErrValueTree<T, E> +where + T::Tree: Clone, + E::Tree: Clone, +{ + fn clone(&self) -> Self { + MaybeErrValueTree(self.0.clone()) + } +} + +impl<T: Strategy, E: Strategy> fmt::Debug for MaybeErrValueTree<T, E> +where + T::Tree: fmt::Debug, + E::Tree: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MaybeErrValueTree({:?})", self.0) + } +} + +/// Create a strategy for `Result`s where `Ok` values are taken from `t` and +/// `Err` values are taken from `e`. +/// +/// `Ok` and `Err` are chosen with equal probability. +/// +/// Generated values shrink to `Err`. +pub fn maybe_ok<T: Strategy, E: Strategy>(t: T, e: E) -> MaybeOk<T, E> { + maybe_ok_weighted(0.5, t, e) +} + +/// Create a strategy for `Result`s where `Ok` values are taken from `t` and +/// `Err` values are taken from `e`. +/// +/// `probability_of_ok` is the probability (between 0.0 and 1.0, exclusive) +/// that `Ok` is initially chosen. +/// +/// Generated values shrink to `Err`. +pub fn maybe_ok_weighted<T: Strategy, E: Strategy>( + probability_of_ok: impl Into<Probability>, + t: T, + e: E, +) -> MaybeOk<T, E> { + let prob = probability_of_ok.into().into(); + let (ok_weight, err_weight) = float_to_weight(prob); + + MaybeOk(TupleUnion::new(( + ( + err_weight, + Arc::new(statics::Map::new(e, WrapErr(PhantomData, PhantomData))), + ), + ( + ok_weight, + Arc::new(statics::Map::new(t, WrapOk(PhantomData, PhantomData))), + ), + ))) +} + +/// Create a strategy for `Result`s where `Ok` values are taken from `t` and +/// `Err` values are taken from `e`. +/// +/// `Ok` and `Err` are chosen with equal probability. +/// +/// Generated values shrink to `Ok`. +pub fn maybe_err<T: Strategy, E: Strategy>(t: T, e: E) -> MaybeErr<T, E> { + maybe_err_weighted(0.5, t, e) +} + +/// Create a strategy for `Result`s where `Ok` values are taken from `t` and +/// `Err` values are taken from `e`. +/// +/// `probability_of_ok` is the probability (between 0.0 and 1.0, exclusive) +/// that `Err` is initially chosen. +/// +/// Generated values shrink to `Ok`. +pub fn maybe_err_weighted<T: Strategy, E: Strategy>( + probability_of_err: impl Into<Probability>, + t: T, + e: E, +) -> MaybeErr<T, E> { + let prob = probability_of_err.into().into(); + let (err_weight, ok_weight) = float_to_weight(prob); + + MaybeErr(TupleUnion::new(( + ( + ok_weight, + Arc::new(statics::Map::new(t, WrapOk(PhantomData, PhantomData))), + ), + ( + err_weight, + Arc::new(statics::Map::new(e, WrapErr(PhantomData, PhantomData))), + ), + ))) +} + +#[cfg(test)] +mod test { + use super::*; + + fn count_ok_of_1000(s: impl Strategy<Value = Result<(), ()>>) -> u32 { + let mut runner = TestRunner::deterministic(); + let mut count = 0; + for _ in 0..1000 { + count += s.new_tree(&mut runner).unwrap().current().is_ok() as u32; + } + + count + } + + #[test] + fn probability_defaults_to_0p5() { + let count = count_ok_of_1000(maybe_err(Just(()), Just(()))); + assert!(count > 400 && count < 600); + let count = count_ok_of_1000(maybe_ok(Just(()), Just(()))); + assert!(count > 400 && count < 600); + } + + #[test] + fn probability_handled_correctly() { + let count = + count_ok_of_1000(maybe_err_weighted(0.1, Just(()), Just(()))); + assert!(count > 800 && count < 950); + + let count = + count_ok_of_1000(maybe_err_weighted(0.9, Just(()), Just(()))); + assert!(count > 50 && count < 150); + + let count = + count_ok_of_1000(maybe_ok_weighted(0.9, Just(()), Just(()))); + assert!(count > 800 && count < 950); + + let count = + count_ok_of_1000(maybe_ok_weighted(0.1, Just(()), Just(()))); + assert!(count > 50 && count < 150); + } + + #[test] + fn shrink_to_correct_case() { + let mut runner = TestRunner::default(); + { + let input = maybe_err(Just(()), Just(())); + for _ in 0..64 { + let mut val = input.new_tree(&mut runner).unwrap(); + if val.current().is_ok() { + assert!(!val.simplify()); + assert!(val.current().is_ok()); + } else { + assert!(val.simplify()); + assert!(val.current().is_ok()); + } + } + } + { + let input = maybe_ok(Just(()), Just(())); + for _ in 0..64 { + let mut val = input.new_tree(&mut runner).unwrap(); + if val.current().is_err() { + assert!(!val.simplify()); + assert!(val.current().is_err()); + } else { + assert!(val.simplify()); + assert!(val.current().is_err()); + } + } + } + } + + #[test] + fn test_sanity() { + check_strategy_sanity(maybe_ok(0i32..100i32, 0i32..100i32), None); + check_strategy_sanity(maybe_err(0i32..100i32, 0i32..100i32), None); + } +} |