diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/proptest/src/strategy/unions.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/proptest/src/strategy/unions.rs')
-rw-r--r-- | vendor/proptest/src/strategy/unions.rs | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/vendor/proptest/src/strategy/unions.rs b/vendor/proptest/src/strategy/unions.rs new file mode 100644 index 000000000..fb5806297 --- /dev/null +++ b/vendor/proptest/src/strategy/unions.rs @@ -0,0 +1,697 @@ +//- +// 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. + +use crate::std_facade::{fmt, Arc, Vec}; +use core::cmp::{max, min}; +use core::u32; + +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore; + +use crate::num::sample_uniform; +use crate::strategy::{lazy::LazyValueTree, traits::*}; +use crate::test_runner::*; + +/// A **relative** `weight` of a particular `Strategy` corresponding to `T` +/// coupled with `T` itself. The weight is currently given in `u32`. +pub type W<T> = (u32, T); + +/// A **relative** `weight` of a particular `Strategy` corresponding to `T` +/// coupled with `Arc<T>`. The weight is currently given in `u32`. +pub type WA<T> = (u32, Arc<T>); + +/// A `Strategy` which picks from one of several delegate `Stragegy`s. +/// +/// See `Strategy::prop_union()`. +#[derive(Clone, Debug)] +#[must_use = "strategies do nothing unless used"] +pub struct Union<T: Strategy> { + // In principle T could be any `Strategy + Clone`, but that isn't possible + // for BC reasons with the 0.9 series. + options: Vec<WA<T>>, +} + +impl<T: Strategy> Union<T> { + /// Create a strategy which selects uniformly from the given delegate + /// strategies. + /// + /// When shrinking, after maximal simplification of the chosen element, the + /// strategy will move to earlier options and continue simplification with + /// those. + /// + /// ## Panics + /// + /// Panics if `options` is empty. + pub fn new(options: impl IntoIterator<Item = T>) -> Self { + let options: Vec<WA<T>> = + options.into_iter().map(|v| (1, Arc::new(v))).collect(); + assert!(!options.is_empty()); + Self { options } + } + + pub(crate) fn try_new<E>( + it: impl Iterator<Item = Result<T, E>>, + ) -> Result<Self, E> { + let options: Vec<WA<T>> = it + .map(|r| r.map(|v| (1, Arc::new(v)))) + .collect::<Result<_, _>>()?; + + assert!(!options.is_empty()); + Ok(Self { options }) + } + + /// Create a strategy which selects from the given delegate strategies. + /// + /// Each strategy is assigned a non-zero weight which determines how + /// frequently that strategy is chosen. For example, a strategy with a + /// weight of 2 will be chosen twice as frequently as one with a weight of + /// 1\. + /// + /// ## Panics + /// + /// Panics if `options` is empty or any element has a weight of 0. + /// + /// Panics if the sum of the weights overflows a `u32`. + pub fn new_weighted(options: Vec<W<T>>) -> Self { + assert!(!options.is_empty()); + assert!( + !options.iter().any(|&(w, _)| 0 == w), + "Union option has a weight of 0" + ); + assert!( + options.iter().map(|&(w, _)| u64::from(w)).sum::<u64>() + <= u64::from(u32::MAX), + "Union weights overflow u32" + ); + let options = + options.into_iter().map(|(w, v)| (w, Arc::new(v))).collect(); + Self { options } + } + + /// Add `other` as an additional alternate strategy with weight 1. + pub fn or(mut self, other: T) -> Self { + self.options.push((1, Arc::new(other))); + self + } +} + +fn pick_weighted<I: Iterator<Item = u32>>( + runner: &mut TestRunner, + weights1: I, + weights2: I, +) -> usize { + let sum = weights1.map(u64::from).sum(); + let weighted_pick = sample_uniform(runner, 0, sum); + weights2 + .scan(0u64, |state, w| { + *state += u64::from(w); + Some(*state) + }) + .filter(|&v| v <= weighted_pick) + .count() +} + +impl<T: Strategy> Strategy for Union<T> { + type Tree = UnionValueTree<T>; + type Value = T::Value; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> { + fn extract_weight<V>(&(w, _): &WA<V>) -> u32 { + w + } + + let pick = pick_weighted( + runner, + self.options.iter().map(extract_weight::<T>), + self.options.iter().map(extract_weight::<T>), + ); + + let mut options = Vec::with_capacity(pick); + + // Delay initialization for all options less than pick. + for option in &self.options[0..pick] { + options.push(LazyValueTree::new(Arc::clone(&option.1), runner)); + } + + // Initialize the tree at pick so at least one value is available. Note + // that if generation for the value at pick fails, the entire strategy + // will fail. This seems like the right call. + options.push(LazyValueTree::new_initialized( + self.options[pick].1.new_tree(runner)?, + )); + + Ok(UnionValueTree { + options, + pick, + min_pick: 0, + prev_pick: None, + }) + } +} + +macro_rules! access_vec { + ([$($muta:tt)*] $dst:ident = $this:expr, $ix:expr, $body:block) => {{ + let $dst = &$($muta)* $this.options[$ix]; + $body + }} +} + +/// `ValueTree` corresponding to `Union`. +pub struct UnionValueTree<T: Strategy> { + options: Vec<LazyValueTree<T>>, + // This struct maintains the invariant that between function calls, + // `pick` and `prev_pick` (if Some) always point to initialized + // trees. + pick: usize, + min_pick: usize, + prev_pick: Option<usize>, +} + +macro_rules! lazy_union_value_tree_body { + ($typ:ty, $access:ident) => { + type Value = $typ; + + fn current(&self) -> Self::Value { + $access!([] opt = self, self.pick, { + opt.as_inner().unwrap_or_else(|| + panic!( + "value tree at self.pick = {} must be initialized", + self.pick, + ) + ).current() + }) + } + + fn simplify(&mut self) -> bool { + let orig_pick = self.pick; + if $access!([mut] opt = self, orig_pick, { + opt.as_inner_mut().unwrap_or_else(|| + panic!( + "value tree at self.pick = {} must be initialized", + orig_pick, + ) + ).simplify() + }) { + self.prev_pick = None; + return true; + } + + assert!( + self.pick >= self.min_pick, + "self.pick = {} should never go below self.min_pick = {}", + self.pick, + self.min_pick, + ); + if self.pick == self.min_pick { + // No more simplification to be done. + return false; + } + + // self.prev_pick is always a valid pick. + self.prev_pick = Some(self.pick); + + let mut next_pick = self.pick; + while next_pick > self.min_pick { + next_pick -= 1; + let initialized = $access!([mut] opt = self, next_pick, { + opt.maybe_init(); + opt.is_initialized() + }); + if initialized { + // next_pick was correctly initialized above. + self.pick = next_pick; + return true; + } + } + + false + } + + fn complicate(&mut self) -> bool { + if let Some(pick) = self.prev_pick { + // simplify() ensures that the previous pick was initialized. + self.pick = pick; + self.min_pick = pick; + self.prev_pick = None; + true + } else { + let pick = self.pick; + $access!([mut] opt = self, pick, { + opt.as_inner_mut().unwrap_or_else(|| + panic!( + "value tree at self.pick = {} must be initialized", + pick, + ) + ).complicate() + }) + } + } + } +} + +impl<T: Strategy> ValueTree for UnionValueTree<T> { + lazy_union_value_tree_body!(T::Value, access_vec); +} + +impl<T: Strategy> Clone for UnionValueTree<T> +where + T::Tree: Clone, +{ + fn clone(&self) -> Self { + Self { + options: self.options.clone(), + pick: self.pick, + min_pick: self.min_pick, + prev_pick: self.prev_pick, + } + } +} + +impl<T: Strategy> fmt::Debug for UnionValueTree<T> +where + T::Tree: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("UnionValueTree") + .field("options", &self.options) + .field("pick", &self.pick) + .field("min_pick", &self.min_pick) + .field("prev_pick", &self.prev_pick) + .finish() + } +} + +macro_rules! def_access_tuple { + ($b:tt $name:ident, $($n:tt)*) => { + macro_rules! $name { + ([$b($b muta:tt)*] $b dst:ident = $b this:expr, + $b ix:expr, $b body:block) => { + match $b ix { + 0 => { + let $b dst = &$b($b muta)* $b this.options.0; + $b body + }, + $( + $n => { + if let Some(ref $b($b muta)* $b dst) = + $b this.options.$n + { + $b body + } else { + panic!("TupleUnion tried to access \ + uninitialised slot {}", $n) + } + }, + )* + _ => panic!("TupleUnion tried to access out-of-range \ + slot {}", $b ix), + } + } + } + } +} + +def_access_tuple!($ access_tuple2, 1); +def_access_tuple!($ access_tuple3, 1 2); +def_access_tuple!($ access_tuple4, 1 2 3); +def_access_tuple!($ access_tuple5, 1 2 3 4); +def_access_tuple!($ access_tuple6, 1 2 3 4 5); +def_access_tuple!($ access_tuple7, 1 2 3 4 5 6); +def_access_tuple!($ access_tuple8, 1 2 3 4 5 6 7); +def_access_tuple!($ access_tuple9, 1 2 3 4 5 6 7 8); +def_access_tuple!($ access_tupleA, 1 2 3 4 5 6 7 8 9); + +/// Similar to `Union`, but internally uses a tuple to hold the strategies. +/// +/// This allows better performance than vanilla `Union` since one does not need +/// to resort to boxing and dynamic dispatch to handle heterogeneous +/// strategies. +/// +/// The difference between this and `TupleUnion` is that with this, value trees +/// for variants that aren't picked at first are generated lazily. +#[must_use = "strategies do nothing unless used"] +#[derive(Clone, Copy, Debug)] +pub struct TupleUnion<T>(T); + +impl<T> TupleUnion<T> { + /// Wrap `tuple` in a `TupleUnion`. + /// + /// The struct definition allows any `T` for `tuple`, but to be useful, it + /// must be a 2- to 10-tuple of `(u32, Arc<impl Strategy>)` pairs where all + /// strategies ultimately produce the same value. Each `u32` indicates the + /// relative weight of its corresponding strategy. + /// You may use `WA<S>` as an alias for `(u32, Arc<S>)`. + /// + /// Using this constructor directly is discouraged; prefer to use + /// `prop_oneof!` since it is generally clearer. + pub fn new(tuple: T) -> Self { + TupleUnion(tuple) + } +} + +macro_rules! tuple_union { + ($($gen:ident $ix:tt)*) => { + impl<A : Strategy, $($gen: Strategy<Value = A::Value>),*> + Strategy for TupleUnion<(WA<A>, $(WA<$gen>),*)> { + type Tree = TupleUnionValueTree< + (LazyValueTree<A>, $(Option<LazyValueTree<$gen>>),*)>; + type Value = A::Value; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> { + let weights = [((self.0).0).0, $(((self.0).$ix).0),*]; + let pick = pick_weighted(runner, weights.iter().cloned(), + weights.iter().cloned()); + + Ok(TupleUnionValueTree { + options: ( + if 0 == pick { + LazyValueTree::new_initialized( + ((self.0).0).1.new_tree(runner)?) + } else { + LazyValueTree::new( + Arc::clone(&((self.0).0).1), runner) + }, + $( + if $ix == pick { + Some(LazyValueTree::new_initialized( + ((self.0).$ix).1.new_tree(runner)?)) + } else if $ix < pick { + Some(LazyValueTree::new( + Arc::clone(&((self.0).$ix).1), runner)) + } else { + None + }),*), + pick: pick, + min_pick: 0, + prev_pick: None, + }) + } + } + } +} + +tuple_union!(B 1); +tuple_union!(B 1 C 2); +tuple_union!(B 1 C 2 D 3); +tuple_union!(B 1 C 2 D 3 E 4); +tuple_union!(B 1 C 2 D 3 E 4 F 5); +tuple_union!(B 1 C 2 D 3 E 4 F 5 G 6); +tuple_union!(B 1 C 2 D 3 E 4 F 5 G 6 H 7); +tuple_union!(B 1 C 2 D 3 E 4 F 5 G 6 H 7 I 8); +tuple_union!(B 1 C 2 D 3 E 4 F 5 G 6 H 7 I 8 J 9); + +/// `ValueTree` type produced by `TupleUnion`. +#[derive(Clone, Copy, Debug)] +pub struct TupleUnionValueTree<T> { + options: T, + pick: usize, + min_pick: usize, + prev_pick: Option<usize>, +} + +macro_rules! value_tree_tuple { + ($access:ident, $($gen:ident)*) => { + impl<A : Strategy, $($gen: Strategy<Value = A::Value>),*> ValueTree + for TupleUnionValueTree< + (LazyValueTree<A>, $(Option<LazyValueTree<$gen>>),*) + > { + lazy_union_value_tree_body!(A::Value, $access); + } + } +} + +value_tree_tuple!(access_tuple2, B); +value_tree_tuple!(access_tuple3, B C); +value_tree_tuple!(access_tuple4, B C D); +value_tree_tuple!(access_tuple5, B C D E); +value_tree_tuple!(access_tuple6, B C D E F); +value_tree_tuple!(access_tuple7, B C D E F G); +value_tree_tuple!(access_tuple8, B C D E F G H); +value_tree_tuple!(access_tuple9, B C D E F G H I); +value_tree_tuple!(access_tupleA, B C D E F G H I J); + +const WEIGHT_BASE: u32 = 0x8000_0000; + +/// Convert a floating-point weight in the range (0.0,1.0) to a pair of weights +/// that can be used with `Union` and similar. +/// +/// The first return value is the weight corresponding to `f`; the second +/// return value is the weight corresponding to `1.0 - f`. +/// +/// This call does not make any guarantees as to what range of weights it may +/// produce, except that adding the two return values will never overflow a +/// `u32`. As such, it is generally not meaningful to combine any other weights +/// with the two returned. +/// +/// ## Panics +/// +/// Panics if `f` is not a real number between 0.0 and 1.0, both exclusive. +pub fn float_to_weight(f: f64) -> (u32, u32) { + assert!(f > 0.0 && f < 1.0, "Invalid probability: {}", f); + + // Clamp to 1..WEIGHT_BASE-1 so that we never produce a weight of 0. + let pos = max( + 1, + min(WEIGHT_BASE - 1, (f * f64::from(WEIGHT_BASE)).round() as u32), + ); + let neg = WEIGHT_BASE - pos; + + (pos, neg) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::strategy::just::Just; + + // FIXME(2018-06-01): figure out a way to run this test on no_std. + // The problem is that the default seed is fixed and does not produce + // enough passed tests. We need some universal source of non-determinism + // for the seed, which is unlikely. + #[cfg(feature = "std")] + #[test] + fn test_union() { + let input = (10u32..20u32).prop_union(30u32..40u32); + // Expect that 25% of cases pass (left input happens to be < 15, and + // left is chosen as initial value). Of the 75% that fail, 50% should + // converge to 15 and 50% to 30 (the latter because the left is beneath + // the passing threshold). + let mut passed = 0; + let mut converged_low = 0; + let mut converged_high = 0; + let mut runner = TestRunner::deterministic(); + for _ in 0..256 { + let case = input.new_tree(&mut runner).unwrap(); + let result = runner.run_one(case, |v| { + prop_assert!(v < 15); + Ok(()) + }); + + match result { + Ok(true) => passed += 1, + Err(TestError::Fail(_, 15)) => converged_low += 1, + Err(TestError::Fail(_, 30)) => converged_high += 1, + e => panic!("Unexpected result: {:?}", e), + } + } + + assert!(passed >= 32 && passed <= 96, "Bad passed count: {}", passed); + assert!( + converged_low >= 32 && converged_low <= 160, + "Bad converged_low count: {}", + converged_low + ); + assert!( + converged_high >= 32 && converged_high <= 160, + "Bad converged_high count: {}", + converged_high + ); + } + + #[test] + fn test_union_weighted() { + let input = Union::new_weighted(vec![ + (1, Just(0usize)), + (2, Just(1usize)), + (1, Just(2usize)), + ]); + + let mut counts = [0, 0, 0]; + let mut runner = TestRunner::deterministic(); + for _ in 0..65536 { + counts[input.new_tree(&mut runner).unwrap().current()] += 1; + } + + println!("{:?}", counts); + assert!(counts[0] > 0); + assert!(counts[2] > 0); + assert!(counts[1] > counts[0] * 3 / 2); + assert!(counts[1] > counts[2] * 3 / 2); + } + + #[test] + fn test_union_sanity() { + check_strategy_sanity( + Union::new_weighted(vec![ + (1, 0i32..100), + (2, 200i32..300), + (1, 400i32..500), + ]), + None, + ); + } + + // FIXME(2018-06-01): See note on `test_union`. + #[cfg(feature = "std")] + #[test] + fn test_tuple_union() { + let input = TupleUnion::new(( + (1, Arc::new(10u32..20u32)), + (1, Arc::new(30u32..40u32)), + )); + // Expect that 25% of cases pass (left input happens to be < 15, and + // left is chosen as initial value). Of the 75% that fail, 50% should + // converge to 15 and 50% to 30 (the latter because the left is beneath + // the passing threshold). + let mut passed = 0; + let mut converged_low = 0; + let mut converged_high = 0; + let mut runner = TestRunner::deterministic(); + for _ in 0..256 { + let case = input.new_tree(&mut runner).unwrap(); + let result = runner.run_one(case, |v| { + prop_assert!(v < 15); + Ok(()) + }); + + match result { + Ok(true) => passed += 1, + Err(TestError::Fail(_, 15)) => converged_low += 1, + Err(TestError::Fail(_, 30)) => converged_high += 1, + e => panic!("Unexpected result: {:?}", e), + } + } + + assert!(passed >= 32 && passed <= 96, "Bad passed count: {}", passed); + assert!( + converged_low >= 32 && converged_low <= 160, + "Bad converged_low count: {}", + converged_low + ); + assert!( + converged_high >= 32 && converged_high <= 160, + "Bad converged_high count: {}", + converged_high + ); + } + + #[test] + fn test_tuple_union_weighting() { + let input = TupleUnion::new(( + (1, Arc::new(Just(0usize))), + (2, Arc::new(Just(1usize))), + (1, Arc::new(Just(2usize))), + )); + + let mut counts = [0, 0, 0]; + let mut runner = TestRunner::deterministic(); + for _ in 0..65536 { + counts[input.new_tree(&mut runner).unwrap().current()] += 1; + } + + println!("{:?}", counts); + assert!(counts[0] > 0); + assert!(counts[2] > 0); + assert!(counts[1] > counts[0] * 3 / 2); + assert!(counts[1] > counts[2] * 3 / 2); + } + + #[test] + fn test_tuple_union_all_sizes() { + let mut runner = TestRunner::deterministic(); + let r = Arc::new(1i32..10); + + macro_rules! test { + ($($part:expr),*) => {{ + let input = TupleUnion::new(( + $((1, $part.clone())),*, + (1, Arc::new(Just(0i32))) + )); + + let mut pass = false; + for _ in 0..1024 { + if 0 == input.new_tree(&mut runner).unwrap().current() { + pass = true; + break; + } + } + + assert!(pass); + }} + } + + test!(r); // 2 + test!(r, r); // 3 + test!(r, r, r); // 4 + test!(r, r, r, r); // 5 + test!(r, r, r, r, r); // 6 + test!(r, r, r, r, r, r); // 7 + test!(r, r, r, r, r, r, r); // 8 + test!(r, r, r, r, r, r, r, r); // 9 + test!(r, r, r, r, r, r, r, r, r); // 10 + } + + #[test] + fn test_tuple_union_sanity() { + check_strategy_sanity( + TupleUnion::new(( + (1, Arc::new(0i32..100i32)), + (1, Arc::new(200i32..1000i32)), + (1, Arc::new(2000i32..3000i32)), + )), + None, + ); + } + + /// Test that unions work even if local filtering causes errors. + #[test] + fn test_filter_union_sanity() { + let filter_strategy = (0u32..256).prop_filter("!%5", |&v| 0 != v % 5); + check_strategy_sanity( + Union::new(vec![filter_strategy; 8]), + Some(filter_sanity_options()), + ); + } + + /// Test that tuple unions work even if local filtering causes errors. + #[test] + fn test_filter_tuple_union_sanity() { + let filter_strategy = (0u32..256).prop_filter("!%5", |&v| 0 != v % 5); + check_strategy_sanity( + TupleUnion::new(( + (1, Arc::new(filter_strategy.clone())), + (1, Arc::new(filter_strategy.clone())), + (1, Arc::new(filter_strategy.clone())), + (1, Arc::new(filter_strategy.clone())), + )), + Some(filter_sanity_options()), + ); + } + + fn filter_sanity_options() -> CheckStrategySanityOptions { + CheckStrategySanityOptions { + // Due to internal rejection sampling, `simplify()` can + // converge back to what `complicate()` would do. + strict_complicate_after_simplify: false, + // Make failed filters return errors to test edge cases. + error_on_local_rejects: true, + ..CheckStrategySanityOptions::default() + } + } +} |