mod deletion;
mod floats;
mod index_passes;
mod integers;
mod mutation;
mod sequence;
use std::collections::HashMap;
use crate::native::core::{ChoiceNode, ChoiceValue, MAX_SHRINK_ITERATIONS, NodeSortKey, sort_key};
pub enum ShrinkRun<'a> {
Full(&'a [ChoiceNode]),
Probe {
prefix: &'a [ChoiceValue],
seed: u64,
max_size: usize,
},
}
pub type TestFn<'a> = dyn FnMut(ShrinkRun) -> (bool, Vec<ChoiceNode>) + 'a;
pub struct Shrinker<'a> {
test_fn: Box<TestFn<'a>>,
pub current_nodes: Vec<ChoiceNode>,
pub improvements: usize,
pub downgraded: Vec<Vec<ChoiceValue>>,
pub max_improvements: Option<usize>,
}
impl<'a> Shrinker<'a> {
pub fn with_probe(test_fn: Box<TestFn<'a>>, initial_nodes: Vec<ChoiceNode>) -> Self {
Shrinker {
test_fn,
current_nodes: initial_nodes,
improvements: 0,
downgraded: Vec::new(),
max_improvements: None,
}
}
pub fn consider(&mut self, nodes: &[ChoiceNode]) -> bool {
if sort_key(nodes) == sort_key(&self.current_nodes) {
return true;
}
if let Some(max) = self.max_improvements {
if self.improvements >= max {
return false;
}
}
let (is_interesting, actual_nodes) = (self.test_fn)(ShrinkRun::Full(nodes));
if is_interesting && sort_key(&actual_nodes) < sort_key(&self.current_nodes) {
let old: Vec<ChoiceValue> =
self.current_nodes.iter().map(|n| n.value.clone()).collect();
self.downgraded.push(old);
self.improvements += 1;
self.current_nodes = actual_nodes;
}
is_interesting
}
pub(super) fn probe(&mut self, prefix: &[ChoiceValue], seed: u64, max_size: usize) {
if let Some(max) = self.max_improvements {
if self.improvements >= max {
return;
}
}
let (is_interesting, actual_nodes) = (self.test_fn)(ShrinkRun::Probe {
prefix,
seed,
max_size,
});
if is_interesting && sort_key(&actual_nodes) < sort_key(&self.current_nodes) {
let old: Vec<ChoiceValue> =
self.current_nodes.iter().map(|n| n.value.clone()).collect();
self.downgraded.push(old);
self.improvements += 1;
self.current_nodes = actual_nodes;
}
}
pub fn replace(&mut self, values: &HashMap<usize, ChoiceValue>) -> bool {
let mut attempt: Vec<ChoiceNode> = self.current_nodes.clone();
for (&i, v) in values {
if i >= attempt.len() {
return false; }
if !attempt[i].kind.validate(v) {
return false; }
attempt[i] = attempt[i].with_value(v.clone());
}
self.consider(&attempt)
}
pub fn shrink(&mut self) {
let mut prev: Vec<NodeSortKey> = Vec::new();
let mut iterations = 0;
loop {
let current_key: Vec<NodeSortKey> =
self.current_nodes.iter().map(|n| n.sort_key()).collect();
if current_key == prev || iterations >= MAX_SHRINK_ITERATIONS {
break;
}
prev = current_key;
iterations += 1;
self.delete_chunks();
self.zero_choices();
self.swap_integer_sign();
self.binary_search_integer_towards_zero();
self.bind_deletion();
self.redistribute_integers();
self.lower_integers_together();
self.shrink_duplicates();
self.sort_values();
self.swap_adjacent_blocks();
self.shrink_floats();
self.redistribute_numeric_pairs();
self.lower_and_bump();
self.try_shortening_via_increment();
self.mutate_and_shrink();
}
}
}
pub(super) fn bin_search_down(lo: i128, hi: i128, f: &mut impl FnMut(i128) -> bool) -> i128 {
if f(lo) {
return lo;
}
let mut lo = lo;
let mut hi = hi;
while lo.checked_add(1).is_some_and(|n| n < hi) {
let mid = lo + (hi - lo) / 2;
if f(mid) {
hi = mid;
} else {
lo = mid;
}
}
hi
}
pub(super) fn find_integer(mut f: impl FnMut(usize) -> bool) -> usize {
for i in 1..5 {
if !f(i) {
return i - 1;
}
}
let mut lo = 4;
let mut hi = 5;
while f(hi) {
lo = hi;
let Some(next) = hi.checked_mul(2) else {
return lo; };
hi = next;
}
while lo + 1 < hi {
let mid = lo + (hi - lo) / 2;
if f(mid) {
lo = mid;
} else {
hi = mid;
}
}
lo
}