Provide intermediate feedback during property test runs.

Avoid the interface to hang for several seconds without feedback when counterexamples are being simplified. This sends a heads-up to the user to indicate that a research of a counter example is going on.
This commit is contained in:
KtorZ 2024-09-19 20:18:34 +02:00
parent 91843b2c0e
commit 3f149ab346
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 80 additions and 7 deletions

View File

@ -9,8 +9,9 @@
### Changed ### Changed
- **aiken-project**: Fix documentation link-tree generation messing up with modules when re-inserting the same module. @KtorZ - **aiken-project**: Fix documentation link-tree generation messing up with modules when re-inserting the same module. @KtorZ
- **aiken-project**: Provide intermediate feedback when looking for counterexamples during property tests. @KtorZ
- **aiken-lang**: Fix formatter adding extra unnecessary newlines after literal lists clause values or assignments. @KtorZ - **aiken-lang**: Fix formatter adding extra unnecessary newlines after literal lists clause values or assignments. @KtorZ
- **aiken0lang**: Fix formatting of long multi-line if/is expressions. @KtorZ - **aiken-lang**: Fix formatting of long multi-line if/is expressions. @KtorZ
### Removed ### Removed

View File

@ -9,12 +9,18 @@ use crate::{
use cryptoxide::{blake2b::Blake2b, digest::Digest}; use cryptoxide::{blake2b::Blake2b, digest::Digest};
use indexmap::IndexMap; use indexmap::IndexMap;
use itertools::Itertools; use itertools::Itertools;
use owo_colors::{OwoColorize, Stream}; use owo_colors::{OwoColorize, Stream, Stream::Stderr};
use pallas_primitives::alonzo::{Constr, PlutusData}; use pallas_primitives::alonzo::{Constr, PlutusData};
use patricia_tree::PatriciaMap; use patricia_tree::PatriciaMap;
use std::{ use std::{
borrow::Borrow, collections::BTreeMap, convert::TryFrom, fmt::Debug, ops::Deref, path::PathBuf, borrow::Borrow,
collections::BTreeMap,
convert::TryFrom,
fmt::{Debug, Display},
ops::Deref,
path::PathBuf,
rc::Rc, rc::Rc,
time::Duration,
}; };
use uplc::{ use uplc::{
ast::{Constant, Data, Name, NamedDeBruijn, Program, Term}, ast::{Constant, Data, Name, NamedDeBruijn, Program, Term},
@ -199,7 +205,7 @@ impl UnitTest {
} }
/// ----- PropertyTest ----------------------------------------------------------------- /// ----- PropertyTest -----------------------------------------------------------------
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PropertyTest { pub struct PropertyTest {
pub input_path: PathBuf, pub input_path: PathBuf,
@ -231,6 +237,42 @@ pub struct FuzzerError {
uplc_error: uplc::machine::Error, uplc_error: uplc::machine::Error,
} }
#[derive(Debug, Clone)]
pub enum Event {
Simplifying { choices: usize },
Simplified { duration: Duration, steps: usize },
}
impl Display for Event {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
match self {
Event::Simplifying { choices } => f.write_str(&format!(
"{} {}",
" Simplifying"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
format!("counterexample from {choices} choices")
.if_supports_color(Stderr, |s| s.bold()),
)),
Event::Simplified { duration, steps } => f.write_str(&format!(
"{} {}",
" Simplified"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
format!(
"counterexample in {} after {steps} steps",
if duration.as_secs() == 0 {
format!("{}ms", duration.as_millis())
} else {
format!("{}s", duration.as_secs())
}
)
.if_supports_color(Stderr, |s| s.bold()),
)),
}
}
}
impl PropertyTest { impl PropertyTest {
pub const DEFAULT_MAX_SUCCESS: usize = 100; pub const DEFAULT_MAX_SUCCESS: usize = 100;
@ -626,6 +668,16 @@ impl<'a> Counterexample<'a> {
pub fn simplify(&mut self) { pub fn simplify(&mut self) {
let mut prev; let mut prev;
let mut steps = 0;
let now = std::time::Instant::now();
eprintln!(
"{}",
Event::Simplifying {
choices: self.choices.len(),
}
);
loop { loop {
prev = self.choices.clone(); prev = self.choices.clone();
@ -644,6 +696,7 @@ impl<'a> Counterexample<'a> {
while !underflow { while !underflow {
if i >= self.choices.len() { if i >= self.choices.len() {
(i, underflow) = i.overflowing_sub(1); (i, underflow) = i.overflowing_sub(1);
steps += 1;
continue; continue;
} }
@ -674,6 +727,8 @@ impl<'a> Counterexample<'a> {
(i, underflow) = i.overflowing_sub(1); (i, underflow) = i.overflowing_sub(1);
} }
steps += 1;
} }
k /= 2 k /= 2
@ -687,6 +742,7 @@ impl<'a> Counterexample<'a> {
while k > 1 { while k > 1 {
let mut i = self.choices.len(); let mut i = self.choices.len();
while i >= k { while i >= k {
steps += 1;
let ivs = (i - k..i).map(|j| (j, 0)).collect::<Vec<_>>(); let ivs = (i - k..i).map(|j| (j, 0)).collect::<Vec<_>>();
i -= if self.replace(ivs) { k } else { 1 } i -= if self.replace(ivs) { k } else { 1 }
} }
@ -698,6 +754,7 @@ impl<'a> Counterexample<'a> {
// smaller number than doing multiple subtractions would. // smaller number than doing multiple subtractions would.
let (mut i, mut underflow) = (self.choices.len() - 1, false); let (mut i, mut underflow) = (self.choices.len() - 1, false);
while !underflow { while !underflow {
steps += 1;
self.binary_search_replace(0, self.choices[i], |v| vec![(i, v)]); self.binary_search_replace(0, self.choices[i], |v| vec![(i, v)]);
(i, underflow) = i.overflowing_sub(1); (i, underflow) = i.overflowing_sub(1);
} }
@ -707,6 +764,7 @@ impl<'a> Counterexample<'a> {
while k > 1 { while k > 1 {
let mut i = self.choices.len() - 1; let mut i = self.choices.len() - 1;
while i >= k { while i >= k {
steps += 1;
let (from, to) = (i - k, i); let (from, to) = (i - k, i);
self.replace( self.replace(
(from..to) (from..to)
@ -740,6 +798,8 @@ impl<'a> Counterexample<'a> {
self.binary_search_replace(0, iv, |v| vec![(i, v), (j, jv + (iv - v))]); self.binary_search_replace(0, iv, |v| vec![(i, v), (j, jv + (iv - v))]);
} }
steps += 1;
j -= 1 j -= 1
} }
} }
@ -751,6 +811,14 @@ impl<'a> Counterexample<'a> {
break; break;
} }
} }
eprintln!(
"{}",
Event::Simplified {
duration: now.elapsed(),
steps,
}
);
} }
/// Try to replace a value with a smaller value by doing a binary search between /// Try to replace a value with a smaller value by doing a binary search between
@ -1011,7 +1079,7 @@ impl PropertyTestResult<PlutusData> {
counterexample: self.counterexample.map(|ok| { counterexample: self.counterexample.map(|ok| {
ok.map(|counterexample| { ok.map(|counterexample| {
UntypedExpr::reify_data(data_types, counterexample, &self.test.fuzzer.type_info) UntypedExpr::reify_data(data_types, counterexample, &self.test.fuzzer.type_info)
.expect("Failed to reify counterexample?") .expect("failed to reify counterexample?")
}) })
}), }),
iterations: self.iterations, iterations: self.iterations,

View File

@ -168,7 +168,7 @@ impl EventListener for Terminal {
} }
Event::RunningTests => { Event::RunningTests => {
eprintln!( eprintln!(
"{} {}\n", "{} {}",
" Testing" " Testing"
.if_supports_color(Stderr, |s| s.bold()) .if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()), .if_supports_color(Stderr, |s| s.purple()),
@ -205,7 +205,7 @@ impl EventListener for Terminal {
let summary = format!("{}{}", seed_info, fmt_test_summary(results, true)); let summary = format!("{}{}", seed_info, fmt_test_summary(results, true));
println!( println!(
"{}\n", "\n{}",
pretty::indent( pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border &pretty::open_box(&title, &tests, &summary, |border| border
.if_supports_color(Stderr, |s| s.bright_black()) .if_supports_color(Stderr, |s| s.bright_black())
@ -214,6 +214,10 @@ impl EventListener for Terminal {
) )
); );
} }
if !tests.is_empty() {
println!();
}
} }
Event::ResolvingPackages { name } => { Event::ResolvingPackages { name } => {
eprintln!( eprintln!(