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(); let mut module_src = IndexMap::new();
println!("Looking for modules definitions");
for module in self.values() { for module in self.values() {
for def in module.ast.definitions() { for def in module.ast.definitions() {
match def { match def {
Definition::Fn(func) => { Definition::Fn(func) => {
println!("Found function: {}", func.name);
functions.insert( functions.insert(
FunctionAccessKey { FunctionAccessKey {
module_name: module.name.clone(), 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. // also the element itself, which may involve more than one choice.
let mut k = 8; let mut k = 8;
while k > 0 { while k > 0 {
if k > self.choices.len() { let (mut i, mut underflow) = if self.choices.len() < k {
break; (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 = [ let mut choices = [
&self.choices[..i], &self.choices[..i],
if j < self.choices.len() { if j < self.choices.len() {
@ -564,10 +573,7 @@ impl<'a> Counterexample<'a> {
] ]
.concat(); .concat();
if self.consider(&choices) { if !self.consider(&choices) {
break;
}
// Perform an extra reduction step that decrease the size of choices near // Perform an extra reduction step that decrease the size of choices near
// the end, to cope with dependencies between choices, e.g. drawing a // the end, to cope with dependencies between choices, e.g. drawing a
// number as a list length, and then drawing that many elements. // 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 { if i > 0 && choices[i - 1] > 0 {
choices[i - 1] -= 1; choices[i - 1] -= 1;
if self.consider(&choices) { if self.consider(&choices) {
break; i += 1;
}; };
} }
(i, underflow) = i.overflowing_sub(1);
}
} }
k /= 2 k /= 2
@ -591,11 +600,25 @@ impl<'a> Counterexample<'a> {
while k > 1 { while k > 1 {
let mut i: isize = self.choices.len() as isize - k; let mut i: isize = self.choices.len() as isize - k;
while i >= 0 { 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 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... // TODO: Remaining shrinking strategies...
// //
// - Swaps // - Swaps
@ -610,15 +633,42 @@ impl<'a> Counterexample<'a> {
} }
} }
// Replace a block between indices 'i' and 'k' by zeroes. /// Try to replace a value with a smaller value by doing a binary search between
fn zeroes(&mut self, i: isize, k: isize) -> bool { /// 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(); let mut choices = self.choices.clone();
for j in i..(i + k) { for (i, v) in ivs {
if j >= self.choices.len() as isize { if i >= choices.len() {
return false; return false;
} }
choices[j as usize] = 0; choices[i] = v;
} }
self.consider(&choices) 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] #[test]
fn test_prop_basic() { fn test_prop_basic() {
let prop = property(indoc! { r#" let prop = property(indoc! { r#"
@ -991,13 +1061,18 @@ mod test {
assert!(prop.run(42).is_success()); assert!(prop.run(42).is_success());
} }
// fn counterexample<'a>(choices: &'a [u32], property: &'a PropertyTest) -> Counterexample<'a> { #[test]
// let value = todo!(); fn test_prop_always_odd() {
// let prop = property(indoc! { r#"
// Counterexample { test foo(n: Int via int()) {
// value, n % 2 == 0
// choices: choices.to_vec(), }
// property, "#});
// }
// } let mut counterexample = prop.expect_failure(12);
counterexample.simplify();
assert_eq!(counterexample.choices, vec![1]);
}
} }