Merge pull request #160 from txpipe/pairing_testing
Aiken Testing Framework
This commit is contained in:
commit
0449c818e5
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -2,6 +2,30 @@
|
|||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
|
|
@ -59,6 +59,7 @@ dependencies = [
|
|||
"ignore",
|
||||
"indoc",
|
||||
"miette",
|
||||
"owo-colors",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
|
|
|
@ -15,6 +15,7 @@ hex = "0.4.3"
|
|||
ignore = "0.4.18"
|
||||
indoc = "1.0"
|
||||
miette = { version = "5.3.0", features = ["fancy"] }
|
||||
owo-colors = "3.5.0"
|
||||
pallas-addresses = "0.14.0"
|
||||
pallas-codec = "0.14.0"
|
||||
pallas-crypto = "0.14.0"
|
||||
|
|
|
@ -6,8 +6,17 @@ pub struct Args {
|
|||
/// Path to project
|
||||
#[clap(short, long)]
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Skip tests; run only the type-checker
|
||||
#[clap(short, long)]
|
||||
skip_tests: bool,
|
||||
}
|
||||
|
||||
pub fn exec(Args { directory }: Args) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| p.check())
|
||||
pub fn exec(
|
||||
Args {
|
||||
directory,
|
||||
skip_tests,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| p.check(skip_tests))
|
||||
}
|
||||
|
|
|
@ -61,7 +61,9 @@ pub fn exec(
|
|||
program = program.apply_term(&term);
|
||||
}
|
||||
|
||||
let (term, cost, logs) = program.eval();
|
||||
let budget = ExBudget::default();
|
||||
|
||||
let (term, cost, logs) = program.eval(budget);
|
||||
|
||||
match term {
|
||||
Ok(term) => {
|
||||
|
@ -74,8 +76,6 @@ pub fn exec(
|
|||
}
|
||||
}
|
||||
|
||||
let budget = ExBudget::default();
|
||||
|
||||
println!(
|
||||
"\nCosts\n-----\ncpu: {}\nmemory: {}",
|
||||
budget.cpu - cost.cpu,
|
||||
|
|
|
@ -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 std::env;
|
||||
use std::path::PathBuf;
|
||||
use owo_colors::OwoColorize;
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
pub mod cmd;
|
||||
|
||||
pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()>
|
||||
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 {
|
||||
d
|
||||
|
@ -17,7 +23,7 @@ where
|
|||
|
||||
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);
|
||||
|
||||
|
@ -30,10 +36,129 @@ where
|
|||
if let Err(err) = build_result {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
|
|
@ -138,6 +138,8 @@ pub enum Definition<T, Expr, ConstantRecordTag, PackageName> {
|
|||
Use(Use<PackageName>),
|
||||
|
||||
ModuleConstant(ModuleConstant<T, ConstantRecordTag>),
|
||||
|
||||
Test(Function<T, Expr>),
|
||||
}
|
||||
|
||||
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::TypeAlias(TypeAlias { 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::TypeAlias(TypeAlias { 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,7 +215,23 @@ impl<'comments> Formatter<'comments> {
|
|||
return_annotation,
|
||||
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 {
|
||||
alias,
|
||||
|
@ -493,9 +509,11 @@ impl<'comments> Formatter<'comments> {
|
|||
commented(doc, comments)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn definition_fn<'a>(
|
||||
&mut self,
|
||||
public: &'a bool,
|
||||
keyword: &'a str,
|
||||
name: &'a str,
|
||||
args: &'a [UntypedArg],
|
||||
return_annotation: &'a Option<Annotation>,
|
||||
|
@ -504,7 +522,8 @@ impl<'comments> Formatter<'comments> {
|
|||
) -> Document<'a> {
|
||||
// Fn name and args
|
||||
let head = pub_(*public)
|
||||
.append("fn ")
|
||||
.append(keyword)
|
||||
.append(" ")
|
||||
.append(name)
|
||||
.append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false))));
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ fn module_parser() -> impl Parser<Token, Vec<UntypedDefinition>, Error = ParseEr
|
|||
data_parser(),
|
||||
type_alias_parser(),
|
||||
fn_parser(),
|
||||
test_parser(),
|
||||
constant_parser(),
|
||||
))
|
||||
.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> {
|
||||
pub_parser()
|
||||
.or_not()
|
||||
|
|
|
@ -67,6 +67,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
|
|||
"check" => Token::Assert,
|
||||
"const" => Token::Const,
|
||||
"fn" => Token::Fn,
|
||||
"test" => Token::Test,
|
||||
"if" => Token::If,
|
||||
"else" => Token::Else,
|
||||
"is" => Token::Is,
|
||||
|
|
|
@ -68,6 +68,7 @@ pub enum Token {
|
|||
Opaque,
|
||||
Pub,
|
||||
Use,
|
||||
Test,
|
||||
Todo,
|
||||
Trace,
|
||||
Type,
|
||||
|
@ -145,6 +146,7 @@ impl fmt::Display for Token {
|
|||
Token::Todo => "todo",
|
||||
Token::Trace => "try",
|
||||
Token::Type => "type",
|
||||
Token::Test => "test",
|
||||
};
|
||||
write!(f, "\"{}\"", s)
|
||||
}
|
||||
|
|
|
@ -255,6 +255,7 @@ impl<'a> Environment<'a> {
|
|||
definition @ (Definition::TypeAlias { .. }
|
||||
| Definition::DataType { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::Test { .. }
|
||||
| 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(())
|
||||
|
@ -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 {
|
||||
location,
|
||||
public,
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
RecordConstructorArg, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition,
|
||||
UntypedModule, Use,
|
||||
},
|
||||
builtins,
|
||||
builtins::function,
|
||||
parser::token::Token,
|
||||
IdGenerator,
|
||||
|
@ -66,8 +67,8 @@ impl UntypedModule {
|
|||
for def in self.definitions().cloned() {
|
||||
match def {
|
||||
Definition::ModuleConstant { .. } => consts.push(def),
|
||||
|
||||
Definition::Fn { .. }
|
||||
| Definition::Test { .. }
|
||||
| Definition::TypeAlias { .. }
|
||||
| Definition::DataType { .. }
|
||||
| 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 {
|
||||
doc,
|
||||
location,
|
||||
|
|
|
@ -293,7 +293,7 @@ impl Diagnostic for Warning {
|
|||
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
match self {
|
||||
Warning::Type { .. } => Some(Box::new("aiken::typecheck")),
|
||||
Warning::Type { .. } => Some(Box::new("aiken::check")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod error;
|
|||
pub mod format;
|
||||
pub mod module;
|
||||
pub mod script;
|
||||
pub mod telemetry;
|
||||
|
||||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, TypedFunction},
|
||||
|
@ -25,12 +26,17 @@ use pallas::{
|
|||
use pallas_traverse::ComputeHash;
|
||||
use script::Script;
|
||||
use serde_json::json;
|
||||
use uplc::ast::{DeBruijn, Program};
|
||||
use telemetry::{EventListener, TestInfo};
|
||||
use uplc::{
|
||||
ast::{DeBruijn, Program},
|
||||
machine::cost_model::ExBudget,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
error::{Error, Warning},
|
||||
module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules},
|
||||
telemetry::Event,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -47,7 +53,10 @@ pub const MINT: &str = "mint";
|
|||
pub const WITHDRAWL: &str = "withdrawl";
|
||||
pub const VALIDATOR_NAMES: [&str; 4] = [SPEND, CERT, MINT, WITHDRAWL];
|
||||
|
||||
pub struct Project {
|
||||
pub struct Project<T>
|
||||
where
|
||||
T: EventListener,
|
||||
{
|
||||
config: Config,
|
||||
defined_modules: HashMap<String, PathBuf>,
|
||||
id_gen: IdGenerator,
|
||||
|
@ -55,10 +64,14 @@ pub struct Project {
|
|||
root: PathBuf,
|
||||
sources: Vec<Source>,
|
||||
pub warnings: Vec<Warning>,
|
||||
event_listener: T,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn new(config: Config, root: PathBuf) -> Project {
|
||||
impl<T> Project<T>
|
||||
where
|
||||
T: EventListener,
|
||||
{
|
||||
pub fn new(config: Config, root: PathBuf, event_listener: T) -> Project<T> {
|
||||
let id_gen = IdGenerator::new();
|
||||
|
||||
let mut module_types = HashMap::new();
|
||||
|
@ -74,34 +87,60 @@ impl Project {
|
|||
root,
|
||||
sources: vec![],
|
||||
warnings: vec![],
|
||||
event_listener,
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
self.compile(false, false)
|
||||
pub fn check(&mut self, skip_tests: bool) -> Result<(), Error> {
|
||||
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()?;
|
||||
|
||||
let parsed_modules = self.parse_sources()?;
|
||||
|
||||
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 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 {
|
||||
self.event_listener.handle_event(Event::GeneratingUPLC {
|
||||
output_path: self.output_path(),
|
||||
});
|
||||
let programs = self.code_gen(validators, &checked_modules)?;
|
||||
|
||||
self.write_build_outputs(programs, uplc_dump)?;
|
||||
}
|
||||
|
||||
if run_tests {
|
||||
let tests = self.test_gen(&checked_modules)?;
|
||||
self.run_tests(tests);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -336,6 +375,7 @@ impl Project {
|
|||
func,
|
||||
);
|
||||
}
|
||||
Definition::Test(_) => {}
|
||||
Definition::TypeAlias(ta) => {
|
||||
type_aliases.insert((module.name.clone(), ta.alias.clone()), ta);
|
||||
}
|
||||
|
@ -385,11 +425,135 @@ impl Project {
|
|||
Ok(programs)
|
||||
}
|
||||
|
||||
fn write_build_outputs(&self, programs: Vec<Script>, uplc_dump: bool) -> Result<(), Error> {
|
||||
let assets = self.root.join("assets");
|
||||
// TODO: revisit ownership and lifetimes of data in this function
|
||||
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 {
|
||||
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)?;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -495,6 +495,7 @@ impl From<Term<FakeNamedDeBruijn>> for Term<NamedDeBruijn> {
|
|||
impl Program<NamedDeBruijn> {
|
||||
pub fn eval(
|
||||
&self,
|
||||
initial_budget: ExBudget,
|
||||
) -> (
|
||||
Result<Term<NamedDeBruijn>, crate::machine::Error>,
|
||||
ExBudget,
|
||||
|
@ -503,7 +504,7 @@ impl Program<NamedDeBruijn> {
|
|||
let mut machine = Machine::new(
|
||||
Language::PlutusV2,
|
||||
CostModel::default(),
|
||||
ExBudget::default(),
|
||||
initial_budget,
|
||||
200,
|
||||
);
|
||||
|
||||
|
@ -558,6 +559,7 @@ impl Program<NamedDeBruijn> {
|
|||
impl Program<DeBruijn> {
|
||||
pub fn eval(
|
||||
&self,
|
||||
initial_budget: ExBudget,
|
||||
) -> (
|
||||
Result<Term<NamedDeBruijn>, crate::machine::Error>,
|
||||
ExBudget,
|
||||
|
@ -565,7 +567,7 @@ impl Program<DeBruijn> {
|
|||
) {
|
||||
let program: Program<NamedDeBruijn> = self.clone().into();
|
||||
|
||||
program.eval()
|
||||
program.eval(initial_budget)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
pub struct CostModel {
|
||||
pub machine_costs: MachineCosts,
|
||||
|
|
|
@ -786,7 +786,7 @@ pub fn eval_redeemer(
|
|||
|
||||
program.eval_as(&Language::PlutusV2, costs, initial_budget)
|
||||
} else {
|
||||
program.eval()
|
||||
program.eval(ExBudget::default())
|
||||
};
|
||||
|
||||
match result {
|
||||
|
@ -889,7 +889,7 @@ pub fn eval_redeemer(
|
|||
|
||||
program.eval_as(&Language::PlutusV2, costs, initial_budget)
|
||||
} else {
|
||||
program.eval()
|
||||
program.eval(ExBudget::default())
|
||||
};
|
||||
|
||||
match result {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
use aiken/builtin
|
||||
|
||||
test bar() {
|
||||
builtin.length_of_bytearray(#[2, 2, 3]) == 3
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use sample
|
||||
|
||||
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])]
|
||||
|
||||
|
@ -11,3 +11,7 @@ pub fn spend(datum: sample.Datum, rdmr: sample.Redeemer, _ctx: Nil) -> Bool {
|
|||
|
||||
z == #(#[222], #[222])
|
||||
}
|
||||
|
||||
test foo() {
|
||||
1 + 1 == 2
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue