Merge pull request #160 from txpipe/pairing_testing

Aiken Testing Framework
This commit is contained in:
Matthias Benkort 2022-12-09 16:03:59 +01:00 committed by GitHub
commit 0449c818e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 504 additions and 37 deletions

View File

@ -2,6 +2,30 @@
## [next] - 2022-MM-DD ## [next] - 2022-MM-DD
### Added
- **aiken-lang**: integrated unit tests
Aiken now supports writing unit tests directly in source files using the new
`test` keyword. Tests are functions with no arguments that are implicitly typed
to `bool`. For example:
```gleam
test foo () {
1 + 1 == 2
}
```
- **aiken**: new `--skip-tests` flag for the `check` command
### Changed
- **aiken**: `check` now also runs and reports on any `test` found in the project
### Removed
N/A
## [v0.0.26] - 2022-11-23 ## [v0.0.26] - 2022-11-23
### Added ### Added

1
Cargo.lock generated
View File

@ -59,6 +59,7 @@ dependencies = [
"ignore", "ignore",
"indoc", "indoc",
"miette", "miette",
"owo-colors",
"pallas-addresses", "pallas-addresses",
"pallas-codec", "pallas-codec",
"pallas-crypto", "pallas-crypto",

View File

@ -15,6 +15,7 @@ hex = "0.4.3"
ignore = "0.4.18" ignore = "0.4.18"
indoc = "1.0" indoc = "1.0"
miette = { version = "5.3.0", features = ["fancy"] } miette = { version = "5.3.0", features = ["fancy"] }
owo-colors = "3.5.0"
pallas-addresses = "0.14.0" pallas-addresses = "0.14.0"
pallas-codec = "0.14.0" pallas-codec = "0.14.0"
pallas-crypto = "0.14.0" pallas-crypto = "0.14.0"

View File

@ -6,8 +6,17 @@ pub struct Args {
/// Path to project /// Path to project
#[clap(short, long)] #[clap(short, long)]
directory: Option<PathBuf>, directory: Option<PathBuf>,
/// Skip tests; run only the type-checker
#[clap(short, long)]
skip_tests: bool,
} }
pub fn exec(Args { directory }: Args) -> miette::Result<()> { pub fn exec(
crate::with_project(directory, |p| p.check()) Args {
directory,
skip_tests,
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, |p| p.check(skip_tests))
} }

View File

@ -61,7 +61,9 @@ pub fn exec(
program = program.apply_term(&term); program = program.apply_term(&term);
} }
let (term, cost, logs) = program.eval(); let budget = ExBudget::default();
let (term, cost, logs) = program.eval(budget);
match term { match term {
Ok(term) => { Ok(term) => {
@ -74,8 +76,6 @@ pub fn exec(
} }
} }
let budget = ExBudget::default();
println!( println!(
"\nCosts\n-----\ncpu: {}\nmemory: {}", "\nCosts\n-----\ncpu: {}\nmemory: {}",
budget.cpu - cost.cpu, budget.cpu - cost.cpu,

View File

@ -1,13 +1,19 @@
pub mod cmd; use std::{env, path::PathBuf};
use aiken_project::{config::Config, Project}; use aiken_project::{
config::Config,
telemetry::{self, TestInfo},
Project,
};
use miette::IntoDiagnostic; use miette::IntoDiagnostic;
use std::env; use owo_colors::OwoColorize;
use std::path::PathBuf; use uplc::machine::cost_model::ExBudget;
pub mod cmd;
pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()> pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()>
where where
A: FnMut(&mut Project) -> Result<(), aiken_project::error::Error>, A: FnMut(&mut Project<Terminal>) -> Result<(), aiken_project::error::Error>,
{ {
let project_path = if let Some(d) = directory { let project_path = if let Some(d) = directory {
d d
@ -17,7 +23,7 @@ where
let config = Config::load(project_path.clone()).into_diagnostic()?; let config = Config::load(project_path.clone()).into_diagnostic()?;
let mut project = Project::new(config, project_path); let mut project = Project::new(config, project_path, Terminal::default());
let build_result = action(&mut project); let build_result = action(&mut project);
@ -30,10 +36,129 @@ where
if let Err(err) = build_result { if let Err(err) = build_result {
err.report(); err.report();
miette::bail!("failed: {} error(s), {warning_count} warning(s)", err.len(),); miette::bail!("Failed: {} error(s), {warning_count} warning(s)", err.len(),);
}; };
println!("\nfinished with {warning_count} warning(s)\n"); println!("\nFinished with {warning_count} warning(s)\n");
Ok(()) Ok(())
} }
#[derive(Debug, Default, Clone, Copy)]
pub struct Terminal;
impl telemetry::EventListener for Terminal {
fn handle_event(&self, event: telemetry::Event) {
match event {
telemetry::Event::StartingCompilation {
name,
version,
root,
} => {
println!(
"{} {} {} ({})",
"Compiling".bold().purple(),
name.bold(),
version,
root.to_str().unwrap_or("").bright_blue()
);
}
telemetry::Event::ParsingProjectFiles => {
println!("{}", "...Parsing project files".bold().purple());
}
telemetry::Event::TypeChecking => {
println!("{}", "...Type-checking project".bold().purple());
}
telemetry::Event::GeneratingUPLC { output_path } => {
println!(
"{} in {}",
"...Generating Untyped Plutus Core".bold().purple(),
output_path.to_str().unwrap_or("").bright_blue()
);
}
telemetry::Event::RunningTests => {
println!("{}\n", "...Running tests".bold().purple());
}
telemetry::Event::FinishedTests { tests } => {
let (max_mem, max_cpu) = tests.iter().fold(
(0, 0),
|(max_mem, max_cpu), TestInfo { spent_budget, .. }| {
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
(spent_budget.mem, spent_budget.cpu)
} else if spent_budget.mem > max_mem {
(spent_budget.mem, max_cpu)
} else if spent_budget.cpu > max_cpu {
(max_mem, spent_budget.cpu)
} else {
(max_mem, max_cpu)
}
},
);
let max_mem = max_mem.to_string().len() as i32;
let max_cpu = max_cpu.to_string().len() as i32;
for test_info in &tests {
println!("{}", fmt_test(test_info, max_mem, max_cpu))
}
let (n_passed, n_failed) =
tests
.iter()
.fold((0, 0), |(n_passed, n_failed), test_info| {
if test_info.is_passing {
(n_passed + 1, n_failed)
} else {
(n_passed, n_failed + 1)
}
});
println!(
"{}",
format!(
"\n Summary: {} test(s), {}; {}.",
tests.len(),
format!("{} passed", n_passed).bright_green(),
format!("{} failed", n_failed).bright_red()
)
.bold()
)
}
}
}
}
fn fmt_test(test_info: &TestInfo, max_mem: i32, max_cpu: i32) -> String {
let TestInfo {
is_passing,
test,
spent_budget,
} = test_info;
let ExBudget { mem, cpu } = spent_budget;
format!(
" [{}] [mem: {}, cpu: {}] {}::{}",
if *is_passing {
"PASS".bold().green().to_string()
} else {
"FAIL".bold().red().to_string()
},
pad_left(mem.to_string(), max_mem, " "),
pad_left(cpu.to_string(), max_cpu, " "),
test.module.blue(),
test.name.bright_blue()
)
}
fn pad_left(mut text: String, n: i32, delimiter: &str) -> String {
let diff = n - text.len() as i32;
if diff.is_positive() {
for _ in 0..diff {
text.insert_str(0, delimiter);
}
}
text
}

View File

@ -138,6 +138,8 @@ pub enum Definition<T, Expr, ConstantRecordTag, PackageName> {
Use(Use<PackageName>), Use(Use<PackageName>),
ModuleConstant(ModuleConstant<T, ConstantRecordTag>), ModuleConstant(ModuleConstant<T, ConstantRecordTag>),
Test(Function<T, Expr>),
} }
impl<A, B, C, E> Definition<A, B, C, E> { impl<A, B, C, E> Definition<A, B, C, E> {
@ -147,7 +149,8 @@ impl<A, B, C, E> Definition<A, B, C, E> {
| Definition::Use(Use { location, .. }) | Definition::Use(Use { location, .. })
| Definition::TypeAlias(TypeAlias { location, .. }) | Definition::TypeAlias(TypeAlias { location, .. })
| Definition::DataType(DataType { location, .. }) | Definition::DataType(DataType { location, .. })
| Definition::ModuleConstant(ModuleConstant { location, .. }) => *location, | Definition::ModuleConstant(ModuleConstant { location, .. })
| Definition::Test(Function { location, .. }) => *location,
} }
} }
@ -157,7 +160,8 @@ impl<A, B, C, E> Definition<A, B, C, E> {
Definition::Fn(Function { doc, .. }) Definition::Fn(Function { doc, .. })
| Definition::TypeAlias(TypeAlias { doc, .. }) | Definition::TypeAlias(TypeAlias { doc, .. })
| Definition::DataType(DataType { doc, .. }) | Definition::DataType(DataType { doc, .. })
| Definition::ModuleConstant(ModuleConstant { doc, .. }) => { | Definition::ModuleConstant(ModuleConstant { doc, .. })
| Definition::Test(Function { doc, .. }) => {
let _ = std::mem::replace(doc, Some(new_doc)); let _ = std::mem::replace(doc, Some(new_doc));
} }
} }

View File

@ -215,7 +215,23 @@ impl<'comments> Formatter<'comments> {
return_annotation, return_annotation,
end_position, end_position,
.. ..
}) => self.definition_fn(public, name, args, return_annotation, body, *end_position), }) => self.definition_fn(
public,
"fn",
name,
args,
return_annotation,
body,
*end_position,
),
Definition::Test(Function {
name,
arguments: args,
body,
end_position,
..
}) => self.definition_fn(&false, "test", name, args, &None, body, *end_position),
Definition::TypeAlias(TypeAlias { Definition::TypeAlias(TypeAlias {
alias, alias,
@ -493,9 +509,11 @@ impl<'comments> Formatter<'comments> {
commented(doc, comments) commented(doc, comments)
} }
#[allow(clippy::too_many_arguments)]
fn definition_fn<'a>( fn definition_fn<'a>(
&mut self, &mut self,
public: &'a bool, public: &'a bool,
keyword: &'a str,
name: &'a str, name: &'a str,
args: &'a [UntypedArg], args: &'a [UntypedArg],
return_annotation: &'a Option<Annotation>, return_annotation: &'a Option<Annotation>,
@ -504,7 +522,8 @@ impl<'comments> Formatter<'comments> {
) -> Document<'a> { ) -> Document<'a> {
// Fn name and args // Fn name and args
let head = pub_(*public) let head = pub_(*public)
.append("fn ") .append(keyword)
.append(" ")
.append(name) .append(name)
.append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false)))); .append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false))));

View File

@ -74,6 +74,7 @@ fn module_parser() -> impl Parser<Token, Vec<UntypedDefinition>, Error = ParseEr
data_parser(), data_parser(),
type_alias_parser(), type_alias_parser(),
fn_parser(), fn_parser(),
test_parser(),
constant_parser(), constant_parser(),
)) ))
.repeated() .repeated()
@ -266,6 +267,36 @@ pub fn fn_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseEr
) )
} }
pub fn test_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
just(Token::Test)
.ignore_then(select! {Token::Name {name} => name})
.then_ignore(just(Token::LeftParen))
.then_ignore(just(Token::RightParen))
.map_with_span(|name, span| (name, span))
.then(
expr_seq_parser()
.or_not()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
)
.map_with_span(|((name, span_end), body), span| {
ast::UntypedDefinition::Test(ast::Function {
arguments: vec![],
body: body.unwrap_or(expr::UntypedExpr::Todo {
kind: TodoKind::EmptyFunction,
location: span,
label: None,
}),
doc: None,
location: span_end,
end_position: span.end - 1,
name,
public: true,
return_annotation: None,
return_type: (),
})
})
}
fn constant_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> { fn constant_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
pub_parser() pub_parser()
.or_not() .or_not()

View File

@ -67,6 +67,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
"check" => Token::Assert, "check" => Token::Assert,
"const" => Token::Const, "const" => Token::Const,
"fn" => Token::Fn, "fn" => Token::Fn,
"test" => Token::Test,
"if" => Token::If, "if" => Token::If,
"else" => Token::Else, "else" => Token::Else,
"is" => Token::Is, "is" => Token::Is,

View File

@ -68,6 +68,7 @@ pub enum Token {
Opaque, Opaque,
Pub, Pub,
Use, Use,
Test,
Todo, Todo,
Trace, Trace,
Type, Type,
@ -145,6 +146,7 @@ impl fmt::Display for Token {
Token::Todo => "todo", Token::Todo => "todo",
Token::Trace => "try", Token::Trace => "try",
Token::Type => "type", Token::Type => "type",
Token::Test => "test",
}; };
write!(f, "\"{}\"", s) write!(f, "\"{}\"", s)
} }

View File

@ -255,6 +255,7 @@ impl<'a> Environment<'a> {
definition @ (Definition::TypeAlias { .. } definition @ (Definition::TypeAlias { .. }
| Definition::DataType { .. } | Definition::DataType { .. }
| Definition::Use { .. } | Definition::Use { .. }
| Definition::Test { .. }
| Definition::ModuleConstant { .. }) => definition, | Definition::ModuleConstant { .. }) => definition,
} }
} }
@ -911,7 +912,10 @@ impl<'a> Environment<'a> {
} }
} }
Definition::Fn { .. } | Definition::Use { .. } | Definition::ModuleConstant { .. } => {} Definition::Fn { .. }
| Definition::Test { .. }
| Definition::Use { .. }
| Definition::ModuleConstant { .. } => {}
} }
Ok(()) Ok(())
@ -990,6 +994,24 @@ impl<'a> Environment<'a> {
} }
} }
Definition::Test(Function { name, location, .. }) => {
hydrators.insert(name.clone(), Hydrator::new());
let arg_types = vec![];
let return_type = builtins::bool();
self.insert_variable(
name.clone(),
ValueConstructorVariant::ModuleFn {
name: name.clone(),
field_map: None,
module: module_name.to_owned(),
arity: 0,
location: *location,
builtin: None,
},
function(arg_types, return_type),
);
}
Definition::DataType(DataType { Definition::DataType(DataType {
location, location,
public, public,

View File

@ -6,6 +6,7 @@ use crate::{
RecordConstructorArg, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition, RecordConstructorArg, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition,
UntypedModule, Use, UntypedModule, Use,
}, },
builtins,
builtins::function, builtins::function,
parser::token::Token, parser::token::Token,
IdGenerator, IdGenerator,
@ -66,8 +67,8 @@ impl UntypedModule {
for def in self.definitions().cloned() { for def in self.definitions().cloned() {
match def { match def {
Definition::ModuleConstant { .. } => consts.push(def), Definition::ModuleConstant { .. } => consts.push(def),
Definition::Fn { .. } Definition::Fn { .. }
| Definition::Test { .. }
| Definition::TypeAlias { .. } | Definition::TypeAlias { .. }
| Definition::DataType { .. } | Definition::DataType { .. }
| Definition::Use { .. } => not_consts.push(def), | Definition::Use { .. } => not_consts.push(def),
@ -233,6 +234,17 @@ fn infer_definition(
})) }))
} }
Definition::Test(f) => {
if let Definition::Fn(f) =
infer_definition(Definition::Fn(f), module_name, hydrators, environment)?
{
environment.unify(f.return_type.clone(), builtins::bool(), f.location)?;
Ok(Definition::Test(f))
} else {
unreachable!("test defintion inferred as something else than a function?")
}
}
Definition::TypeAlias(TypeAlias { Definition::TypeAlias(TypeAlias {
doc, doc,
location, location,

View File

@ -293,7 +293,7 @@ impl Diagnostic for Warning {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
match self { match self {
Warning::Type { .. } => Some(Box::new("aiken::typecheck")), Warning::Type { .. } => Some(Box::new("aiken::check")),
} }
} }
} }

View File

@ -9,6 +9,7 @@ pub mod error;
pub mod format; pub mod format;
pub mod module; pub mod module;
pub mod script; pub mod script;
pub mod telemetry;
use aiken_lang::{ use aiken_lang::{
ast::{Definition, Function, ModuleKind, TypedFunction}, ast::{Definition, Function, ModuleKind, TypedFunction},
@ -25,12 +26,17 @@ use pallas::{
use pallas_traverse::ComputeHash; use pallas_traverse::ComputeHash;
use script::Script; use script::Script;
use serde_json::json; use serde_json::json;
use uplc::ast::{DeBruijn, Program}; use telemetry::{EventListener, TestInfo};
use uplc::{
ast::{DeBruijn, Program},
machine::cost_model::ExBudget,
};
use crate::{ use crate::{
config::Config, config::Config,
error::{Error, Warning}, error::{Error, Warning},
module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules}, module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules},
telemetry::Event,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -47,7 +53,10 @@ pub const MINT: &str = "mint";
pub const WITHDRAWL: &str = "withdrawl"; pub const WITHDRAWL: &str = "withdrawl";
pub const VALIDATOR_NAMES: [&str; 4] = [SPEND, CERT, MINT, WITHDRAWL]; pub const VALIDATOR_NAMES: [&str; 4] = [SPEND, CERT, MINT, WITHDRAWL];
pub struct Project { pub struct Project<T>
where
T: EventListener,
{
config: Config, config: Config,
defined_modules: HashMap<String, PathBuf>, defined_modules: HashMap<String, PathBuf>,
id_gen: IdGenerator, id_gen: IdGenerator,
@ -55,10 +64,14 @@ pub struct Project {
root: PathBuf, root: PathBuf,
sources: Vec<Source>, sources: Vec<Source>,
pub warnings: Vec<Warning>, pub warnings: Vec<Warning>,
event_listener: T,
} }
impl Project { impl<T> Project<T>
pub fn new(config: Config, root: PathBuf) -> Project { where
T: EventListener,
{
pub fn new(config: Config, root: PathBuf, event_listener: T) -> Project<T> {
let id_gen = IdGenerator::new(); let id_gen = IdGenerator::new();
let mut module_types = HashMap::new(); let mut module_types = HashMap::new();
@ -74,34 +87,60 @@ impl Project {
root, root,
sources: vec![], sources: vec![],
warnings: vec![], warnings: vec![],
event_listener,
} }
} }
pub fn build(&mut self, uplc: bool) -> Result<(), Error> { pub fn build(&mut self, uplc: bool) -> Result<(), Error> {
self.compile(true, uplc) self.compile(true, uplc, false)
} }
pub fn check(&mut self) -> Result<(), Error> { pub fn check(&mut self, skip_tests: bool) -> Result<(), Error> {
self.compile(false, false) self.compile(false, false, !skip_tests)
} }
pub fn compile(&mut self, uplc_gen: bool, uplc_dump: bool) -> Result<(), Error> { pub fn compile(
&mut self,
uplc_gen: bool,
uplc_dump: bool,
run_tests: bool,
) -> Result<(), Error> {
self.event_listener
.handle_event(Event::StartingCompilation {
root: self.root.clone(),
name: self.config.name.clone(),
version: self.config.version.clone(),
});
self.event_listener.handle_event(Event::ParsingProjectFiles);
self.read_source_files()?; self.read_source_files()?;
let parsed_modules = self.parse_sources()?; let parsed_modules = self.parse_sources()?;
let processing_sequence = parsed_modules.sequence()?; let processing_sequence = parsed_modules.sequence()?;
self.event_listener.handle_event(Event::TypeChecking);
let mut checked_modules = self.type_check(parsed_modules, processing_sequence)?; let mut checked_modules = self.type_check(parsed_modules, processing_sequence)?;
let validators = self.validate_validators(&mut checked_modules)?; let validators = self.validate_validators(&mut checked_modules)?;
// TODO: In principle, uplc_gen and run_tests can't be true together. We probably want to
// model the options differently to make it obvious at the type-level.
if uplc_gen { if uplc_gen {
self.event_listener.handle_event(Event::GeneratingUPLC {
output_path: self.output_path(),
});
let programs = self.code_gen(validators, &checked_modules)?; let programs = self.code_gen(validators, &checked_modules)?;
self.write_build_outputs(programs, uplc_dump)?; self.write_build_outputs(programs, uplc_dump)?;
} }
if run_tests {
let tests = self.test_gen(&checked_modules)?;
self.run_tests(tests);
}
Ok(()) Ok(())
} }
@ -336,6 +375,7 @@ impl Project {
func, func,
); );
} }
Definition::Test(_) => {}
Definition::TypeAlias(ta) => { Definition::TypeAlias(ta) => {
type_aliases.insert((module.name.clone(), ta.alias.clone()), ta); type_aliases.insert((module.name.clone(), ta.alias.clone()), ta);
} }
@ -385,11 +425,135 @@ impl Project {
Ok(programs) Ok(programs)
} }
fn write_build_outputs(&self, programs: Vec<Script>, uplc_dump: bool) -> Result<(), Error> { // TODO: revisit ownership and lifetimes of data in this function
let assets = self.root.join("assets"); fn test_gen(&mut self, checked_modules: &CheckedModules) -> Result<Vec<Script>, Error> {
let mut programs = Vec::new();
let mut functions = HashMap::new();
let mut type_aliases = HashMap::new();
let mut data_types = HashMap::new();
let mut imports = HashMap::new();
let mut constants = HashMap::new();
// let mut indices_to_remove = Vec::new();
let mut tests = Vec::new();
for module in checked_modules.values() {
for (_index, def) in module.ast.definitions().enumerate() {
match def {
Definition::Fn(func) => {
functions.insert(
FunctionAccessKey {
module_name: module.name.clone(),
function_name: func.name.clone(),
},
func,
);
}
Definition::Test(func) => {
tests.push((module.name.clone(), func));
// indices_to_remove.push(index);
}
Definition::TypeAlias(ta) => {
type_aliases.insert((module.name.clone(), ta.alias.clone()), ta);
}
Definition::DataType(dt) => {
data_types.insert(
DataTypeKey {
module_name: module.name.clone(),
defined_type: dt.name.clone(),
},
dt,
);
}
Definition::Use(import) => {
imports.insert((module.name.clone(), import.module.join("/")), import);
}
Definition::ModuleConstant(mc) => {
constants.insert((module.name.clone(), mc.name.clone()), mc);
}
}
}
// for index in indices_to_remove.drain(0..) {
// module.ast.definitions.remove(index);
// }
}
for (module_name, func_def) in tests {
let Function {
arguments,
name,
body,
..
} = func_def;
let mut generator = CodeGenerator::new(
&functions,
// &type_aliases,
&data_types,
// &imports,
// &constants,
&self.module_types,
);
let program = generator.generate(body.clone(), arguments.clone());
let script = Script::new(module_name, name.to_string(), program.try_into().unwrap());
programs.push(script);
}
Ok(programs)
}
fn run_tests(&self, tests: Vec<Script>) {
// TODO: in the future we probably just want to be able to
// tell the machine to not explode on budget consumption.
let initial_budget = ExBudget {
mem: i64::MAX,
cpu: i64::MAX,
};
if !tests.is_empty() {
self.event_listener.handle_event(Event::RunningTests);
}
let mut results = Vec::new();
for test in tests {
match test.program.eval(initial_budget) {
(Ok(..), remaining_budget, _) => {
let test_info = TestInfo {
is_passing: true,
test,
spent_budget: initial_budget - remaining_budget,
};
results.push(test_info);
}
(Err(_), remaining_budget, _) => {
let test_info = TestInfo {
is_passing: false,
test,
spent_budget: initial_budget - remaining_budget,
};
results.push(test_info);
}
}
}
self.event_listener
.handle_event(Event::FinishedTests { tests: results });
}
fn output_path(&self) -> PathBuf {
self.root.join("assets")
}
fn write_build_outputs(&self, programs: Vec<Script>, uplc_dump: bool) -> Result<(), Error> {
for script in programs { for script in programs {
let script_output_dir = assets.join(script.module).join(script.name); let script_output_dir = self.output_path().join(script.module).join(script.name);
fs::create_dir_all(&script_output_dir)?; fs::create_dir_all(&script_output_dir)?;

View File

@ -0,0 +1,30 @@
use crate::script::Script;
use std::path::PathBuf;
use uplc::machine::cost_model::ExBudget;
pub trait EventListener: std::fmt::Debug {
fn handle_event(&self, event: Event);
}
pub enum Event {
StartingCompilation {
name: String,
version: String,
root: PathBuf,
},
ParsingProjectFiles,
TypeChecking,
GeneratingUPLC {
output_path: PathBuf,
},
RunningTests,
FinishedTests {
tests: Vec<TestInfo>,
},
}
pub struct TestInfo {
pub is_passing: bool,
pub test: Script,
pub spent_budget: ExBudget,
}

View File

@ -495,6 +495,7 @@ impl From<Term<FakeNamedDeBruijn>> for Term<NamedDeBruijn> {
impl Program<NamedDeBruijn> { impl Program<NamedDeBruijn> {
pub fn eval( pub fn eval(
&self, &self,
initial_budget: ExBudget,
) -> ( ) -> (
Result<Term<NamedDeBruijn>, crate::machine::Error>, Result<Term<NamedDeBruijn>, crate::machine::Error>,
ExBudget, ExBudget,
@ -503,7 +504,7 @@ impl Program<NamedDeBruijn> {
let mut machine = Machine::new( let mut machine = Machine::new(
Language::PlutusV2, Language::PlutusV2,
CostModel::default(), CostModel::default(),
ExBudget::default(), initial_budget,
200, 200,
); );
@ -558,6 +559,7 @@ impl Program<NamedDeBruijn> {
impl Program<DeBruijn> { impl Program<DeBruijn> {
pub fn eval( pub fn eval(
&self, &self,
initial_budget: ExBudget,
) -> ( ) -> (
Result<Term<NamedDeBruijn>, crate::machine::Error>, Result<Term<NamedDeBruijn>, crate::machine::Error>,
ExBudget, ExBudget,
@ -565,7 +567,7 @@ impl Program<DeBruijn> {
) { ) {
let program: Program<NamedDeBruijn> = self.clone().into(); let program: Program<NamedDeBruijn> = self.clone().into();
program.eval() program.eval(initial_budget)
} }
} }

View File

@ -47,6 +47,17 @@ impl Default for ExBudget {
} }
} }
impl std::ops::Sub for ExBudget {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
ExBudget {
mem: self.mem - rhs.mem,
cpu: self.cpu - rhs.cpu,
}
}
}
#[derive(Default)] #[derive(Default)]
pub struct CostModel { pub struct CostModel {
pub machine_costs: MachineCosts, pub machine_costs: MachineCosts,

View File

@ -786,7 +786,7 @@ pub fn eval_redeemer(
program.eval_as(&Language::PlutusV2, costs, initial_budget) program.eval_as(&Language::PlutusV2, costs, initial_budget)
} else { } else {
program.eval() program.eval(ExBudget::default())
}; };
match result { match result {
@ -889,7 +889,7 @@ pub fn eval_redeemer(
program.eval_as(&Language::PlutusV2, costs, initial_budget) program.eval_as(&Language::PlutusV2, costs, initial_budget)
} else { } else {
program.eval() program.eval(ExBudget::default())
}; };
match result { match result {

View File

@ -0,0 +1,5 @@
use aiken/builtin
test bar() {
builtin.length_of_bytearray(#[2, 2, 3]) == 3
}

View File

@ -1,7 +1,7 @@
use sample use sample
pub fn spend(datum: sample.Datum, rdmr: sample.Redeemer, _ctx: Nil) -> Bool { pub fn spend(datum: sample.Datum, rdmr: sample.Redeemer, _ctx: Nil) -> Bool {
let x = #(datum, #[244]) let _x = #(datum, rdmr, #[244])
let y = [#(#[222], #[222]), #(#[233], #[52])] let y = [#(#[222], #[222]), #(#[233], #[52])]
@ -11,3 +11,7 @@ pub fn spend(datum: sample.Datum, rdmr: sample.Redeemer, _ctx: Nil) -> Bool {
z == #(#[222], #[222]) z == #(#[222], #[222])
} }
test foo() {
1 + 1 == 2
}