Only reify unit tests assertions on failure.

This commit is contained in:
KtorZ 2024-03-07 19:07:55 +01:00
parent 0d599f7e2d
commit 8e558d893f
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 75 additions and 48 deletions

View File

@ -38,7 +38,7 @@ use aiken_lang::{
expr::UntypedExpr, expr::UntypedExpr,
gen_uplc::CodeGenerator, gen_uplc::CodeGenerator,
line_numbers::LineNumbers, line_numbers::LineNumbers,
tipo::TypeInfo, tipo::{Type, TypeInfo},
IdGenerator, IdGenerator,
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
@ -55,11 +55,12 @@ use std::{
fs::{self, File}, fs::{self, File},
io::BufReader, io::BufReader,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc,
}; };
use telemetry::EventListener; use telemetry::EventListener;
use test_framework::{Test, TestResult}; use test_framework::{Test, TestResult};
use uplc::{ use uplc::{
ast::{Name, Program}, ast::{Constant, Name, Program},
PlutusData, PlutusData,
}; };
@ -834,7 +835,7 @@ where
Ok(tests) Ok(tests)
} }
fn run_tests(&self, tests: Vec<Test>, seed: u32) -> Vec<TestResult<UntypedExpr>> { fn run_tests(&self, tests: Vec<Test>, seed: u32) -> Vec<TestResult<UntypedExpr, UntypedExpr>> {
use rayon::prelude::*; use rayon::prelude::*;
let data_types = utils::indexmap::as_ref_values(&self.data_types); let data_types = utils::indexmap::as_ref_values(&self.data_types);
@ -845,7 +846,7 @@ where
Test::UnitTest(unit_test) => unit_test.run(), Test::UnitTest(unit_test) => unit_test.run(),
Test::PropertyTest(property_test) => property_test.run(seed), Test::PropertyTest(property_test) => property_test.run(seed),
}) })
.collect::<Vec<TestResult<PlutusData>>>() .collect::<Vec<TestResult<(Constant, Rc<Type>), PlutusData>>>()
.into_iter() .into_iter()
.map(|test| test.reify(&data_types)) .map(|test| test.reify(&data_types))
.collect() .collect()

View File

@ -38,7 +38,7 @@ pub enum Event {
RunningTests, RunningTests,
FinishedTests { FinishedTests {
seed: u32, seed: u32,
tests: Vec<TestResult<UntypedExpr>>, tests: Vec<TestResult<UntypedExpr, UntypedExpr>>,
}, },
WaitingForBuildDirLock, WaitingForBuildDirLock,
ResolvingPackages { ResolvingPackages {
@ -266,7 +266,7 @@ impl EventListener for Terminal {
} }
fn fmt_test( fn fmt_test(
result: &TestResult<UntypedExpr>, result: &TestResult<UntypedExpr, UntypedExpr>,
max_mem: usize, max_mem: usize,
max_cpu: usize, max_cpu: usize,
max_iter: usize, max_iter: usize,
@ -324,16 +324,16 @@ fn fmt_test(
// Annotations // Annotations
match result { match result {
TestResult::UnitTestResult(UnitTestResult { TestResult::UnitTestResult(UnitTestResult {
test: unit_test, .. assertion: Some(assertion),
test: unit_test,
..
}) if !result.is_success() => { }) if !result.is_success() => {
if let Some(ref assertion) = unit_test.assertion {
test = format!( test = format!(
"{test}\n{}{new_line}", "{test}\n{}{new_line}",
assertion.to_string(Stderr, unit_test.can_error), assertion.to_string(Stderr, unit_test.can_error),
new_line = if result.logs().is_empty() { "\n" } else { "" }, new_line = if result.logs().is_empty() { "\n" } else { "" },
); );
} }
}
_ => (), _ => (),
} }
@ -396,7 +396,7 @@ fn fmt_test(
test test
} }
fn fmt_test_summary<T>(tests: &[&TestResult<T>], styled: bool) -> String { fn fmt_test_summary<T>(tests: &[&TestResult<T, T>], styled: bool) -> String {
let (n_passed, n_failed) = tests.iter().fold((0, 0), |(n_passed, n_failed), result| { let (n_passed, n_failed) = tests.iter().fold((0, 0), |(n_passed, n_failed), result| {
if result.is_success() { if result.is_success() {
(n_passed + 1, n_failed) (n_passed + 1, n_failed)
@ -420,16 +420,16 @@ fn fmt_test_summary<T>(tests: &[&TestResult<T>], styled: bool) -> String {
) )
} }
fn group_by_module<T>(results: &Vec<TestResult<T>>) -> BTreeMap<String, Vec<&TestResult<T>>> { fn group_by_module<T>(results: &Vec<TestResult<T, T>>) -> BTreeMap<String, Vec<&TestResult<T, T>>> {
let mut modules = BTreeMap::new(); let mut modules = BTreeMap::new();
for r in results { for r in results {
let xs: &mut Vec<&TestResult<_>> = modules.entry(r.module().to_string()).or_default(); let xs: &mut Vec<&TestResult<_, _>> = modules.entry(r.module().to_string()).or_default();
xs.push(r); xs.push(r);
} }
modules modules
} }
fn find_max_execution_units<T>(xs: &[TestResult<T>]) -> (usize, usize, usize) { fn find_max_execution_units<T>(xs: &[TestResult<T, T>]) -> (usize, usize, usize) {
let (max_mem, max_cpu, max_iter) = let (max_mem, max_cpu, max_iter) =
xs.iter() xs.iter()
.fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test { .fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test {

View File

@ -49,8 +49,6 @@ impl Test {
module_name: String, module_name: String,
input_path: PathBuf, input_path: PathBuf,
) -> Test { ) -> Test {
let data_types = generator.data_types().clone();
let program = generator.generate_raw(&test.body, &[], &module_name); let program = generator.generate_raw(&test.body, &[], &module_name);
let assertion = match test.body.try_into() { let assertion = match test.body.try_into() {
@ -65,10 +63,7 @@ impl Test {
.expect("failed to convert assertion operaand to NamedDeBruijn") .expect("failed to convert assertion operaand to NamedDeBruijn")
.eval(ExBudget::max()) .eval(ExBudget::max())
.unwrap_constant() .unwrap_constant()
.map(|cst| { .map(|cst| (cst, side.tipo()))
UntypedExpr::reify_constant(&data_types, cst, &side.tipo())
.expect("failed to reify assertion operand?")
})
}; };
Some(Assertion { Some(Assertion {
@ -165,13 +160,13 @@ pub struct UnitTest {
pub name: String, pub name: String,
pub can_error: bool, pub can_error: bool,
pub program: Program<Name>, pub program: Program<Name>,
pub assertion: Option<Assertion<UntypedExpr>>, pub assertion: Option<Assertion<(Constant, Rc<Type>)>>,
} }
unsafe impl Send for UnitTest {} unsafe impl Send for UnitTest {}
impl UnitTest { impl UnitTest {
pub fn run<T>(self) -> TestResult<T> { pub fn run<T>(self) -> TestResult<(Constant, Rc<Type>), T> {
let mut eval_result = Program::<NamedDeBruijn>::try_from(self.program.clone()) let mut eval_result = Program::<NamedDeBruijn>::try_from(self.program.clone())
.unwrap() .unwrap()
.eval(ExBudget::max()); .eval(ExBudget::max());
@ -184,6 +179,7 @@ impl UnitTest {
spent_budget: eval_result.cost(), spent_budget: eval_result.cost(),
logs: eval_result.logs(), logs: eval_result.logs(),
output: eval_result.result().ok(), output: eval_result.result().ok(),
assertion: self.assertion,
}) })
} }
} }
@ -219,7 +215,7 @@ impl PropertyTest {
/// Run a property test from a given seed. The property is run at most MAX_TEST_RUN times. It /// Run a property test from a given seed. The property is run at most MAX_TEST_RUN times. It
/// may stops earlier on failure; in which case a 'counterexample' is returned. /// may stops earlier on failure; in which case a 'counterexample' is returned.
pub fn run(self, seed: u32) -> TestResult<PlutusData> { pub fn run<U>(self, seed: u32) -> TestResult<U, PlutusData> {
let n = PropertyTest::MAX_TEST_RUN; let n = PropertyTest::MAX_TEST_RUN;
let (counterexample, iterations) = match self.run_n_times(n, Prng::from_seed(seed), None) { let (counterexample, iterations) = match self.run_n_times(n, Prng::from_seed(seed), None) {
@ -679,20 +675,20 @@ impl<'a> Counterexample<'a> {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[derive(Debug)] #[derive(Debug)]
pub enum TestResult<T> { pub enum TestResult<U, T> {
UnitTestResult(UnitTestResult), UnitTestResult(UnitTestResult<U>),
PropertyTestResult(PropertyTestResult<T>), PropertyTestResult(PropertyTestResult<T>),
} }
unsafe impl<T> Send for TestResult<T> {} unsafe impl<U, T> Send for TestResult<U, T> {}
impl TestResult<PlutusData> { impl TestResult<(Constant, Rc<Type>), PlutusData> {
pub fn reify( pub fn reify(
self, self,
data_types: &IndexMap<&DataTypeKey, &TypedDataType>, data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
) -> TestResult<UntypedExpr> { ) -> TestResult<UntypedExpr, UntypedExpr> {
match self { match self {
TestResult::UnitTestResult(test) => TestResult::UnitTestResult(test), TestResult::UnitTestResult(test) => TestResult::UnitTestResult(test.reify(data_types)),
TestResult::PropertyTestResult(test) => { TestResult::PropertyTestResult(test) => {
TestResult::PropertyTestResult(test.reify(data_types)) TestResult::PropertyTestResult(test.reify(data_types))
} }
@ -700,7 +696,7 @@ impl TestResult<PlutusData> {
} }
} }
impl<T> TestResult<T> { impl<U, T> TestResult<U, T> {
pub fn is_success(&self) -> bool { pub fn is_success(&self) -> bool {
match self { match self {
TestResult::UnitTestResult(UnitTestResult { success, .. }) => *success, TestResult::UnitTestResult(UnitTestResult { success, .. }) => *success,
@ -766,15 +762,52 @@ impl<T> TestResult<T> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UnitTestResult { pub struct UnitTestResult<T> {
pub success: bool, pub success: bool,
pub spent_budget: ExBudget, pub spent_budget: ExBudget,
pub output: Option<Term<NamedDeBruijn>>, pub output: Option<Term<NamedDeBruijn>>,
pub logs: Vec<String>, pub logs: Vec<String>,
pub test: UnitTest, pub test: UnitTest,
pub assertion: Option<Assertion<T>>,
} }
unsafe impl Send for UnitTestResult {} unsafe impl<T> Send for UnitTestResult<T> {}
impl UnitTestResult<(Constant, Rc<Type>)> {
pub fn reify(
self,
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
) -> UnitTestResult<UntypedExpr> {
UnitTestResult {
success: self.success,
spent_budget: self.spent_budget,
output: self.output,
logs: self.logs,
test: self.test,
assertion: self.assertion.and_then(|assertion| {
// No need to spend time/cpu on reifying assertions for successful
// tests since they aren't shown.
if self.success {
return None;
}
Some(Assertion {
bin_op: assertion.bin_op,
head: assertion.head.map(|(cst, tipo)| {
UntypedExpr::reify_constant(data_types, cst, &tipo)
.expect("failed to reify assertion operand?")
}),
tail: assertion.tail.map(|xs| {
xs.mapped(|(cst, tipo)| {
UntypedExpr::reify_constant(data_types, cst, &tipo)
.expect("failed to reify assertion operand?")
})
}),
})
}),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct PropertyTestResult<T> { pub struct PropertyTestResult<T> {
@ -791,17 +824,10 @@ impl PropertyTestResult<PlutusData> {
data_types: &IndexMap<&DataTypeKey, &TypedDataType>, data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
) -> PropertyTestResult<UntypedExpr> { ) -> PropertyTestResult<UntypedExpr> {
PropertyTestResult { PropertyTestResult {
counterexample: match self.counterexample { counterexample: self.counterexample.map(|counterexample| {
None => None, UntypedExpr::reify_data(data_types, counterexample, &self.test.fuzzer.type_info)
Some(counterexample) => Some( .expect("Failed to reify counterexample?")
UntypedExpr::reify_data( }),
data_types,
counterexample,
&self.test.fuzzer.type_info,
)
.expect("Failed to reify counterexample?"),
),
},
iterations: self.iterations, iterations: self.iterations,
test: self.test, test: self.test,
} }
@ -1266,7 +1292,7 @@ mod test {
} }
"#}); "#});
assert!(prop.run(42).is_success()); assert!(prop.run::<()>(42).is_success());
} }
#[test] #[test]