//- // Copyright 2017 Jason Lingle // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Support for combining strategies into tuples. //! //! There is no explicit "tuple strategy"; simply make a tuple containing the //! strategy and that tuple is itself a strategy. use crate::strategy::*; use crate::test_runner::*; /// Common `ValueTree` implementation for all tuple strategies. #[derive(Clone, Copy, Debug)] pub struct TupleValueTree { tree: T, shrinker: u32, prev_shrinker: Option, } impl TupleValueTree { /// Create a new `TupleValueTree` wrapping `inner`. /// /// It only makes sense for `inner` to be a tuple of an arity for which the /// type implements `ValueTree`. pub fn new(inner: T) -> Self { TupleValueTree { tree: inner, shrinker: 0, prev_shrinker: None, } } } macro_rules! tuple { ($($fld:tt : $typ:ident),*) => { impl<$($typ : Strategy),*> Strategy for ($($typ,)*) { type Tree = TupleValueTree<($($typ::Tree,)*)>; type Value = ($($typ::Value,)*); fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let values = ($(self.$fld.new_tree(runner)?,)*); Ok(TupleValueTree::new(values)) } } impl<$($typ : ValueTree),*> ValueTree for TupleValueTree<($($typ,)*)> { type Value = ($($typ::Value,)*); fn current(&self) -> Self::Value { ($(self.tree.$fld.current(),)*) } fn simplify(&mut self) -> bool { $( if $fld == self.shrinker { if self.tree.$fld.simplify() { self.prev_shrinker = Some(self.shrinker); return true; } else { self.shrinker += 1; } } )* false } fn complicate(&mut self) -> bool { if let Some(shrinker) = self.prev_shrinker {$( if $fld == shrinker { if self.tree.$fld.complicate() { self.shrinker = shrinker; return true; } else { self.prev_shrinker = None; return false; } } )*} false } } } } tuple!(0: A); tuple!(0: A, 1: B); tuple!(0: A, 1: B, 2: C); tuple!(0: A, 1: B, 2: C, 3: D); tuple!(0: A, 1: B, 2: C, 3: D, 4: E); tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F); tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G); tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H); tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I); tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J); tuple!( 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K ); tuple!( 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L ); #[cfg(test)] mod test { use crate::strategy::*; use super::*; #[test] fn shrinks_fully_ltr() { fn pass(a: (i32, i32)) -> bool { a.0 * a.1 <= 9 } let input = (0..32, 0..32); let mut runner = TestRunner::default(); let mut cases_tested = 0; for _ in 0..256 { // Find a failing test case let mut case = input.new_tree(&mut runner).unwrap(); if pass(case.current()) { continue; } loop { if pass(case.current()) { if !case.complicate() { break; } } else { if !case.simplify() { break; } } } let last = case.current(); assert!(!pass(last)); // Maximally shrunken assert!(pass((last.0 - 1, last.1))); assert!(pass((last.0, last.1 - 1))); cases_tested += 1; } assert!(cases_tested > 32, "Didn't find enough test cases"); } #[test] fn test_sanity() { check_strategy_sanity((0i32..100, 0i32..1000, 0i32..10000), None); } }