252 lines
7.3 KiB
Rust
252 lines
7.3 KiB
Rust
use crate::{pretty, ExBudget};
|
|
use aiken_lang::ast::BinOp;
|
|
use std::{
|
|
borrow::Borrow,
|
|
fmt::{self, Display},
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
};
|
|
use uplc::{
|
|
ast::{Constant, Data, NamedDeBruijn, Program, Term},
|
|
machine::eval_result::EvalResult,
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Test {
|
|
UnitTest(UnitTest),
|
|
PropertyTest(PropertyTest),
|
|
}
|
|
|
|
unsafe impl Send for Test {}
|
|
|
|
impl Test {
|
|
pub fn unit_test(
|
|
input_path: PathBuf,
|
|
module: String,
|
|
name: String,
|
|
can_error: bool,
|
|
program: Program<NamedDeBruijn>,
|
|
evaluation_hint: Option<EvalHint>,
|
|
) -> Test {
|
|
Test::UnitTest(UnitTest {
|
|
input_path,
|
|
module,
|
|
name,
|
|
program,
|
|
can_error,
|
|
evaluation_hint,
|
|
})
|
|
}
|
|
|
|
pub fn property_test(
|
|
input_path: PathBuf,
|
|
module: String,
|
|
name: String,
|
|
can_error: bool,
|
|
program: Program<NamedDeBruijn>,
|
|
fuzzer: Program<NamedDeBruijn>,
|
|
) -> Test {
|
|
Test::PropertyTest(PropertyTest {
|
|
input_path,
|
|
module,
|
|
name,
|
|
program,
|
|
can_error,
|
|
fuzzer,
|
|
})
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
match self {
|
|
Test::UnitTest(test) => &test.name,
|
|
Test::PropertyTest(test) => &test.name,
|
|
}
|
|
}
|
|
|
|
pub fn module(&self) -> &str {
|
|
match self {
|
|
Test::UnitTest(test) => &test.module,
|
|
Test::PropertyTest(test) => &test.module,
|
|
}
|
|
}
|
|
|
|
pub fn input_path(&self) -> &Path {
|
|
match self {
|
|
Test::UnitTest(test) => &test.input_path,
|
|
Test::PropertyTest(test) => &test.input_path,
|
|
}
|
|
}
|
|
|
|
pub fn program(&self) -> &Program<NamedDeBruijn> {
|
|
match self {
|
|
Test::UnitTest(test) => &test.program,
|
|
Test::PropertyTest(test) => &test.program,
|
|
}
|
|
}
|
|
|
|
pub fn evaluation_hint(&self) -> Option<&EvalHint> {
|
|
match self {
|
|
Test::UnitTest(test) => test.evaluation_hint.as_ref(),
|
|
Test::PropertyTest(_) => None,
|
|
}
|
|
}
|
|
|
|
pub fn report<'a>(&'a self, eval_result: &mut EvalResult) -> EvalInfo<'a> {
|
|
let can_error = match self {
|
|
Test::UnitTest(test) => test.can_error,
|
|
Test::PropertyTest(test) => test.can_error,
|
|
};
|
|
|
|
EvalInfo {
|
|
test: self,
|
|
success: !eval_result.failed(can_error),
|
|
spent_budget: eval_result.cost(),
|
|
logs: eval_result.logs(),
|
|
output: eval_result.result().ok(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct UnitTest {
|
|
pub input_path: PathBuf,
|
|
pub module: String,
|
|
pub name: String,
|
|
pub can_error: bool,
|
|
pub program: Program<NamedDeBruijn>,
|
|
pub evaluation_hint: Option<EvalHint>,
|
|
}
|
|
|
|
unsafe impl Send for UnitTest {}
|
|
|
|
impl UnitTest {
|
|
pub fn run(&self) -> EvalResult {
|
|
self.program.clone().eval(ExBudget::max())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PropertyTest {
|
|
pub input_path: PathBuf,
|
|
pub module: String,
|
|
pub name: String,
|
|
pub can_error: bool,
|
|
pub program: Program<NamedDeBruijn>,
|
|
pub fuzzer: Program<NamedDeBruijn>,
|
|
}
|
|
|
|
unsafe impl Send for PropertyTest {}
|
|
|
|
impl PropertyTest {
|
|
pub fn new_seed(seed: u32) -> Term<NamedDeBruijn> {
|
|
Term::Constant(Rc::new(Constant::Data(Data::constr(
|
|
0,
|
|
vec![
|
|
Data::integer(seed.into()),
|
|
Data::integer(0.into()), // Size
|
|
],
|
|
))))
|
|
}
|
|
|
|
pub fn sample(&self, seed: Term<NamedDeBruijn>) -> (Term<NamedDeBruijn>, Term<NamedDeBruijn>) {
|
|
let term = self.fuzzer.apply_term(&seed).eval(ExBudget::max()).result();
|
|
|
|
if let Ok(Term::Constant(rc)) = term {
|
|
match &rc.borrow() {
|
|
Constant::ProtoPair(_, _, new_seed, value) => (
|
|
Term::Constant(new_seed.clone()),
|
|
Term::Constant(value.clone()),
|
|
),
|
|
_ => todo!("Fuzzer yielded a new seed that isn't an integer?"),
|
|
}
|
|
} else {
|
|
todo!("Fuzzer yielded something else than a pair? {:#?}", term)
|
|
}
|
|
}
|
|
|
|
pub fn run(&self, sample: &Term<NamedDeBruijn>) -> EvalResult {
|
|
self.program.apply_term(sample).eval(ExBudget::max())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct EvalHint {
|
|
pub bin_op: BinOp,
|
|
pub left: Program<NamedDeBruijn>,
|
|
pub right: Program<NamedDeBruijn>,
|
|
pub can_error: bool,
|
|
}
|
|
|
|
impl Display for EvalHint {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let unlimited_budget = ExBudget {
|
|
mem: i64::MAX,
|
|
cpu: i64::MAX,
|
|
};
|
|
|
|
let left = pretty::boxed(
|
|
"left",
|
|
&match self.left.clone().eval(unlimited_budget).result() {
|
|
Ok(term) => format!("{term}"),
|
|
Err(err) => format!("{err}"),
|
|
},
|
|
);
|
|
let right = pretty::boxed(
|
|
"right",
|
|
&match self.right.clone().eval(unlimited_budget).result() {
|
|
Ok(term) => format!("{term}"),
|
|
Err(err) => format!("{err}"),
|
|
},
|
|
);
|
|
let msg = if self.can_error {
|
|
match self.bin_op {
|
|
BinOp::And => Some(format!(
|
|
"{left}\n\nand\n\n{right}\n\nare both true but shouldn't."
|
|
)),
|
|
BinOp::Or => Some(format!(
|
|
"neither\n\n{left}\n\nnor\n\n{right}\n\nshould be true."
|
|
)),
|
|
BinOp::Eq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
|
BinOp::NotEq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
|
BinOp::LtInt => Some(format!(
|
|
"{left}\n\nshould be greater than or equal to\n\n{right}"
|
|
)),
|
|
BinOp::LtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
|
BinOp::GtEqInt => Some(format!(
|
|
"{left}\n\nshould be lower than or equal\n\n{right}"
|
|
)),
|
|
BinOp::GtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
|
_ => None,
|
|
}
|
|
} else {
|
|
match self.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,
|
|
}
|
|
}
|
|
.ok_or(fmt::Error)?;
|
|
|
|
f.write_str(&msg)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct EvalInfo<'a> {
|
|
pub success: bool,
|
|
pub spent_budget: ExBudget,
|
|
pub output: Option<Term<NamedDeBruijn>>,
|
|
pub logs: Vec<String>,
|
|
pub test: &'a Test,
|
|
}
|