Fix shrinker impl and implement 3rd strategy of bin_search reduction.
This commit is contained in:
parent
70ea3c9598
commit
3df5bcd96d
|
@ -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(),
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue