use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::hash::{BuildHasher, Hash};
use std::ops::{Coroutine, CoroutineState};
use std::panic::{RefUnwindSafe, catch_unwind};
use std::pin::pin;
use crate::TestResult;
pub trait ShrinkCoro<Y>: Coroutine<TestResult, Yield = Y, Return = Y> {}
impl<X, Y> ShrinkCoro<Y> for X where X: Coroutine<TestResult, Yield = Y, Return = Y> {}
pub fn shrink_bool(high: bool) -> impl ShrinkCoro<bool> {
#[coroutine]
move |_| {
if high == false {
return false;
}
match (yield false) {
TestResult::Fail => false,
TestResult::Pass | TestResult::Reject => true,
}
}
}
pub fn shrink_char_binary_search(high_char: char) -> impl ShrinkCoro<char> {
#[coroutine]
move |_| {
let mut high: u32 = high_char as u32;
if high >= 0xE000 {
let mut low: u32 = 0xE000;
match (yield char::from_u32(low).unwrap()) {
TestResult::Fail => {
high = low;
}
TestResult::Pass | TestResult::Reject => {}
}
while low + 1 < high {
let mid = low + ((high - low) / 2);
match (yield char::from_u32(mid).unwrap()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
}
if high == 0xE000 {
let mut high = 0xD7FF;
let mut low: u32 = 0;
match (yield char::from_u32(low).unwrap()) {
TestResult::Fail => {
high = low;
}
TestResult::Pass | TestResult::Reject => {}
}
while low + 1 < high {
let mid = low + ((high - low) / 2);
match (yield char::from_u32(mid).unwrap()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
if high < 0xD7FF {
return char::from_u32(high).unwrap();
}
}
char::from_u32(high).unwrap()
}
}
macro_rules! shrink_primitive_type_binary_search {
($fn:ident, $ty:ty) => {
pub fn $fn(mut high: $ty) -> impl ShrinkCoro<$ty> {
#[coroutine]
move |_| {
let mut low = <$ty>::MIN;
match (yield low) {
TestResult::Fail => {
high = low;
}
TestResult::Pass | TestResult::Reject => {}
}
while low + 1 as $ty < high {
let mid = low + ((high - low) / 2 as $ty);
match (yield mid) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
};
}
shrink_primitive_type_binary_search!(shrink_u8_binary_search, u8);
shrink_primitive_type_binary_search!(shrink_i8_binary_search, i8);
shrink_primitive_type_binary_search!(shrink_u16_binary_search, u16);
shrink_primitive_type_binary_search!(shrink_i16_binary_search, i16);
shrink_primitive_type_binary_search!(shrink_u32_binary_search, u32);
shrink_primitive_type_binary_search!(shrink_i32_binary_search, i32);
shrink_primitive_type_binary_search!(shrink_f32, f32);
shrink_primitive_type_binary_search!(shrink_u64_binary_search, u64);
shrink_primitive_type_binary_search!(shrink_i64_binary_search, i64);
shrink_primitive_type_binary_search!(shrink_f64, f64);
shrink_primitive_type_binary_search!(shrink_u128_binary_search, u128);
shrink_primitive_type_binary_search!(shrink_i128_binary_search, i128);
shrink_primitive_type_binary_search!(shrink_usize_binary_search, usize);
shrink_primitive_type_binary_search!(shrink_isize_binary_search, isize);
pub fn shrink_usize_exhaustive(falsifier: usize) -> impl ShrinkCoro<usize> {
#[coroutine]
move |_| 'search: {
for value in 0..=falsifier {
match (yield value) {
TestResult::Fail => break 'search value,
TestResult::Pass | TestResult::Reject => continue,
}
}
falsifier
}
}
pub fn shrink_vec_len_binary_search<T: Clone>(mut high: Vec<T>) -> impl ShrinkCoro<Vec<T>> {
#[coroutine]
move |_| {
let mut low = vec![];
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid = high[0..mid_len].to_vec();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_hashset_len_binary_search<T: Eq + Hash + Clone, S: BuildHasher + Clone>(
mut high: HashSet<T, S>,
) -> impl ShrinkCoro<HashSet<T, S>> {
#[coroutine]
move |_| {
let mut low = HashSet::with_hasher(high.hasher().clone());
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid = {
let mut mid = HashSet::with_hasher(high.hasher().clone());
for elem in high.iter().take(mid_len) {
mid.insert(elem.clone());
}
mid
};
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_btreeset_len_binary_search<T: Ord + Clone>(
mut high: BTreeSet<T>,
) -> impl ShrinkCoro<BTreeSet<T>> {
#[coroutine]
move |_| {
let mut low = BTreeSet::new();
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid: BTreeSet<T> = high.iter().take(mid_len).cloned().collect();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_vec_deque_len_binary_search<T: Ord + Clone>(
mut high: VecDeque<T>,
) -> impl ShrinkCoro<VecDeque<T>> {
#[coroutine]
move |_| {
let mut low = VecDeque::new();
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid: VecDeque<T> = high.iter().take(mid_len).cloned().collect();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_binary_heap_len_binary_search<T: Ord + Clone>(
mut high: BinaryHeap<T>,
) -> impl ShrinkCoro<BinaryHeap<T>> {
#[coroutine]
move |_| {
let mut low = BinaryHeap::new();
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid: BinaryHeap<T> = high.iter().take(mid_len).cloned().collect();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_linked_list_len_binary_search<T: Ord + Clone>(
mut high: LinkedList<T>,
) -> impl ShrinkCoro<LinkedList<T>> {
#[coroutine]
move |_| {
let mut low = LinkedList::new();
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid: LinkedList<T> = high.iter().take(mid_len).cloned().collect();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_hashmap_len_binary_search<K: Eq + Hash + Clone, V: Clone, S: BuildHasher + Clone>(
mut high: HashMap<K, V, S>,
) -> impl ShrinkCoro<HashMap<K, V, S>> {
#[coroutine]
move |_| {
let mut low = HashMap::with_hasher(high.hasher().clone());
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid = {
let mut mid = HashMap::with_hasher(high.hasher().clone());
for (k, v) in high.iter().take(mid_len) {
mid.insert(k.clone(), v.clone());
}
mid
};
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_btreemap_len_binary_search<K: Ord + Clone, V: Clone>(
mut high: BTreeMap<K, V>,
) -> impl ShrinkCoro<BTreeMap<K, V>> {
#[coroutine]
move |_| {
let mut low = BTreeMap::new();
match (yield low.clone()) {
TestResult::Fail => {
high = low.clone();
}
TestResult::Pass | TestResult::Reject => {}
}
while high.len() > low.len() + 1 {
let mid_len = low.len() + ((high.len() - low.len()) / 2);
let mid: BTreeMap<K, V> = high
.iter()
.take(mid_len)
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
match (yield mid.clone()) {
TestResult::Fail => {
high = mid;
}
TestResult::Pass | TestResult::Reject => {
low = mid;
}
}
}
high
}
}
pub fn shrink_option<T: Clone>(
mut shrink_t: impl ShrinkCoro<T> + Unpin,
) -> impl ShrinkCoro<Option<T>> {
#[coroutine]
move |_| {
let test_result = yield None;
match test_result {
TestResult::Fail => {
return None;
}
TestResult::Pass | TestResult::Reject => {}
}
let mut test_result = TestResult::Fail;
loop {
let value = match pin!(&mut shrink_t).resume(test_result) {
CoroutineState::Yielded(value) => value,
CoroutineState::Complete(value) => return Some(value),
};
test_result = yield Some(value.clone());
}
}
}
pub fn shrink_with_rejections<T: Clone + RefUnwindSafe>(
test: impl Fn(T) -> TestResult + RefUnwindSafe,
mut shrink_t: impl ShrinkCoro<T> + Unpin,
) -> T {
let mut result = TestResult::Fail;
loop {
let value = match pin!(&mut shrink_t).resume(result) {
CoroutineState::Yielded(value) => value,
CoroutineState::Complete(falsifier) => return falsifier,
};
result = match catch_unwind(|| test(value.clone())) {
Ok(r) => r,
Err(..) => TestResult::Fail,
}
}
}
pub fn shrink<T: Clone + RefUnwindSafe>(
test: impl Fn(T) -> bool + RefUnwindSafe,
shrink_t: impl ShrinkCoro<T> + Unpin,
) -> T {
shrink_with_rejections(|value| TestResult::from(test(value)), shrink_t)
}
#[cfg(test)]
mod tests {
use rand::RngExt;
use crate::{make_rng_with_seed, make_test_rng};
use crate::sip::HasherBuilder;
use super::*;
#[test]
fn test_shrink_usize_binary_search() {
let shrinker = shrink_usize_binary_search(123);
let smallest_falsifier = shrink(|v| v < 10, shrinker);
assert_eq!(smallest_falsifier, 10);
}
#[test]
fn test_shrink_usize_exhaustive() {
let shrinker = shrink_usize_exhaustive(123);
let smallest_falsifier = shrink(|v| v < 10, shrinker);
assert_eq!(smallest_falsifier, 10);
}
#[test]
fn test_shrink_vec_binary_search() {
let shrinker = shrink_vec_len_binary_search(vec![1, 2, 3]);
let smallest_falsifier = shrink(|v| !v.contains(&1), shrinker);
assert_eq!(smallest_falsifier, &[1]);
}
#[test]
fn test_shrink_btreeset_binary_search() {
let shrinker = shrink_btreeset_len_binary_search(BTreeSet::from_iter(&[1, 2, 3]));
let smallest_falsifier = shrink(|v| !v.contains(&1), shrinker);
assert_eq!(smallest_falsifier, BTreeSet::from_iter(&[1]));
}
#[test]
fn test_shrink_vec_deque_binary_search() {
let shrinker = shrink_vec_deque_len_binary_search(VecDeque::from_iter([1, 2, 3]));
let smallest_falsifier = shrink(|v| !v.contains(&1), shrinker);
assert_eq!(smallest_falsifier, VecDeque::from_iter([1]));
}
#[test]
fn test_shrink_linked_list_binary_search() {
let shrinker = shrink_linked_list_len_binary_search(LinkedList::from_iter([1, 2, 3]));
let smallest_falsifier = shrink(|v| !v.contains(&1), shrinker);
assert_eq!(smallest_falsifier, LinkedList::from_iter([1]));
}
#[test]
fn test_shrink_btreemap_binary_search() {
let shrinker =
shrink_btreemap_len_binary_search(BTreeMap::from([(1, ()), (2, ()), (3, ())]));
let smallest_falsifier = shrink(|v| !v.contains_key(&1), shrinker);
assert_eq!(smallest_falsifier, BTreeMap::from([(1, ())]));
}
#[test]
fn test_shrink_hashset_binary_search() {
let rng = make_test_rng();
let controlled_seed = {
let mut r = rng.borrow_mut();
let seed: u64 = r.random();
seed
};
let input = {
let builder = HasherBuilder::new(make_rng_with_seed(controlled_seed));
let mut input = HashSet::with_hasher(builder);
input.insert(1);
input.insert(2);
input.insert(3);
input
};
let elem = input.iter().next().unwrap().clone();
let shrinker = shrink_hashset_len_binary_search(input);
let smallest_falsifier = shrink(|v| !v.contains(&elem), shrinker);
let expected = {
let builder = HasherBuilder::new(make_rng_with_seed(controlled_seed));
let mut expected = HashSet::with_hasher(builder);
expected.insert(elem);
expected
};
assert_eq!(smallest_falsifier, expected);
}
#[test]
fn test_shrink_hashmap_binary_search() {
let rng = make_test_rng();
let controlled_seed = {
let mut r = rng.borrow_mut();
let seed: u64 = r.random();
seed
};
let input = {
let builder = HasherBuilder::new(make_rng_with_seed(controlled_seed));
let mut input = HashMap::with_hasher(builder);
input.insert(1, ());
input.insert(2, ());
input.insert(3, ());
input
};
let k = input.iter().next().unwrap().0.clone();
let shrinker = shrink_hashmap_len_binary_search(input);
let smallest_falsifier = shrink(|v| !v.contains_key(&k), shrinker);
let expected = {
let builder = HasherBuilder::new(make_rng_with_seed(controlled_seed));
let mut expected = HashMap::with_hasher(builder);
expected.insert(k, ());
expected
};
assert_eq!(smallest_falsifier, expected);
}
}