Fix shrinker impl and implement 3rd strategy of bin_search reduction.

This commit is contained in:
KtorZ 2024-03-02 18:54:54 +01:00
parent 70ea3c9598
commit 3df5bcd96d
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
2 changed files with 107 additions and 35 deletions

View File

@ -371,13 +371,10 @@ impl CheckedModules {
let mut module_src = IndexMap::new();
println!("Looking for modules definitions");
for module in self.values() {
for def in module.ast.definitions() {
match def {
Definition::Fn(func) => {
println!("Found function: {}", func.name);
functions.insert(
FunctionAccessKey {
module_name: module.name.clone(),

View File

@ -549,11 +549,20 @@ impl<'a> Counterexample<'a> {
// also the element itself, which may involve more than one choice.
let mut k = 8;
while k > 0 {
if k > self.choices.len() {
break;
let (mut i, mut underflow) = if self.choices.len() < k {
(0, true)
} else {
(self.choices.len() - k, false)
};
while !underflow {
if i >= self.choices.len() {
(i, underflow) = i.overflowing_sub(1);
continue;
}
for (i, j) in (0..=self.choices.len() - k).map(|i| (i, i + k)).rev() {
let j = i + k;
let mut choices = [
&self.choices[..i],
if j < self.choices.len() {
@ -564,10 +573,7 @@ impl<'a> Counterexample<'a> {
]
.concat();
if self.consider(&choices) {
break;
}
if !self.consider(&choices) {
// Perform an extra reduction step that decrease the size of choices near
// the end, to cope with dependencies between choices, e.g. drawing a
// number as a list length, and then drawing that many elements.
@ -576,9 +582,12 @@ impl<'a> Counterexample<'a> {
if i > 0 && choices[i - 1] > 0 {
choices[i - 1] -= 1;
if self.consider(&choices) {
break;
i += 1;
};
}
(i, underflow) = i.overflowing_sub(1);
}
}
k /= 2
@ -591,11 +600,25 @@ impl<'a> Counterexample<'a> {
while k > 1 {
let mut i: isize = self.choices.len() as isize - k;
while i >= 0 {
i -= if self.zeroes(i, k) { k } else { 1 }
let ivs = (i..i + k).map(|j| (j as usize, 0)).collect::<Vec<_>>();
i -= if self.replace(ivs) { k } else { 1 }
}
k /= 2
}
// Replace choices with smaller value, by doing a binary search. This will replace n
// with 0 or n - 1, if possible, but will also more efficiently replace it with, a
// smaller number than doing multiple subtractions would.
let (mut i, mut underflow) = if self.choices.is_empty() {
(0, true)
} else {
(self.choices.len() - 1, false)
};
while !underflow {
self.binary_search_replace(0, self.choices[i], |v| vec![(i, v)]);
(i, underflow) = i.overflowing_sub(1);
}
// TODO: Remaining shrinking strategies...
//
// - Swaps
@ -610,15 +633,42 @@ impl<'a> Counterexample<'a> {
}
}
// Replace a block between indices 'i' and 'k' by zeroes.
fn zeroes(&mut self, i: isize, k: isize) -> bool {
/// Try to replace a value with a smaller value by doing a binary search between
/// two extremes. This converges relatively fast in order to shrink down values.
/// fast.
fn binary_search_replace<F>(&mut self, lo: u32, hi: u32, f: F) -> u32
where
F: Fn(u32) -> Vec<(usize, u32)>,
{
if self.replace(f(lo)) {
return lo;
}
let mut lo = lo;
let mut hi = hi;
while lo + 1 < hi {
let mid = lo + (hi - lo) / 2;
if self.replace(f(mid)) {
hi = mid;
} else {
lo = mid;
}
}
hi
}
// Replace values in the choices vector, based on the index-value list provided
// and consider the resulting choices.
fn replace(&mut self, ivs: Vec<(usize, u32)>) -> bool {
let mut choices = self.choices.clone();
for j in i..(i + k) {
if j >= self.choices.len() as isize {
for (i, v) in ivs {
if i >= choices.len() {
return false;
}
choices[j as usize] = 0;
choices[i] = v;
}
self.consider(&choices)
@ -980,6 +1030,26 @@ mod test {
}
}
impl PropertyTest {
fn expect_failure(&self, seed: u32) -> Counterexample {
let (next_prng, value) = Prng::from_seed(seed)
.sample(&self.fuzzer.program)
.expect("running seeded Prng cannot fail.");
let result = self.eval(&value);
if result.failed(self.can_error) {
return Counterexample {
value,
choices: next_prng.choices(),
property: self,
};
}
unreachable!("Prng constructed from a seed necessarily yield a seed.");
}
}
#[test]
fn test_prop_basic() {
let prop = property(indoc! { r#"
@ -991,13 +1061,18 @@ mod test {
assert!(prop.run(42).is_success());
}
// fn counterexample<'a>(choices: &'a [u32], property: &'a PropertyTest) -> Counterexample<'a> {
// let value = todo!();
//
// Counterexample {
// value,
// choices: choices.to_vec(),
// property,
// }
// }
#[test]
fn test_prop_always_odd() {
let prop = property(indoc! { r#"
test foo(n: Int via int()) {
n % 2 == 0
}
"#});
let mut counterexample = prop.expect_failure(12);
counterexample.simplify();
assert_eq!(counterexample.choices, vec![1]);
}
}