proptest/
array.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//! Support for strategies producing fixed-length arrays.
11//!
12//! An array of strategies (but only length 1 to 32 for now) is itself a
13//! strategy which generates arrays of that size drawing elements from the
14//! corresponding input strategies.
15//!
16//! See also [`UniformArrayStrategy`](struct.UniformArrayStrategy.html) for
17//! easily making a strategy for an array drawn from one strategy.
18//!
19//! General implementations are available for sizes 1 through 32.
20
21use core::marker::PhantomData;
22
23use crate::strategy::*;
24use crate::test_runner::*;
25
26/// A `Strategy` which generates fixed-size arrays containing values drawn from
27/// an inner strategy.
28///
29/// `T` must be an array type of length 1 to 32 whose values are produced by
30/// strategy `S`. Instances of this type are normally created by the various
31/// `uniformXX` functions in this module.
32///
33/// This is mainly useful when the inner strategy is not `Copy`, precluding
34/// expressing the strategy as `[myStrategy; 32]`, for example.
35///
36/// ## Example
37///
38/// ```
39/// use proptest::prelude::*;
40///
41/// proptest! {
42///   #[test]
43///   fn test_something(a in prop::array::uniform32(1u32..)) {
44///     let unexpected = [0u32;32];
45///     // `a` is also a [u32;32], so we can compare them directly
46///     assert_ne!(unexpected, a);
47///   }
48/// }
49/// # fn main() { }
50/// ```
51#[must_use = "strategies do nothing unless used"]
52#[derive(Clone, Copy, Debug)]
53pub struct UniformArrayStrategy<S, T> {
54    strategy: S,
55    _marker: PhantomData<T>,
56}
57
58impl<S, T> UniformArrayStrategy<S, T> {
59    /// Directly create a `UniformArrayStrategy`.
60    ///
61    /// This is only intended for advanced use, since the only way to specify
62    /// the array size is with the turbofish operator and explicitly naming the
63    /// type of the values in the array and the strategy itself.
64    ///
65    /// Prefer the `uniformXX` functions at module-level unless something
66    /// precludes their use.
67    pub fn new(strategy: S) -> Self {
68        UniformArrayStrategy {
69            strategy,
70            _marker: PhantomData,
71        }
72    }
73}
74
75/// A `ValueTree` operating over a fixed-size array.
76#[derive(Clone, Copy, Debug)]
77pub struct ArrayValueTree<T> {
78    tree: T,
79    shrinker: usize,
80    last_shrinker: Option<usize>,
81}
82
83/// Create a strategy to generate fixed-length arrays.
84///
85/// All values within the new strategy are generated using the given
86/// strategy.
87///
88/// See [`UniformArrayStrategy`](struct.UniformArrayStrategy.html) for
89/// example usage.
90pub fn uniform<S: Strategy, const N: usize>(
91    strategy: S,
92) -> UniformArrayStrategy<S, [S::Value; N]> {
93    UniformArrayStrategy {
94        strategy,
95        _marker: PhantomData,
96    }
97}
98
99macro_rules! small_array {
100    ($n:tt $uni:ident) => {
101        /// Create a strategy to generate fixed-length arrays.
102        ///
103        /// All values within the new strategy are generated using the given
104        /// strategy. The length of the array corresponds to the suffix of the
105        /// name of this function.
106        ///
107        /// See [`UniformArrayStrategy`](struct.UniformArrayStrategy.html) for
108        /// example usage.
109        pub fn $uni<S: Strategy>(
110            strategy: S,
111        ) -> UniformArrayStrategy<S, [S::Value; $n]> {
112            UniformArrayStrategy {
113                strategy,
114                _marker: PhantomData,
115            }
116        }
117    };
118}
119
120impl<S: Strategy, const N: usize> Strategy for [S; N] {
121    type Tree = ArrayValueTree<[S::Tree; N]>;
122    type Value = [S::Value; N];
123
124    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
125        Ok(ArrayValueTree {
126            tree: unarray::build_array_result(|i| self[i].new_tree(runner))?,
127            shrinker: 0,
128            last_shrinker: None,
129        })
130    }
131}
132impl<S: Strategy, const N: usize> Strategy
133    for UniformArrayStrategy<S, [S::Value; N]>
134{
135    type Tree = ArrayValueTree<[S::Tree; N]>;
136    type Value = [S::Value; N];
137
138    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
139        Ok(ArrayValueTree {
140            tree: unarray::build_array_result(|_| {
141                self.strategy.new_tree(runner)
142            })?,
143            shrinker: 0,
144            last_shrinker: None,
145        })
146    }
147}
148impl<T: ValueTree, const N: usize> ValueTree for ArrayValueTree<[T; N]> {
149    type Value = [T::Value; N];
150
151    fn current(&self) -> [T::Value; N] {
152        core::array::from_fn(|i| self.tree[i].current())
153    }
154
155    fn simplify(&mut self) -> bool {
156        while self.shrinker < N {
157            if self.tree[self.shrinker].simplify() {
158                self.last_shrinker = Some(self.shrinker);
159                return true;
160            } else {
161                self.shrinker += 1;
162            }
163        }
164        false
165    }
166
167    fn complicate(&mut self) -> bool {
168        if let Some(shrinker) = self.last_shrinker {
169            self.shrinker = shrinker;
170            if self.tree[shrinker].complicate() {
171                true
172            } else {
173                self.last_shrinker = None;
174                false
175            }
176        } else {
177            false
178        }
179    }
180}
181
182small_array!(1 uniform1);
183small_array!(2 uniform2);
184small_array!(3 uniform3);
185small_array!(4 uniform4);
186small_array!(5 uniform5);
187small_array!(6 uniform6);
188small_array!(7 uniform7);
189small_array!(8 uniform8);
190small_array!(9 uniform9);
191small_array!(10 uniform10);
192small_array!(11 uniform11);
193small_array!(12 uniform12);
194small_array!(13 uniform13);
195small_array!(14 uniform14);
196small_array!(15 uniform15);
197small_array!(16 uniform16);
198small_array!(17 uniform17);
199small_array!(18 uniform18);
200small_array!(19 uniform19);
201small_array!(20 uniform20);
202small_array!(21 uniform21);
203small_array!(22 uniform22);
204small_array!(23 uniform23);
205small_array!(24 uniform24);
206small_array!(25 uniform25);
207small_array!(26 uniform26);
208small_array!(27 uniform27);
209small_array!(28 uniform28);
210small_array!(29 uniform29);
211small_array!(30 uniform30);
212small_array!(31 uniform31);
213small_array!(32 uniform32);
214
215#[cfg(test)]
216mod test {
217    use super::*;
218
219    #[test]
220    fn shrinks_fully_ltr() {
221        fn pass(a: [i32; 2]) -> bool {
222            a[0] * a[1] <= 9
223        }
224
225        let input = [0..32, 0..32];
226        let mut runner = TestRunner::deterministic();
227
228        let mut cases_tested = 0;
229        for _ in 0..256 {
230            // Find a failing test case
231            let mut case = input.new_tree(&mut runner).unwrap();
232            if pass(case.current()) {
233                continue;
234            }
235
236            loop {
237                if pass(case.current()) {
238                    if !case.complicate() {
239                        break;
240                    }
241                } else {
242                    if !case.simplify() {
243                        break;
244                    }
245                }
246            }
247
248            let last = case.current();
249            assert!(!pass(last));
250            // Maximally shrunken
251            assert!(pass([last[0] - 1, last[1]]));
252            assert!(pass([last[0], last[1] - 1]));
253
254            cases_tested += 1;
255        }
256
257        assert!(cases_tested > 32, "Didn't find enough test cases");
258    }
259
260    #[test]
261    fn test_sanity() {
262        check_strategy_sanity([(0i32..1000), (1i32..1000)], None);
263    }
264}