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