Collect and display evaluation hints on test failures.

This commit is contained in:
KtorZ 2022-12-14 22:00:56 +01:00
parent 7b22b63ad8
commit 978a6c6981
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 93 additions and 10 deletions

View File

@ -85,6 +85,21 @@ pub struct Function<T, Expr> {
pub type TypedTypeAlias = TypeAlias<Arc<Type>>; pub type TypedTypeAlias = TypeAlias<Arc<Type>>;
pub type UntypedTypeAlias = TypeAlias<()>; pub type UntypedTypeAlias = TypeAlias<()>;
impl TypedFunction {
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
match &self.body {
TypedExpr::BinOp {
name,
tipo,
left,
right,
..
} if tipo == &bool() => Some((name.clone(), left.clone(), right.clone())),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct TypeAlias<T> { pub struct TypeAlias<T> {
pub alias: String, pub alias: String,

View File

@ -1,13 +1,18 @@
use crate::{pretty, script::EvalHint};
use aiken_lang::{
ast::{BinOp, Span},
parser::error::ParseError,
tipo,
};
use miette::{
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
io, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use uplc::machine::cost_model::ExBudget;
use aiken_lang::{ast::Span, parser::error::ParseError, tipo};
use miette::{
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
};
#[allow(dead_code)] #[allow(dead_code)]
#[derive(thiserror::Error)] #[derive(thiserror::Error)]
@ -75,7 +80,11 @@ pub enum Error {
}, },
#[error("{} failed", name)] #[error("{} failed", name)]
TestFailure { name: String, path: PathBuf }, TestFailure {
name: String,
path: PathBuf,
evaluation_hint: Option<EvalHint>,
},
} }
impl Error { impl Error {
@ -231,7 +240,34 @@ impl Diagnostic for Error {
Error::Format { .. } => None, Error::Format { .. } => None,
Error::ValidatorMustReturnBool { .. } => Some(Box::new("Try annotating the validator's return type with Bool")), Error::ValidatorMustReturnBool { .. } => Some(Box::new("Try annotating the validator's return type with Bool")),
Error::WrongValidatorArity { .. } => Some(Box::new("Validators require a minimum number of arguments please add the missing arguments.\nIf you don't need one of the required arguments use an underscore `_datum`.")), Error::WrongValidatorArity { .. } => Some(Box::new("Validators require a minimum number of arguments please add the missing arguments.\nIf you don't need one of the required arguments use an underscore `_datum`.")),
Error::TestFailure { .. } => None, Error::TestFailure { evaluation_hint, .. } =>{
match evaluation_hint {
None => None,
Some(hint) => {
let budget = ExBudget { mem: i64::MAX, cpu: i64::MAX, };
let left = pretty::boxed("left", match hint.left.eval(budget) {
(Ok(term), _, _) => format!("{term}"),
(Err(err), _, _) => format!("{err}"),
});
let right = pretty::boxed("right", match hint.right.eval(budget) {
(Ok(term), _, _) => format!("{term}"),
(Err(err), _, _) => format!("{err}"),
});
let msg = match hint.bin_op {
BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")),
BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")),
BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
BinOp::LtEqInt => Some(format!("{left}\n\nshould be lower than or equal to\n\n{right}")),
BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
BinOp::GtInt => Some(format!("{left}\n\nshould be greater than or equal to\n\n{right}")),
_ => None
}?;
Some(Box::new(msg))
}
}
},
} }
} }

View File

@ -25,9 +25,14 @@ use pallas::{
ledger::{addresses::Address, primitives::babbage}, ledger::{addresses::Address, primitives::babbage},
}; };
use pallas_traverse::ComputeHash; use pallas_traverse::ComputeHash;
use script::Script; use script::{EvalHint, EvalInfo, Script};
use serde_json::json; use serde_json::json;
use telemetry::{EvalInfo, EventListener}; use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use telemetry::EventListener;
use uplc::{ use uplc::{
ast::{Constant, DeBruijn, Program, Term}, ast::{Constant, DeBruijn, Program, Term},
machine::cost_model::ExBudget, machine::cost_model::ExBudget,
@ -159,6 +164,7 @@ where
Some(Error::TestFailure { Some(Error::TestFailure {
name: e.script.name.clone(), name: e.script.name.clone(),
path: e.script.input_path.clone(), path: e.script.input_path.clone(),
evaluation_hint: e.script.evaluation_hint.clone(),
}) })
} }
}) })
@ -472,7 +478,13 @@ where
let program = generator.generate(body, arguments, true); let program = generator.generate(body, arguments, true);
let script = Script::new(input_path, module_name, name, program.try_into().unwrap()); let script = Script::new(
input_path,
module_name,
name,
program.try_into().unwrap(),
None,
);
programs.push(script); programs.push(script);
} }
@ -571,6 +583,25 @@ where
&self.module_types, &self.module_types,
); );
let evaluation_hint = if let Some((bin_op, left_src, right_src)) = func_def.test_hint()
{
let left = CodeGenerator::new(&functions, &data_types, &self.module_types)
.generate(*left_src, vec![], false)
.try_into()
.unwrap();
let right = CodeGenerator::new(&functions, &data_types, &self.module_types)
.generate(*right_src, vec![], false)
.try_into()
.unwrap();
Some(EvalHint {
bin_op,
left,
right,
})
} else {
None
};
let program = generator.generate(body.clone(), arguments.clone(), false); let program = generator.generate(body.clone(), arguments.clone(), false);
let script = Script::new( let script = Script::new(
@ -578,6 +609,7 @@ where
module_name, module_name,
name.to_string(), name.to_string(),
program.try_into().unwrap(), program.try_into().unwrap(),
evaluation_hint,
); );
programs.push(script); programs.push(script);