Add preliminary plumbing to run property test through the CLI.
This is very very rough at the moment. But it does a couple of thing: 1. The 'ArgVia' now contains an Expr/TypedExpr which should unify to a Fuzzer. This is to avoid having to introduce custom logic to handle fuzzer referencing. So this now accepts function call, field access etc.. so long as they unify to the right thing. 2. I've done quite a lot of cleanup in aiken-project mostly around the tests and the naming surrounding them. What we used to call 'Script' is now called 'Test' and is an enum between UnitTest (ex-Script) and PropertyTest. I've moved some boilerplate and relevant function under those module Impl. 3. I've completed the end-to-end pipeline of: - Compiling the property test - Compiling the fuzzer - Generating an initial seed - Running property tests sequentially, threading the seed through each step. An interesting finding is that, I had to wrap the prop test in a similar wrapper that we use for validator, to ensure we convert primitive types wrapped in Data back to UPLC terms. This is necessary because the fuzzer return a ProtoPair (and soon an Array) which holds 'Data'. At the moment, we do nothing with the size, though the size should ideally grow after each iteration (up to a certain cap). In addition, there are a couple of todo/fixme that I left in the code as reminders of what's left to do beyond the obvious (error and success reporting, testing, etc..)
This commit is contained in:
parent
aadf3cfb48
commit
3762473a60
File diff suppressed because it is too large
Load Diff
|
@ -183,7 +183,11 @@ pub type UntypedTypeAlias = TypeAlias<()>;
|
||||||
|
|
||||||
impl TypedTest {
|
impl TypedTest {
|
||||||
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
||||||
|
if self.arguments.is_empty() {
|
||||||
do_test_hint(&self.body)
|
do_test_hint(&self.body)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,17 +371,11 @@ pub struct Validator<T, Expr> {
|
||||||
pub params: Vec<Arg<T>>,
|
pub params: Vec<Arg<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
pub type TypedDefinition = Definition<Rc<Type>, TypedExpr, String>;
|
||||||
pub struct DefinitionIdentifier {
|
pub type UntypedDefinition = Definition<(), UntypedExpr, ()>;
|
||||||
pub module: Option<String>,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TypedDefinition = Definition<Rc<Type>, TypedExpr, String, ()>;
|
|
||||||
pub type UntypedDefinition = Definition<(), UntypedExpr, (), DefinitionIdentifier>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Definition<T, Expr, PackageName, Ann> {
|
pub enum Definition<T, Expr, PackageName> {
|
||||||
Fn(Function<T, Expr, Arg<T>>),
|
Fn(Function<T, Expr, Arg<T>>),
|
||||||
|
|
||||||
TypeAlias(TypeAlias<T>),
|
TypeAlias(TypeAlias<T>),
|
||||||
|
@ -388,12 +386,12 @@ pub enum Definition<T, Expr, PackageName, Ann> {
|
||||||
|
|
||||||
ModuleConstant(ModuleConstant<T>),
|
ModuleConstant(ModuleConstant<T>),
|
||||||
|
|
||||||
Test(Function<T, Expr, ArgVia<T, Ann>>),
|
Test(Function<T, Expr, ArgVia<T, Expr>>),
|
||||||
|
|
||||||
Validator(Validator<T, Expr>),
|
Validator(Validator<T, Expr>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B, C, D> Definition<A, B, C, D> {
|
impl<A, B, C> Definition<A, B, C> {
|
||||||
pub fn location(&self) -> Span {
|
pub fn location(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
Definition::Fn(Function { location, .. })
|
Definition::Fn(Function { location, .. })
|
||||||
|
@ -643,14 +641,14 @@ impl<A> Arg<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TypedArgVia = ArgVia<Rc<Type>, ()>;
|
pub type TypedArgVia = ArgVia<Rc<Type>, TypedExpr>;
|
||||||
pub type UntypedArgVia = ArgVia<(), DefinitionIdentifier>;
|
pub type UntypedArgVia = ArgVia<(), UntypedExpr>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ArgVia<T, Ann> {
|
pub struct ArgVia<T, Expr> {
|
||||||
pub arg_name: ArgName,
|
pub arg_name: ArgName,
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
pub via: Ann,
|
pub via: Expr,
|
||||||
pub tipo: T,
|
pub tipo: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,17 +664,6 @@ impl<T, Ann> From<ArgVia<T, Ann>> for Arg<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TypedArg> for TypedArgVia {
|
|
||||||
fn from(arg: TypedArg) -> TypedArgVia {
|
|
||||||
ArgVia {
|
|
||||||
arg_name: arg.arg_name,
|
|
||||||
tipo: arg.tipo,
|
|
||||||
location: arg.location,
|
|
||||||
via: (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ArgName {
|
pub enum ArgName {
|
||||||
Discarded {
|
Discarded {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, Arg, ArgName, ArgVia, AssignmentKind, BinOp, ByteArrayFormatPreference,
|
Annotation, Arg, ArgName, ArgVia, AssignmentKind, BinOp, ByteArrayFormatPreference,
|
||||||
CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, DefinitionIdentifier,
|
CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch,
|
||||||
Function, IfBranch, LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor,
|
LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg,
|
||||||
RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp,
|
RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport,
|
||||||
UnqualifiedImport, UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard,
|
UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard, UntypedDefinition,
|
||||||
UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg,
|
UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator,
|
||||||
Use, Validator, CAPTURE_VARIABLE,
|
CAPTURE_VARIABLE,
|
||||||
},
|
},
|
||||||
docvec,
|
docvec,
|
||||||
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||||
|
@ -471,18 +471,16 @@ impl<'comments> Formatter<'comments> {
|
||||||
commented(doc, comments)
|
commented(doc, comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_arg_via<'a, A>(&mut self, arg: &'a ArgVia<A, DefinitionIdentifier>) -> Document<'a> {
|
fn fn_arg_via<'a, A>(&mut self, arg: &'a ArgVia<A, UntypedExpr>) -> Document<'a> {
|
||||||
let comments = self.pop_comments(arg.location.start);
|
let comments = self.pop_comments(arg.location.start);
|
||||||
|
|
||||||
let doc_comments = self.doc_comments(arg.location.start);
|
let doc_comments = self.doc_comments(arg.location.start);
|
||||||
|
|
||||||
let doc = arg.arg_name.to_doc().append(" via ");
|
let doc = arg
|
||||||
|
.arg_name
|
||||||
let doc = match arg.via.module {
|
.to_doc()
|
||||||
Some(ref module) => doc.append(module.to_doc()).append("."),
|
.append(" via ")
|
||||||
None => doc,
|
.append(self.expr(&arg.via, false))
|
||||||
}
|
|
||||||
.append(arg.via.name.to_doc())
|
|
||||||
.group();
|
.group();
|
||||||
|
|
||||||
let doc = doc_comments.append(doc.group()).group();
|
let doc = doc_comments.append(doc.group()).group();
|
||||||
|
|
|
@ -198,7 +198,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
self.finalize(term)
|
self.finalize(term)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_test(&mut self, test_body: &TypedExpr, module_name: &String) -> Program<Name> {
|
pub fn generate_raw(&mut self, test_body: &TypedExpr, module_name: &String) -> Program<Name> {
|
||||||
let mut air_tree = self.build(test_body, module_name, &[]);
|
let mut air_tree = self.build(test_body, module_name, &[]);
|
||||||
|
|
||||||
air_tree = AirTree::no_op(air_tree);
|
air_tree = AirTree::no_op(air_tree);
|
||||||
|
|
|
@ -3,7 +3,12 @@ use chumsky::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast,
|
ast,
|
||||||
expr::UntypedExpr,
|
expr::UntypedExpr,
|
||||||
parser::{error::ParseError, expr, token::Token},
|
parser::{
|
||||||
|
chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain},
|
||||||
|
error::ParseError,
|
||||||
|
expr::{self, var},
|
||||||
|
token::Token,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||||
|
@ -63,20 +68,33 @@ pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.then_ignore(just(Token::Via))
|
.then_ignore(just(Token::Via))
|
||||||
.then(
|
.then(fuzzer())
|
||||||
select! { Token::Name { name } => name }
|
.map_with_span(|(arg_name, via), location| ast::ArgVia {
|
||||||
.then_ignore(just(Token::Dot))
|
|
||||||
.or_not(),
|
|
||||||
)
|
|
||||||
.then(select! { Token::Name { name } => name })
|
|
||||||
.map_with_span(|((arg_name, module), name), location| ast::ArgVia {
|
|
||||||
arg_name,
|
arg_name,
|
||||||
via: ast::DefinitionIdentifier { module, name },
|
via,
|
||||||
tipo: (),
|
tipo: (),
|
||||||
location,
|
location,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fuzzer<'a>() -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
|
||||||
|
recursive(|expression| {
|
||||||
|
let chain = choice((
|
||||||
|
tuple_index(),
|
||||||
|
field_access::parser(),
|
||||||
|
call(expression.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
var()
|
||||||
|
.then(chain.repeated())
|
||||||
|
.foldl(|expr, chain| match chain {
|
||||||
|
Chain::Call(args, span) => expr.call(args, span),
|
||||||
|
Chain::FieldAccess(label, span) => expr.field_access(label, span),
|
||||||
|
Chain::TupleIndex(index, span) => expr.tuple_index(index, span),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::assert_definition;
|
use crate::assert_definition;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, Arg, ArgName, DataType, Definition, Function, Layer, ModuleConstant,
|
Annotation, Arg, ArgName, ArgVia, DataType, Definition, Function, Layer, ModuleConstant,
|
||||||
ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg,
|
ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg,
|
||||||
TypedDefinition, TypedFunction, TypedModule, UntypedArg, UntypedDefinition, UntypedModule,
|
TypedDefinition, TypedFunction, TypedModule, UntypedArg, UntypedDefinition, UntypedModule,
|
||||||
Use, Validator,
|
Use, Validator,
|
||||||
|
@ -383,7 +383,7 @@ fn infer_definition(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let annotation = match f.arguments.first() {
|
let (typed_via, annotation) = match f.arguments.first() {
|
||||||
Some(arg) => {
|
Some(arg) => {
|
||||||
if f.arguments.len() > 1 {
|
if f.arguments.len() > 1 {
|
||||||
return Err(Error::IncorrectTestArity {
|
return Err(Error::IncorrectTestArity {
|
||||||
|
@ -392,12 +392,17 @@ fn infer_definition(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let ValueConstructor { tipo, .. } = ExprTyper::new(environment, lines, tracing)
|
let typed_via =
|
||||||
.infer_value_constructor(&arg.via.module, &arg.via.name, &arg.location)?;
|
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
|
||||||
|
|
||||||
Ok(Some(annotate_fuzzer(&tipo, &arg.location)?))
|
let tipo = typed_via.tipo();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Some(typed_via),
|
||||||
|
Some(annotate_fuzzer(&tipo, &arg.location)?),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok((None, None)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let typed_f = infer_function(
|
let typed_f = infer_function(
|
||||||
|
@ -439,13 +444,25 @@ fn infer_definition(
|
||||||
location: typed_f.location,
|
location: typed_f.location,
|
||||||
name: typed_f.name,
|
name: typed_f.name,
|
||||||
public: typed_f.public,
|
public: typed_f.public,
|
||||||
arguments: match annotation {
|
arguments: match typed_via {
|
||||||
Some(_) => vec![typed_f
|
Some(via) => {
|
||||||
|
let Arg {
|
||||||
|
arg_name,
|
||||||
|
location,
|
||||||
|
tipo,
|
||||||
|
..
|
||||||
|
} = typed_f
|
||||||
.arguments
|
.arguments
|
||||||
.first()
|
.first()
|
||||||
.expect("has exactly one argument")
|
.expect("has exactly one argument")
|
||||||
.to_owned()
|
.to_owned();
|
||||||
.into()],
|
vec![ArgVia {
|
||||||
|
arg_name,
|
||||||
|
location,
|
||||||
|
tipo,
|
||||||
|
via,
|
||||||
|
}]
|
||||||
|
}
|
||||||
None => vec![],
|
None => vec![],
|
||||||
},
|
},
|
||||||
return_annotation: typed_f.return_annotation,
|
return_annotation: typed_f.return_annotation,
|
||||||
|
|
|
@ -43,6 +43,7 @@ zip = "0.6.4"
|
||||||
|
|
||||||
aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" }
|
aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" }
|
||||||
uplc = { path = '../uplc', version = "1.0.24-alpha" }
|
uplc = { path = '../uplc', version = "1.0.24-alpha" }
|
||||||
|
num-bigint = "0.4.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
blst = "0.3.11"
|
blst = "0.3.11"
|
||||||
|
|
|
@ -22,10 +22,13 @@ use crate::blueprint::{
|
||||||
Blueprint,
|
Blueprint,
|
||||||
};
|
};
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction, Validator},
|
ast::{
|
||||||
|
Definition, Function, ModuleKind, Span, Tracing, TypedDataType, TypedFunction, Validator,
|
||||||
|
},
|
||||||
builtins,
|
builtins,
|
||||||
gen_uplc::builder::{DataTypeKey, FunctionAccessKey},
|
expr::TypedExpr,
|
||||||
tipo::TypeInfo,
|
gen_uplc::builder::{cast_validator_args, DataTypeKey, FunctionAccessKey},
|
||||||
|
tipo::{Type, TypeInfo},
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -38,16 +41,17 @@ use pallas::ledger::{
|
||||||
traverse::ComputeHash,
|
traverse::ComputeHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
use script::{EvalHint, EvalInfo, Script};
|
use script::{EvalHint, EvalInfo, PropertyTest, Test};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
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 uplc::{
|
use uplc::{
|
||||||
ast::{DeBruijn, Name, Program, Term},
|
ast::{DeBruijn, Name, NamedDeBruijn, Program, Term},
|
||||||
machine::cost_model::ExBudget,
|
machine::cost_model::ExBudget,
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
|
@ -319,7 +323,7 @@ where
|
||||||
self.event_listener.handle_event(Event::RunningTests);
|
self.event_listener.handle_event(Event::RunningTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = self.eval_scripts(tests);
|
let results = self.run_tests(tests.iter().collect());
|
||||||
|
|
||||||
let errors: Vec<Error> = results
|
let errors: Vec<Error> = results
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -328,14 +332,13 @@ where
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Error::TestFailure {
|
Some(Error::TestFailure {
|
||||||
name: e.script.name.clone(),
|
name: e.test.name().to_string(),
|
||||||
path: e.script.input_path.clone(),
|
path: e.test.input_path().to_path_buf(),
|
||||||
evaluation_hint: e
|
evaluation_hint: e
|
||||||
.script
|
.test
|
||||||
.evaluation_hint
|
.evaluation_hint()
|
||||||
.as_ref()
|
|
||||||
.map(|hint| hint.to_string()),
|
.map(|hint| hint.to_string()),
|
||||||
src: e.script.program.to_pretty(),
|
src: e.test.program().to_pretty(),
|
||||||
verbose,
|
verbose,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -684,7 +687,7 @@ where
|
||||||
match_tests: Option<Vec<String>>,
|
match_tests: Option<Vec<String>>,
|
||||||
exact_match: bool,
|
exact_match: bool,
|
||||||
tracing: Tracing,
|
tracing: Tracing,
|
||||||
) -> Result<Vec<Script>, Error> {
|
) -> Result<Vec<Test>, Error> {
|
||||||
let mut scripts = Vec::new();
|
let mut scripts = Vec::new();
|
||||||
let mut testable_validators = Vec::new();
|
let mut testable_validators = Vec::new();
|
||||||
|
|
||||||
|
@ -802,6 +805,7 @@ where
|
||||||
name,
|
name,
|
||||||
body,
|
body,
|
||||||
can_error,
|
can_error,
|
||||||
|
arguments,
|
||||||
..
|
..
|
||||||
} = func_def;
|
} = func_def;
|
||||||
|
|
||||||
|
@ -815,13 +819,13 @@ where
|
||||||
let evaluation_hint = func_def.test_hint().map(|(bin_op, left_src, right_src)| {
|
let evaluation_hint = func_def.test_hint().map(|(bin_op, left_src, right_src)| {
|
||||||
let left = generator
|
let left = generator
|
||||||
.clone()
|
.clone()
|
||||||
.generate_test(&left_src, &module_name)
|
.generate_raw(&left_src, &module_name)
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let right = generator
|
let right = generator
|
||||||
.clone()
|
.clone()
|
||||||
.generate_test(&right_src, &module_name)
|
.generate_raw(&right_src, &module_name)
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -833,9 +837,10 @@ where
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let program = generator.generate_test(body, &module_name);
|
if arguments.is_empty() {
|
||||||
|
let program = generator.generate_raw(body, &module_name);
|
||||||
|
|
||||||
let script = Script::new(
|
let test = Test::unit_test(
|
||||||
input_path,
|
input_path,
|
||||||
module_name,
|
module_name,
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
|
@ -844,33 +849,79 @@ where
|
||||||
evaluation_hint,
|
evaluation_hint,
|
||||||
);
|
);
|
||||||
|
|
||||||
programs.push(script);
|
programs.push(test);
|
||||||
|
} else {
|
||||||
|
let parameter = arguments.first().unwrap().to_owned();
|
||||||
|
|
||||||
|
let via = parameter.via.clone();
|
||||||
|
|
||||||
|
let body = TypedExpr::Fn {
|
||||||
|
location: Span::empty(),
|
||||||
|
tipo: Rc::new(Type::Fn {
|
||||||
|
args: vec![parameter.tipo.clone()],
|
||||||
|
ret: body.tipo(),
|
||||||
|
}),
|
||||||
|
is_capture: false,
|
||||||
|
args: vec![parameter.clone().into()],
|
||||||
|
body: Box::new(body.clone()),
|
||||||
|
return_annotation: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let program = generator.clone().generate_raw(&body, &module_name);
|
||||||
|
|
||||||
|
let term = cast_validator_args(program.term, &[parameter.into()]);
|
||||||
|
|
||||||
|
let fuzzer: Program<NamedDeBruijn> = generator
|
||||||
|
.clone()
|
||||||
|
.generate_raw(&via, &module_name)
|
||||||
|
.try_into()
|
||||||
|
.expect("TODO: provide a better error when one is trying to instantiate something that isn't a fuzzer as one");
|
||||||
|
|
||||||
|
let prop = Test::property_test(
|
||||||
|
input_path,
|
||||||
|
module_name,
|
||||||
|
name.to_string(),
|
||||||
|
*can_error,
|
||||||
|
Program { term, ..program }.try_into().unwrap(),
|
||||||
|
fuzzer,
|
||||||
|
);
|
||||||
|
|
||||||
|
programs.push(prop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(programs)
|
Ok(programs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_scripts(&self, scripts: Vec<Script>) -> Vec<EvalInfo> {
|
fn run_tests<'a>(&'a self, tests: Vec<&'a Test>) -> Vec<EvalInfo<'a>> {
|
||||||
use rayon::prelude::*;
|
// FIXME: Find a way to re-introduce parallel testing despite the references (which aren't
|
||||||
|
// sizeable).
|
||||||
|
// We do now hold references to tests because the property tests results are all pointing
|
||||||
|
// to the same test, so we end up copying the same test over and over.
|
||||||
|
//
|
||||||
|
// So we might want to rework the evaluation result to avoid that and keep parallel testing
|
||||||
|
// possible.
|
||||||
|
// use rayon::prelude::*;
|
||||||
|
|
||||||
// TODO: in the future we probably just want to be able to
|
tests
|
||||||
// tell the machine to not explode on budget consumption.
|
.iter()
|
||||||
let initial_budget = ExBudget {
|
.flat_map(|test| match test {
|
||||||
mem: i64::MAX,
|
Test::UnitTest(unit_test) => {
|
||||||
cpu: i64::MAX,
|
let mut result = unit_test.run();
|
||||||
};
|
vec![test.report(&mut result)]
|
||||||
|
}
|
||||||
|
Test::PropertyTest(ref property_test) => {
|
||||||
|
let mut seed = PropertyTest::new_seed(42);
|
||||||
|
|
||||||
scripts
|
let mut results = vec![];
|
||||||
.into_par_iter()
|
for _ in 0..100 {
|
||||||
.map(|script| {
|
let (new_seed, sample) = property_test.sample(seed);
|
||||||
let mut eval_result = script.program.clone().eval(initial_budget);
|
seed = new_seed;
|
||||||
|
let mut result = property_test.run(&sample);
|
||||||
|
results.push(test.report(&mut result));
|
||||||
|
}
|
||||||
|
|
||||||
EvalInfo {
|
results
|
||||||
success: !eval_result.failed(script.can_error),
|
|
||||||
script,
|
|
||||||
spent_budget: eval_result.cost(),
|
|
||||||
logs: eval_result.logs(),
|
|
||||||
output: eval_result.result().ok(),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -1,13 +1,114 @@
|
||||||
use crate::{pretty, ExBudget, Term};
|
use crate::{pretty, ExBudget};
|
||||||
use aiken_lang::ast::BinOp;
|
use aiken_lang::ast::BinOp;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
use uplc::{
|
||||||
|
ast::{Constant, Data, NamedDeBruijn, Program, Term},
|
||||||
|
machine::eval_result::EvalResult,
|
||||||
};
|
};
|
||||||
use uplc::ast::{NamedDeBruijn, Program};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Script {
|
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 input_path: PathBuf,
|
||||||
pub module: String,
|
pub module: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -16,25 +117,55 @@ pub struct Script {
|
||||||
pub evaluation_hint: Option<EvalHint>,
|
pub evaluation_hint: Option<EvalHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Script {}
|
unsafe impl Send for UnitTest {}
|
||||||
|
|
||||||
impl Script {
|
impl UnitTest {
|
||||||
pub fn new(
|
pub fn run(&self) -> EvalResult {
|
||||||
input_path: PathBuf,
|
self.program.clone().eval(ExBudget::max())
|
||||||
module: String,
|
|
||||||
name: String,
|
|
||||||
can_error: bool,
|
|
||||||
program: Program<NamedDeBruijn>,
|
|
||||||
evaluation_hint: Option<EvalHint>,
|
|
||||||
) -> Script {
|
|
||||||
Script {
|
|
||||||
input_path,
|
|
||||||
module,
|
|
||||||
name,
|
|
||||||
program,
|
|
||||||
can_error,
|
|
||||||
evaluation_hint,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,12 +242,10 @@ impl Display for EvalHint {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvalInfo {
|
pub struct EvalInfo<'a> {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
pub script: Script,
|
|
||||||
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: &'a Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for EvalInfo {}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub trait EventListener {
|
||||||
fn handle_event(&self, _event: Event) {}
|
fn handle_event(&self, _event: Event) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event<'a> {
|
||||||
StartingCompilation {
|
StartingCompilation {
|
||||||
name: String,
|
name: String,
|
||||||
version: String,
|
version: String,
|
||||||
|
@ -36,11 +36,11 @@ pub enum Event {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
EvaluatingFunction {
|
EvaluatingFunction {
|
||||||
results: Vec<EvalInfo>,
|
results: Vec<EvalInfo<'a>>,
|
||||||
},
|
},
|
||||||
RunningTests,
|
RunningTests,
|
||||||
FinishedTests {
|
FinishedTests {
|
||||||
tests: Vec<EvalInfo>,
|
tests: Vec<EvalInfo<'a>>,
|
||||||
},
|
},
|
||||||
WaitingForBuildDirLock,
|
WaitingForBuildDirLock,
|
||||||
ResolvingPackages {
|
ResolvingPackages {
|
||||||
|
@ -272,7 +272,7 @@ impl EventListener for Terminal {
|
||||||
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
||||||
let EvalInfo {
|
let EvalInfo {
|
||||||
success,
|
success,
|
||||||
script,
|
test,
|
||||||
spent_budget,
|
spent_budget,
|
||||||
logs,
|
logs,
|
||||||
..
|
..
|
||||||
|
@ -303,7 +303,7 @@ fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool)
|
||||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
||||||
.if_supports_color(Stderr, |s| s.cyan())
|
.if_supports_color(Stderr, |s| s.cyan())
|
||||||
.to_string()),
|
.to_string()),
|
||||||
module = pretty::style_if(styled, script.name.clone(), |s| s
|
module = pretty::style_if(styled, test.name().to_string(), |s| s
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
.to_string()),
|
.to_string()),
|
||||||
);
|
);
|
||||||
|
@ -363,7 +363,7 @@ fn fmt_test_summary(tests: &[&EvalInfo], styled: bool) -> String {
|
||||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
|
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
|
||||||
let EvalInfo {
|
let EvalInfo {
|
||||||
output,
|
output,
|
||||||
script,
|
test,
|
||||||
spent_budget,
|
spent_budget,
|
||||||
..
|
..
|
||||||
} = eval_info;
|
} = eval_info;
|
||||||
|
@ -372,8 +372,8 @@ fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
||||||
script.module.if_supports_color(stream, |s| s.blue()),
|
test.module().if_supports_color(stream, |s| s.blue()),
|
||||||
script.name.if_supports_color(stream, |s| s.bright_blue()),
|
test.name().if_supports_color(stream, |s| s.bright_blue()),
|
||||||
pretty::pad_left(mem.to_string(), max_mem, " "),
|
pretty::pad_left(mem.to_string(), max_mem, " "),
|
||||||
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
||||||
output
|
output
|
||||||
|
@ -383,10 +383,12 @@ fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
|
fn group_by_module<'a>(infos: &'a Vec<EvalInfo<'a>>) -> BTreeMap<String, Vec<&'a EvalInfo<'a>>> {
|
||||||
let mut modules = BTreeMap::new();
|
let mut modules = BTreeMap::new();
|
||||||
for eval_info in infos {
|
for eval_info in infos {
|
||||||
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
|
let xs: &mut Vec<&EvalInfo> = modules
|
||||||
|
.entry(eval_info.test.module().to_string())
|
||||||
|
.or_default();
|
||||||
xs.push(eval_info);
|
xs.push(eval_info);
|
||||||
}
|
}
|
||||||
modules
|
modules
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn assert_uplc(source_code: &str, expected: Term<Name>, should_fail: bool) {
|
||||||
|
|
||||||
match &script.2 {
|
match &script.2 {
|
||||||
TestType::Func(Function { body: func, .. }) => {
|
TestType::Func(Function { body: func, .. }) => {
|
||||||
let program = generator.generate_test(func, &script.1);
|
let program = generator.generate_raw(func, &script.1);
|
||||||
|
|
||||||
let debruijn_program: Program<DeBruijn> = program.try_into().unwrap();
|
let debruijn_program: Program<DeBruijn> = program.try_into().unwrap();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::ast::{NamedDeBruijn, Term, Type};
|
||||||
|
|
||||||
use super::{ExBudget, Value};
|
use super::{ExBudget, Value};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
|
#[derive(Debug, Clone, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
|
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
|
||||||
OutOfExError(ExBudget),
|
OutOfExError(ExBudget),
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::ast::{Constant, NamedDeBruijn, Term};
|
||||||
|
|
||||||
use super::{cost_model::ExBudget, Error};
|
use super::{cost_model::ExBudget, Error};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct EvalResult {
|
pub struct EvalResult {
|
||||||
result: Result<Term<NamedDeBruijn>, Error>,
|
result: Result<Term<NamedDeBruijn>, Error>,
|
||||||
remaining_budget: ExBudget,
|
remaining_budget: ExBudget,
|
||||||
|
@ -43,7 +44,7 @@ impl EvalResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result(self) -> Result<Term<NamedDeBruijn>, Error> {
|
pub fn result(&self) -> Result<Term<NamedDeBruijn>, Error> {
|
||||||
self.result
|
self.result.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
name = "thing/thing"
|
name = "aiken-lang/acceptance_test_093"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
license = "Apache-2.0"
|
description = ""
|
||||||
description = "Aiken contracts for project 'thing/thing'"
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
pub fn wow(a: Void) -> Int {
|
type PRNG {
|
||||||
when Some(a) is {
|
seed: Int,
|
||||||
Some(Void) -> 42
|
size: Int,
|
||||||
None -> 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test wow_1() {
|
fn any_int(prng: PRNG) {
|
||||||
wow(Void) == 42
|
(prng, prng.seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
test prop_test_foo(n via any_int) {
|
||||||
|
n > 0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue