Merge pull request #166 from aiken-lang/some-interesting-test-cases
Include generics to get test cases working
This commit is contained in:
commit
d9d1310c6d
|
@ -1,9 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*.ak]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.ak]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tabs
|
||||
indent_size = 4
|
||||
|
|
|
@ -11,6 +11,10 @@ pub struct Args {
|
|||
#[clap(short, long)]
|
||||
skip_tests: bool,
|
||||
|
||||
/// When enabled, also pretty-print test UPLC on failure
|
||||
#[clap(long)]
|
||||
debug: bool,
|
||||
|
||||
/// Only run tests if their path + name match the given string
|
||||
#[clap(short, long)]
|
||||
match_tests: Option<String>,
|
||||
|
@ -20,8 +24,11 @@ pub fn exec(
|
|||
Args {
|
||||
directory,
|
||||
skip_tests,
|
||||
debug,
|
||||
match_tests,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| p.check(skip_tests, match_tests.clone()))
|
||||
crate::with_project(directory, |p| {
|
||||
p.check(skip_tests, match_tests.clone(), debug)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use std::{env, path::PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
use std::{env, path::PathBuf, process};
|
||||
|
||||
use aiken_project::{
|
||||
config::Config,
|
||||
telemetry::{self, TestInfo},
|
||||
Project,
|
||||
};
|
||||
use aiken_project::{config::Config, pretty, script::EvalInfo, telemetry, Project};
|
||||
use miette::IntoDiagnostic;
|
||||
use owo_colors::OwoColorize;
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
@ -35,12 +32,20 @@ where
|
|||
|
||||
if let Err(err) = build_result {
|
||||
err.report();
|
||||
|
||||
miette::bail!("Failed: {} error(s), {warning_count} warning(s)", err.len(),);
|
||||
};
|
||||
|
||||
println!("\nFinished with {warning_count} warning(s)\n");
|
||||
|
||||
println!("{}", "Summary".purple().bold());
|
||||
println!(
|
||||
" {} error(s), {}",
|
||||
err.len(),
|
||||
format!("{warning_count} warning(s)").yellow(),
|
||||
);
|
||||
process::exit(1);
|
||||
} else {
|
||||
println!("{}", "Summary".purple().bold());
|
||||
println!(
|
||||
" 0 error, {}",
|
||||
format!("{warning_count} warning(s)").yellow(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -76,89 +81,149 @@ impl telemetry::EventListener for Terminal {
|
|||
output_path.to_str().unwrap_or("").bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::EvaluatingFunction { results } => {
|
||||
println!("{}\n", "...Evaluating function".bold().purple());
|
||||
|
||||
let (max_mem, max_cpu) = find_max_execution_units(&results);
|
||||
|
||||
for eval_info in &results {
|
||||
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu))
|
||||
}
|
||||
}
|
||||
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_cpu) = find_max_execution_units(&tests);
|
||||
|
||||
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))
|
||||
for (module, infos) in &group_by_module(&tests) {
|
||||
let first = fmt_test(infos.first().unwrap(), max_mem, max_cpu, false).len();
|
||||
println!(
|
||||
"{} {} {}",
|
||||
" ┌──".bright_black(),
|
||||
module.bold().blue(),
|
||||
pretty::pad_left("".to_string(), first - module.len() - 3, "─")
|
||||
.bright_black()
|
||||
);
|
||||
for eval_info in infos {
|
||||
println!(
|
||||
" {} {}",
|
||||
"│".bright_black(),
|
||||
fmt_test(eval_info, max_mem, max_cpu, true)
|
||||
)
|
||||
}
|
||||
let last = fmt_test(infos.last().unwrap(), max_mem, max_cpu, false).len();
|
||||
let summary = fmt_test_summary(infos, false).len();
|
||||
println!(
|
||||
"{} {}\n",
|
||||
pretty::pad_right(" └".to_string(), last - summary + 5, "─")
|
||||
.bright_black(),
|
||||
fmt_test_summary(infos, true),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
||||
let EvalInfo {
|
||||
success,
|
||||
script,
|
||||
spent_budget,
|
||||
} = test_info;
|
||||
..
|
||||
} = eval_info;
|
||||
|
||||
let ExBudget { mem, cpu } = spent_budget;
|
||||
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
||||
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
||||
|
||||
format!(
|
||||
"{} [mem: {}, cpu: {}] {}",
|
||||
if *success {
|
||||
pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string())
|
||||
} else {
|
||||
pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string())
|
||||
},
|
||||
pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()),
|
||||
pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()),
|
||||
pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
|
||||
let (n_passed, n_failed) = tests
|
||||
.iter()
|
||||
.fold((0, 0), |(n_passed, n_failed), test_info| {
|
||||
if test_info.success {
|
||||
(n_passed + 1, n_failed)
|
||||
} else {
|
||||
(n_passed, n_failed + 1)
|
||||
}
|
||||
});
|
||||
format!(
|
||||
"{} | {} | {}",
|
||||
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
||||
.bold()
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{} passed", n_passed), |s| s
|
||||
.bright_green()
|
||||
.bold()
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{} failed", n_failed), |s| s
|
||||
.bright_red()
|
||||
.bold()
|
||||
.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
|
||||
let EvalInfo {
|
||||
output,
|
||||
script,
|
||||
spent_budget,
|
||||
..
|
||||
} = eval_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()
|
||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
||||
script.module.blue(),
|
||||
script.name.bright_blue(),
|
||||
pretty::pad_left(mem.to_string(), max_mem, " "),
|
||||
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
||||
output
|
||||
.as_ref()
|
||||
.map(|x| format!("{}", x))
|
||||
.unwrap_or_else(|| "Error.".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
|
||||
let mut modules = BTreeMap::new();
|
||||
for eval_info in infos {
|
||||
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
|
||||
xs.push(eval_info);
|
||||
}
|
||||
|
||||
text
|
||||
modules
|
||||
}
|
||||
|
||||
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
|
||||
let (max_mem, max_cpu) = xs.iter().fold(
|
||||
(0, 0),
|
||||
|(max_mem, max_cpu), EvalInfo { 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)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
(max_mem.to_string().len(), max_cpu.to_string().len())
|
||||
}
|
||||
|
|
|
@ -28,16 +28,13 @@ pub enum Air {
|
|||
scope: Vec<u64>,
|
||||
constructor: ValueConstructor,
|
||||
name: String,
|
||||
variant_name: String,
|
||||
},
|
||||
|
||||
// Fn {
|
||||
// scope: Vec<u64>,
|
||||
// tipo: Arc<Type>,
|
||||
// is_capture: bool,
|
||||
// args: Vec<Arg<Arc<Type>>>,
|
||||
// body: Box<Self>,
|
||||
// return_annotation: Option<Annotation>,
|
||||
// },
|
||||
Fn {
|
||||
scope: Vec<u64>,
|
||||
params: Vec<String>,
|
||||
},
|
||||
List {
|
||||
scope: Vec<u64>,
|
||||
count: usize,
|
||||
|
@ -67,6 +64,7 @@ pub enum Air {
|
|||
Builtin {
|
||||
scope: Vec<u64>,
|
||||
func: DefaultFunction,
|
||||
tipo: Arc<Type>,
|
||||
},
|
||||
|
||||
BinOp {
|
||||
|
@ -88,6 +86,7 @@ pub enum Air {
|
|||
module_name: String,
|
||||
params: Vec<String>,
|
||||
recursive: bool,
|
||||
variant_name: String,
|
||||
},
|
||||
|
||||
DefineConst {
|
||||
|
@ -237,6 +236,7 @@ impl Air {
|
|||
| Air::List { scope, .. }
|
||||
| Air::ListAccessor { scope, .. }
|
||||
| Air::ListExpose { scope, .. }
|
||||
| Air::Fn { scope, .. }
|
||||
| Air::Call { scope, .. }
|
||||
| Air::Builtin { scope, .. }
|
||||
| Air::BinOp { scope, .. }
|
||||
|
|
|
@ -66,10 +66,8 @@ impl UntypedModule {
|
|||
}
|
||||
}
|
||||
|
||||
pub type TypedDefinition = Definition<Arc<Type>, TypedExpr, String, String>;
|
||||
pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>;
|
||||
|
||||
pub type TypedFunction = Function<Arc<Type>, TypedExpr>;
|
||||
pub type UntypedFunction = Function<(), UntypedExpr>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Function<T, Expr> {
|
||||
|
@ -84,6 +82,24 @@ pub struct Function<T, Expr> {
|
|||
pub end_position: usize,
|
||||
}
|
||||
|
||||
pub type TypedTypeAlias = TypeAlias<Arc<Type>>;
|
||||
pub type UntypedTypeAlias = TypeAlias<()>;
|
||||
|
||||
impl TypedFunction {
|
||||
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
||||
match &self.body {
|
||||
TypedExpr::BinOp {
|
||||
name,
|
||||
tipo,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} if tipo == &bool() => Some((*name, left.clone(), right.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TypeAlias<T> {
|
||||
pub alias: String,
|
||||
|
@ -95,6 +111,9 @@ pub struct TypeAlias<T> {
|
|||
pub tipo: T,
|
||||
}
|
||||
|
||||
pub type TypedDataType = DataType<Arc<Type>>;
|
||||
pub type UntypedDataType = DataType<()>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DataType<T> {
|
||||
pub constructors: Vec<RecordConstructor<T>>,
|
||||
|
@ -107,6 +126,9 @@ pub struct DataType<T> {
|
|||
pub typed_parameters: Vec<T>,
|
||||
}
|
||||
|
||||
pub type TypedUse = Use<String>;
|
||||
pub type UntypedUse = Use<()>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Use<PackageName> {
|
||||
pub as_name: Option<String>,
|
||||
|
@ -116,6 +138,9 @@ pub struct Use<PackageName> {
|
|||
pub unqualified: Vec<UnqualifiedImport>,
|
||||
}
|
||||
|
||||
pub type TypedModuleConstant = ModuleConstant<Arc<Type>, String>;
|
||||
pub type UntypedModuleConstant = ModuleConstant<(), ()>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ModuleConstant<T, ConstantRecordTag> {
|
||||
pub doc: Option<String>,
|
||||
|
@ -127,6 +152,9 @@ pub struct ModuleConstant<T, ConstantRecordTag> {
|
|||
pub tipo: T,
|
||||
}
|
||||
|
||||
pub type TypedDefinition = Definition<Arc<Type>, TypedExpr, String, String>;
|
||||
pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Definition<T, Expr, ConstantRecordTag, PackageName> {
|
||||
Fn(Function<T, Expr>),
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ use std::sync::{
|
|||
|
||||
pub mod air;
|
||||
pub mod ast;
|
||||
pub mod builder;
|
||||
pub mod builtins;
|
||||
pub mod expr;
|
||||
pub mod format;
|
||||
|
|
|
@ -135,6 +135,14 @@ impl Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_option(&self) -> bool {
|
||||
match self {
|
||||
Self::App { module, name, .. } if "Option" == name && module.is_empty() => true,
|
||||
Self::Var { tipo } => tipo.borrow().is_option(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
match self {
|
||||
Self::App {
|
||||
|
@ -143,7 +151,7 @@ impl Type {
|
|||
if let Type::Tuple { elems } = &*args[0] {
|
||||
elems.len() == 2
|
||||
} else if let Type::Var { tipo } = &*args[0] {
|
||||
matches!(tipo.borrow().get_uplc_type(), UplcType::Pair(_, _))
|
||||
matches!(tipo.borrow().get_uplc_type(), Some(UplcType::Pair(_, _)))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -157,7 +165,42 @@ impl Type {
|
|||
matches!(self, Self::Tuple { .. })
|
||||
}
|
||||
|
||||
pub fn get_inner_type(&self) -> Vec<Arc<Type>> {
|
||||
pub fn is_generic(&self) -> bool {
|
||||
match self {
|
||||
Type::App { args, .. } => {
|
||||
let mut is_a_generic = false;
|
||||
for arg in args {
|
||||
is_a_generic = is_a_generic || arg.is_generic();
|
||||
}
|
||||
is_a_generic
|
||||
}
|
||||
|
||||
Type::Var { tipo } => tipo.borrow().is_generic(),
|
||||
Type::Tuple { elems } => {
|
||||
let mut is_a_generic = false;
|
||||
for elem in elems {
|
||||
is_a_generic = is_a_generic || elem.is_generic();
|
||||
}
|
||||
is_a_generic
|
||||
}
|
||||
Type::Fn { args, .. } => {
|
||||
let mut is_a_generic = false;
|
||||
for arg in args {
|
||||
is_a_generic = is_a_generic || arg.is_generic();
|
||||
}
|
||||
is_a_generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_generic(&self) -> Option<u64> {
|
||||
match self {
|
||||
Type::Var { tipo } => tipo.borrow().get_generic(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_inner_types(&self) -> Vec<Arc<Type>> {
|
||||
if self.is_list() {
|
||||
match self {
|
||||
Self::App { args, .. } => args.clone(),
|
||||
|
@ -169,6 +212,13 @@ impl Type {
|
|||
Self::Tuple { elems } => elems.to_vec(),
|
||||
_ => vec![],
|
||||
}
|
||||
} else if matches!(self.get_uplc_type(), UplcType::Data) {
|
||||
match self {
|
||||
Type::App { args, .. } => args.clone(),
|
||||
Type::Fn { args, .. } => args.clone(),
|
||||
Type::Var { tipo } => tipo.borrow().get_inner_type(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
|
@ -374,6 +424,13 @@ impl TypeVar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_option(&self) -> bool {
|
||||
match self {
|
||||
Self::Link { tipo } => tipo.is_option(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
match self {
|
||||
Self::Link { tipo } => tipo.is_map(),
|
||||
|
@ -381,17 +438,33 @@ impl TypeVar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_generic(&self) -> bool {
|
||||
match self {
|
||||
TypeVar::Generic { .. } => true,
|
||||
TypeVar::Link { tipo } => tipo.is_generic(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_generic(&self) -> Option<u64> {
|
||||
match self {
|
||||
TypeVar::Generic { id } => Some(*id),
|
||||
TypeVar::Link { tipo } => tipo.get_generic(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_inner_type(&self) -> Vec<Arc<Type>> {
|
||||
match self {
|
||||
Self::Link { tipo } => tipo.get_inner_type(),
|
||||
Self::Link { tipo } => tipo.get_inner_types(),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uplc_type(&self) -> UplcType {
|
||||
pub fn get_uplc_type(&self) -> Option<UplcType> {
|
||||
match self {
|
||||
Self::Link { tipo } => tipo.get_uplc_type(),
|
||||
_ => unreachable!(),
|
||||
Self::Link { tipo } => Some(tipo.get_uplc_type()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,18 @@
|
|||
use crate::{pretty, script::EvalHint};
|
||||
use aiken_lang::{
|
||||
ast::{BinOp, Span},
|
||||
parser::error::ParseError,
|
||||
tipo,
|
||||
};
|
||||
use miette::{
|
||||
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
|
||||
};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use aiken_lang::{ast::Span, parser::error::ParseError, tipo};
|
||||
use miette::{
|
||||
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
|
||||
};
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(thiserror::Error)]
|
||||
|
@ -28,7 +33,7 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
StandardIo(#[from] io::Error),
|
||||
|
||||
#[error("Syclical module imports")]
|
||||
#[error("Cyclical module imports")]
|
||||
ImportCycle { modules: Vec<String> },
|
||||
|
||||
/// Useful for returning many [`Error::Parse`] at once
|
||||
|
@ -73,6 +78,15 @@ pub enum Error {
|
|||
src: String,
|
||||
named: NamedSource,
|
||||
},
|
||||
|
||||
#[error("{name} failed{}", if *verbose { format!("\n{src}") } else { String::new() } )]
|
||||
TestFailure {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
verbose: bool,
|
||||
src: String,
|
||||
evaluation_hint: Option<EvalHint>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -148,6 +162,7 @@ impl Error {
|
|||
Error::Type { path, .. } => Some(path.to_path_buf()),
|
||||
Error::ValidatorMustReturnBool { path, .. } => Some(path.to_path_buf()),
|
||||
Error::WrongValidatorArity { path, .. } => Some(path.to_path_buf()),
|
||||
Error::TestFailure { path, .. } => Some(path.to_path_buf()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,6 +178,7 @@ impl Error {
|
|||
Error::Type { src, .. } => Some(src.to_string()),
|
||||
Error::ValidatorMustReturnBool { src, .. } => Some(src.to_string()),
|
||||
Error::WrongValidatorArity { src, .. } => Some(src.to_string()),
|
||||
Error::TestFailure { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +219,7 @@ impl Diagnostic for Error {
|
|||
Error::Format { .. } => None,
|
||||
Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")),
|
||||
Error::WrongValidatorArity { .. } => Some(Box::new("aiken::validators")),
|
||||
Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,6 +242,34 @@ impl Diagnostic for Error {
|
|||
Error::Format { .. } => None,
|
||||
Error::ValidatorMustReturnBool { .. } => Some(Box::new("Try annotating the validator's return type with Bool")),
|
||||
Error::WrongValidatorArity { .. } => Some(Box::new("Validators require a minimum number of arguments please add the missing arguments.\nIf you don't need one of the required arguments use an underscore `_datum`.")),
|
||||
Error::TestFailure { evaluation_hint, .. } =>{
|
||||
match evaluation_hint {
|
||||
None => None,
|
||||
Some(hint) => {
|
||||
let budget = ExBudget { mem: i64::MAX, cpu: i64::MAX, };
|
||||
let left = pretty::boxed("left", match hint.left.eval(budget) {
|
||||
(Ok(term), _, _) => format!("{term}"),
|
||||
(Err(err), _, _) => format!("{err}"),
|
||||
});
|
||||
let right = pretty::boxed("right", match hint.right.eval(budget) {
|
||||
(Ok(term), _, _) => format!("{term}"),
|
||||
(Err(err), _, _) => format!("{err}"),
|
||||
});
|
||||
let msg = match hint.bin_op {
|
||||
BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")),
|
||||
BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")),
|
||||
BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
||||
BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
||||
BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
||||
BinOp::LtEqInt => Some(format!("{left}\n\nshould be lower than or equal to\n\n{right}")),
|
||||
BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
||||
BinOp::GtInt => Some(format!("{left}\n\nshould be greater than or equal to\n\n{right}")),
|
||||
_ => None
|
||||
}?;
|
||||
Some(Box::new(msg))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +289,7 @@ impl Diagnostic for Error {
|
|||
Error::WrongValidatorArity { location, .. } => Some(Box::new(
|
||||
vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
|
||||
)),
|
||||
Error::TestFailure { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,6 +305,7 @@ impl Diagnostic for Error {
|
|||
Error::Format { .. } => None,
|
||||
Error::ValidatorMustReturnBool { named, .. } => Some(named),
|
||||
Error::WrongValidatorArity { named, .. } => Some(named),
|
||||
Error::TestFailure { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod format;
|
||||
pub mod module;
|
||||
pub mod options;
|
||||
pub mod pretty;
|
||||
pub mod script;
|
||||
pub mod telemetry;
|
||||
|
||||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, TypedFunction},
|
||||
builtins,
|
||||
ast::{
|
||||
Annotation, DataType, Definition, Function, ModuleKind, RecordConstructor,
|
||||
RecordConstructorArg, Span, TypedDataType, TypedDefinition, TypedFunction,
|
||||
},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
builtins::{self, generic_var},
|
||||
tipo::TypeInfo,
|
||||
uplc::{CodeGenerator, DataTypeKey, FunctionAccessKey},
|
||||
uplc::CodeGenerator,
|
||||
IdGenerator,
|
||||
};
|
||||
use miette::NamedSource;
|
||||
|
@ -26,11 +25,16 @@ use pallas::{
|
|||
ledger::{addresses::Address, primitives::babbage},
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
use script::Script;
|
||||
use script::{EvalHint, EvalInfo, Script};
|
||||
use serde_json::json;
|
||||
use telemetry::{EventListener, TestInfo};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use telemetry::EventListener;
|
||||
use uplc::{
|
||||
ast::{DeBruijn, Program},
|
||||
ast::{Constant, DeBruijn, Program, Term},
|
||||
machine::cost_model::ExBudget,
|
||||
};
|
||||
|
||||
|
@ -101,12 +105,20 @@ where
|
|||
self.compile(options)
|
||||
}
|
||||
|
||||
pub fn check(&mut self, skip_tests: bool, match_tests: Option<String>) -> Result<(), Error> {
|
||||
pub fn check(
|
||||
&mut self,
|
||||
skip_tests: bool,
|
||||
match_tests: Option<String>,
|
||||
verbose: bool,
|
||||
) -> Result<(), Error> {
|
||||
let options = Options {
|
||||
code_gen_mode: if skip_tests {
|
||||
CodeGenMode::NoOp
|
||||
} else {
|
||||
CodeGenMode::Test(match_tests)
|
||||
CodeGenMode::Test {
|
||||
match_tests,
|
||||
verbose,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -140,19 +152,47 @@ where
|
|||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
CodeGenMode::Test(match_tests) => {
|
||||
let tests = self.test_gen(&checked_modules)?;
|
||||
self.run_tests(tests, match_tests);
|
||||
}
|
||||
CodeGenMode::NoOp => (),
|
||||
}
|
||||
CodeGenMode::Test {
|
||||
match_tests,
|
||||
verbose,
|
||||
} => {
|
||||
let tests = self
|
||||
.collect_scripts(&checked_modules, |def| matches!(def, Definition::Test(..)))?;
|
||||
if !tests.is_empty() {
|
||||
self.event_listener.handle_event(Event::RunningTests);
|
||||
}
|
||||
let results = self.eval_scripts(tests, match_tests);
|
||||
let errors: Vec<Error> = results
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
if e.success {
|
||||
None
|
||||
} else {
|
||||
Some(Error::TestFailure {
|
||||
name: e.script.name.clone(),
|
||||
path: e.script.input_path.clone(),
|
||||
evaluation_hint: e.script.evaluation_hint.clone(),
|
||||
src: e.script.program.to_pretty(),
|
||||
verbose,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(())
|
||||
self.event_listener
|
||||
.handle_event(Event::FinishedTests { tests: results });
|
||||
if !errors.is_empty() {
|
||||
Err(Error::List(errors))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
CodeGenMode::NoOp => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_source_files(&mut self) -> Result<(), Error> {
|
||||
|
@ -290,7 +330,7 @@ where
|
|||
fn validate_validators(
|
||||
&self,
|
||||
checked_modules: &mut CheckedModules,
|
||||
) -> Result<Vec<(String, TypedFunction)>, Error> {
|
||||
) -> Result<Vec<(PathBuf, String, TypedFunction)>, Error> {
|
||||
let mut errors = Vec::new();
|
||||
let mut validators = Vec::new();
|
||||
let mut indices_to_remove = Vec::new();
|
||||
|
@ -344,7 +384,11 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
validators.push((module.name.clone(), func_def.clone()));
|
||||
validators.push((
|
||||
module.input_path.clone(),
|
||||
module.name.clone(),
|
||||
func_def.clone(),
|
||||
));
|
||||
indices_to_remove.push(index);
|
||||
}
|
||||
}
|
||||
|
@ -364,7 +408,7 @@ where
|
|||
|
||||
fn code_gen(
|
||||
&mut self,
|
||||
validators: Vec<(String, TypedFunction)>,
|
||||
validators: Vec<(PathBuf, String, TypedFunction)>,
|
||||
checked_modules: &CheckedModules,
|
||||
) -> Result<Vec<Script>, Error> {
|
||||
let mut programs = Vec::new();
|
||||
|
@ -374,6 +418,16 @@ where
|
|||
let mut imports = HashMap::new();
|
||||
let mut constants = HashMap::new();
|
||||
|
||||
let option_data_type = make_option();
|
||||
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
&option_data_type,
|
||||
);
|
||||
|
||||
for module in checked_modules.values() {
|
||||
for def in module.ast.definitions() {
|
||||
match def {
|
||||
|
@ -382,6 +436,7 @@ where
|
|||
FunctionAccessKey {
|
||||
module_name: module.name.clone(),
|
||||
function_name: func.name.clone(),
|
||||
variant_name: String::new(),
|
||||
},
|
||||
func,
|
||||
);
|
||||
|
@ -409,7 +464,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
for (module_name, func_def) in validators {
|
||||
for (input_path, module_name, func_def) in validators {
|
||||
let Function {
|
||||
arguments,
|
||||
name,
|
||||
|
@ -426,9 +481,15 @@ where
|
|||
&self.module_types,
|
||||
);
|
||||
|
||||
let program = generator.generate(body, arguments);
|
||||
let program = generator.generate(body, arguments, true);
|
||||
|
||||
let script = Script::new(module_name, name, program.try_into().unwrap());
|
||||
let script = Script::new(
|
||||
input_path,
|
||||
module_name,
|
||||
name,
|
||||
program.try_into().unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
programs.push(script);
|
||||
}
|
||||
|
@ -437,7 +498,11 @@ where
|
|||
}
|
||||
|
||||
// TODO: revisit ownership and lifetimes of data in this function
|
||||
fn test_gen(&mut self, checked_modules: &CheckedModules) -> Result<Vec<Script>, Error> {
|
||||
fn collect_scripts(
|
||||
&mut self,
|
||||
checked_modules: &CheckedModules,
|
||||
should_collect: fn(&TypedDefinition) -> bool,
|
||||
) -> Result<Vec<Script>, Error> {
|
||||
let mut programs = Vec::new();
|
||||
let mut functions = HashMap::new();
|
||||
let mut type_aliases = HashMap::new();
|
||||
|
@ -445,8 +510,18 @@ where
|
|||
let mut imports = HashMap::new();
|
||||
let mut constants = HashMap::new();
|
||||
|
||||
let option_data_type = make_option();
|
||||
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
&option_data_type,
|
||||
);
|
||||
|
||||
// let mut indices_to_remove = Vec::new();
|
||||
let mut tests = Vec::new();
|
||||
let mut scripts = Vec::new();
|
||||
|
||||
for module in checked_modules.values() {
|
||||
for (_index, def) in module.ast.definitions().enumerate() {
|
||||
|
@ -456,12 +531,18 @@ where
|
|||
FunctionAccessKey {
|
||||
module_name: module.name.clone(),
|
||||
function_name: func.name.clone(),
|
||||
variant_name: String::new(),
|
||||
},
|
||||
func,
|
||||
);
|
||||
if should_collect(def) {
|
||||
scripts.push((module.input_path.clone(), module.name.clone(), func));
|
||||
}
|
||||
}
|
||||
Definition::Test(func) => {
|
||||
tests.push((module.name.clone(), func));
|
||||
if should_collect(def) {
|
||||
scripts.push((module.input_path.clone(), module.name.clone(), func));
|
||||
}
|
||||
// indices_to_remove.push(index);
|
||||
}
|
||||
Definition::TypeAlias(ta) => {
|
||||
|
@ -490,7 +571,7 @@ where
|
|||
// }
|
||||
}
|
||||
|
||||
for (module_name, func_def) in tests {
|
||||
for (input_path, module_name, func_def) in scripts {
|
||||
let Function {
|
||||
arguments,
|
||||
name,
|
||||
|
@ -507,9 +588,34 @@ where
|
|||
&self.module_types,
|
||||
);
|
||||
|
||||
let program = generator.generate(body.clone(), arguments.clone());
|
||||
let evaluation_hint = if let Some((bin_op, left_src, right_src)) = func_def.test_hint()
|
||||
{
|
||||
let left = CodeGenerator::new(&functions, &data_types, &self.module_types)
|
||||
.generate(*left_src, vec![], false)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let right = CodeGenerator::new(&functions, &data_types, &self.module_types)
|
||||
.generate(*right_src, vec![], false)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
Some(EvalHint {
|
||||
bin_op,
|
||||
left,
|
||||
right,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let script = Script::new(module_name, name.to_string(), program.try_into().unwrap());
|
||||
let program = generator.generate(body.clone(), arguments.clone(), false);
|
||||
|
||||
let script = Script::new(
|
||||
input_path,
|
||||
module_name,
|
||||
name.to_string(),
|
||||
program.try_into().unwrap(),
|
||||
evaluation_hint,
|
||||
);
|
||||
|
||||
programs.push(script);
|
||||
}
|
||||
|
@ -517,7 +623,7 @@ where
|
|||
Ok(programs)
|
||||
}
|
||||
|
||||
fn run_tests(&self, tests: Vec<Script>, match_tests: Option<String>) {
|
||||
fn eval_scripts(&self, scripts: Vec<Script>, match_name: Option<String>) -> Vec<EvalInfo> {
|
||||
// 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 {
|
||||
|
@ -525,43 +631,41 @@ where
|
|||
cpu: i64::MAX,
|
||||
};
|
||||
|
||||
if !tests.is_empty() {
|
||||
self.event_listener.handle_event(Event::RunningTests);
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
for test in tests {
|
||||
let path = format!("{}{}", test.module, test.name);
|
||||
for script in scripts {
|
||||
let path = format!("{}{}", script.module, script.name);
|
||||
|
||||
if matches!(&match_tests, Some(search_str) if !path.to_string().contains(search_str)) {
|
||||
if matches!(&match_name, Some(search_str) if !path.to_string().contains(search_str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match test.program.eval(initial_budget) {
|
||||
(Ok(..), remaining_budget, _) => {
|
||||
let test_info = TestInfo {
|
||||
is_passing: true,
|
||||
test,
|
||||
match script.program.eval(initial_budget) {
|
||||
(Ok(result), remaining_budget, _) => {
|
||||
let eval_info = EvalInfo {
|
||||
success: result != Term::Error
|
||||
&& result != Term::Constant(Constant::Bool(false)),
|
||||
script,
|
||||
spent_budget: initial_budget - remaining_budget,
|
||||
output: Some(result),
|
||||
};
|
||||
|
||||
results.push(test_info);
|
||||
results.push(eval_info);
|
||||
}
|
||||
(Err(_), remaining_budget, _) => {
|
||||
let test_info = TestInfo {
|
||||
is_passing: false,
|
||||
test,
|
||||
(Err(..), remaining_budget, _) => {
|
||||
let eval_info = EvalInfo {
|
||||
success: false,
|
||||
script,
|
||||
spent_budget: initial_budget - remaining_budget,
|
||||
output: None,
|
||||
};
|
||||
|
||||
results.push(test_info);
|
||||
results.push(eval_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.event_listener
|
||||
.handle_event(Event::FinishedTests { tests: results });
|
||||
results
|
||||
}
|
||||
|
||||
fn output_path(&self) -> PathBuf {
|
||||
|
@ -722,3 +826,40 @@ fn is_aiken_path(path: &Path, dir: impl AsRef<Path>) -> bool {
|
|||
.expect("is_aiken_path(): to_str"),
|
||||
)
|
||||
}
|
||||
|
||||
fn make_option() -> TypedDataType {
|
||||
DataType {
|
||||
constructors: vec![
|
||||
RecordConstructor {
|
||||
location: Span::empty(),
|
||||
name: "Some".to_string(),
|
||||
arguments: vec![RecordConstructorArg {
|
||||
label: None,
|
||||
annotation: Annotation::Var {
|
||||
location: Span::empty(),
|
||||
name: "a".to_string(),
|
||||
},
|
||||
location: Span::empty(),
|
||||
tipo: generic_var(0),
|
||||
doc: None,
|
||||
}],
|
||||
documentation: None,
|
||||
sugar: false,
|
||||
},
|
||||
RecordConstructor {
|
||||
location: Span::empty(),
|
||||
name: "None".to_string(),
|
||||
arguments: vec![],
|
||||
documentation: None,
|
||||
sugar: false,
|
||||
},
|
||||
],
|
||||
doc: None,
|
||||
location: Span::empty(),
|
||||
name: "Option".to_string(),
|
||||
opaque: false,
|
||||
parameters: vec!["a".to_string()],
|
||||
public: true,
|
||||
typed_parameters: vec![generic_var(0)],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ pub struct Options {
|
|||
}
|
||||
|
||||
pub enum CodeGenMode {
|
||||
Test(Option<String>),
|
||||
Test {
|
||||
match_tests: Option<String>,
|
||||
verbose: bool,
|
||||
},
|
||||
Build(bool),
|
||||
NoOp,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
pub fn boxed(title: &str, content: String) -> String {
|
||||
let n = content.lines().fold(0, |max, l| {
|
||||
let n = l.len();
|
||||
if n > max {
|
||||
n
|
||||
} else {
|
||||
max
|
||||
}
|
||||
});
|
||||
|
||||
let content = content
|
||||
.lines()
|
||||
.map(|line| format!("│ {} │", pad_right(line.to_string(), n, " ")))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let top = format!("┍━ {}┑", pad_right(format!("{title} "), n, "━"));
|
||||
let bottom = format!("┕{}┙", pad_right(String::new(), n + 2, "━"));
|
||||
format!("{top}\n{content}\n{bottom}")
|
||||
}
|
||||
|
||||
pub fn pad_left(mut text: String, n: usize, delimiter: &str) -> String {
|
||||
let diff = n as i32 - text.len() as i32;
|
||||
if diff.is_positive() {
|
||||
for _ in 0..diff {
|
||||
text.insert_str(0, delimiter);
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub fn pad_right(mut text: String, n: usize, delimiter: &str) -> String {
|
||||
let diff = n as i32 - text.len() as i32;
|
||||
if diff.is_positive() {
|
||||
for _ in 0..diff {
|
||||
text.push_str(delimiter);
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub fn style_if(styled: bool, s: String, apply_style: fn(String) -> String) -> String {
|
||||
if styled {
|
||||
apply_style(s)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
|
@ -1,18 +1,46 @@
|
|||
use crate::{ExBudget, Term};
|
||||
use aiken_lang::ast::BinOp;
|
||||
use std::path::PathBuf;
|
||||
use uplc::ast::{NamedDeBruijn, Program};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Script {
|
||||
pub input_path: PathBuf,
|
||||
pub module: String,
|
||||
pub name: String,
|
||||
pub program: Program<NamedDeBruijn>,
|
||||
pub evaluation_hint: Option<EvalHint>,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
pub fn new(module: String, name: String, program: Program<NamedDeBruijn>) -> Script {
|
||||
pub fn new(
|
||||
input_path: PathBuf,
|
||||
module: String,
|
||||
name: String,
|
||||
program: Program<NamedDeBruijn>,
|
||||
evaluation_hint: Option<EvalHint>,
|
||||
) -> Script {
|
||||
Script {
|
||||
input_path,
|
||||
module,
|
||||
name,
|
||||
program,
|
||||
evaluation_hint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EvalHint {
|
||||
pub bin_op: BinOp,
|
||||
pub left: Program<NamedDeBruijn>,
|
||||
pub right: Program<NamedDeBruijn>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EvalInfo {
|
||||
pub success: bool,
|
||||
pub script: Script,
|
||||
pub spent_budget: ExBudget,
|
||||
pub output: Option<Term<NamedDeBruijn>>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::script::Script;
|
||||
use crate::script::EvalInfo;
|
||||
use std::path::PathBuf;
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
pub trait EventListener: std::fmt::Debug {
|
||||
fn handle_event(&self, event: Event);
|
||||
|
@ -17,14 +16,11 @@ pub enum Event {
|
|||
GeneratingUPLC {
|
||||
output_path: PathBuf,
|
||||
},
|
||||
EvaluatingFunction {
|
||||
results: Vec<EvalInfo>,
|
||||
},
|
||||
RunningTests,
|
||||
FinishedTests {
|
||||
tests: Vec<TestInfo>,
|
||||
tests: Vec<EvalInfo>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct TestInfo {
|
||||
pub is_passing: bool,
|
||||
pub test: Script,
|
||||
pub spent_budget: ExBudget,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_001"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,14 @@
|
|||
pub fn length(xs: List<a>) -> Int {
|
||||
when xs is {
|
||||
[] -> 0
|
||||
[_, ..rest] -> 1 + length(rest)
|
||||
}
|
||||
}
|
||||
|
||||
test length_1() {
|
||||
length([1, 2, 3]) == 3
|
||||
}
|
||||
|
||||
test length_2() {
|
||||
length([]) == 0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_002"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,11 @@
|
|||
pub fn repeat(x: a, n: Int) -> List<a> {
|
||||
if n <= 0 {
|
||||
[]
|
||||
} else {
|
||||
[x, ..repeat(x, n - 1)]
|
||||
}
|
||||
}
|
||||
|
||||
test repeat_1() {
|
||||
repeat("aiken", 2) == ["aiken", "aiken"]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_003"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,14 @@
|
|||
pub fn foldr(xs: List<a>, f: fn(a, b) -> b, zero: b) -> b {
|
||||
when xs is {
|
||||
[] -> zero
|
||||
[x, ..rest] -> f(x, foldr(rest, f, zero))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn concat(left: List<a>, right: List<a>) -> List<a> {
|
||||
foldr(left, fn(x, xs) { [x, ..xs] }, right)
|
||||
}
|
||||
|
||||
test concat_1() {
|
||||
concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_004"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,18 @@
|
|||
pub fn foldr(xs: List<a>, f: fn(a, b) -> b, zero: b) -> b {
|
||||
when xs is {
|
||||
[] -> zero
|
||||
[x, ..rest] -> f(x, foldr(rest, f, zero))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepend(x: a, xs: List<a>) -> List<a> {
|
||||
[x, ..xs]
|
||||
}
|
||||
|
||||
pub fn concat(left: List<a>, right: List<a>) -> List<a> {
|
||||
foldr(left, prepend, right)
|
||||
}
|
||||
|
||||
test concat_1() {
|
||||
concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_005"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,16 @@
|
|||
use aiken/builtin.{head_list}
|
||||
|
||||
pub fn head(xs: List<a>) -> Option<a> {
|
||||
when xs is {
|
||||
[] -> None
|
||||
_ -> Some(head_list(xs))
|
||||
}
|
||||
}
|
||||
|
||||
test head_1() {
|
||||
head([1, 2, 3]) == Some(1)
|
||||
}
|
||||
|
||||
test head_2() {
|
||||
head([]) == None
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_006"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,3 @@
|
|||
test foo() {
|
||||
#(1, []) == #(1, [])
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_007"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,9 @@
|
|||
pub fn unzip(xs: List<#(a, b)>) -> #(List<a>, List<b>) {
|
||||
when xs is {
|
||||
[] -> #([], [])
|
||||
[#(a, b), ..rest] -> {
|
||||
let #(a_tail, b_tail) = unzip(rest)
|
||||
#([a, ..a_tail], [b, ..b_tail])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_008"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,21 @@
|
|||
use aiken/builtin
|
||||
|
||||
pub fn is_empty(bytes: ByteArray) -> Bool {
|
||||
builtin.length_of_bytearray(bytes) == 0
|
||||
}
|
||||
|
||||
test is_empty_1() {
|
||||
is_empty(#[]) == True
|
||||
}
|
||||
|
||||
test is_empty_1_alt() {
|
||||
is_empty(#[])
|
||||
}
|
||||
|
||||
test is_empty_2() {
|
||||
is_empty(#[1]) == False
|
||||
}
|
||||
|
||||
test is_empty_2_alt() {
|
||||
!is_empty(#[1])
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_009"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,9 @@
|
|||
use aiken/builtin.{length_of_bytearray}
|
||||
|
||||
pub fn is_empty(bytes: ByteArray) -> Bool {
|
||||
length_of_bytearray(bytes) == 0
|
||||
}
|
||||
|
||||
test is_empty_1() {
|
||||
is_empty(#[]) == True
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_010"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,14 @@
|
|||
pub fn map(opt: Option<a>, f: fn(a) -> b) -> Option<b> {
|
||||
when opt is {
|
||||
None -> None
|
||||
Some(a) -> Some(f(a))
|
||||
}
|
||||
}
|
||||
|
||||
fn add_one(n: Int) -> Int {
|
||||
n + 1
|
||||
}
|
||||
|
||||
test map_1() {
|
||||
map(None, add_one) == None
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_011"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,10 @@
|
|||
pub fn map(xs: List<a>, f: fn(a) -> result) -> List<result> {
|
||||
when xs is {
|
||||
[] -> []
|
||||
[x, ..rest] -> [f(x), ..map(rest, f)]
|
||||
}
|
||||
}
|
||||
|
||||
test map_1() {
|
||||
map([], fn(n) { n + 1 }) == []
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_012"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,18 @@
|
|||
use aiken/builtin
|
||||
|
||||
pub fn filter(xs: List<a>, f: fn(a) -> Bool) -> List<a> {
|
||||
when xs is {
|
||||
[] -> []
|
||||
[x, ..rest] ->
|
||||
if f(x) {
|
||||
[x, ..filter(rest, f)]
|
||||
} else {
|
||||
filter(rest, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test filter_1() {
|
||||
filter([1,
|
||||
2, 3, 4, 5, 6], fn(x) { builtin.mod_integer(x, 2) == 0 }) == [2, 4, 6]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_013"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,13 @@
|
|||
pub fn unzip(xs: List<#(a, b)>) -> #(List<a>, List<b>) {
|
||||
when xs is {
|
||||
[] -> #([], [])
|
||||
[#(a, b), ..rest] -> {
|
||||
let #(a_tail, b_tail) = unzip(rest)
|
||||
#([a, ..a_tail], [b, ..b_tail])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test unzip_1() {
|
||||
unzip([#(1, "a"), #(2, "b")]) == #([1, 2], ["a", "b"])
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_014"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,7 @@
|
|||
// NOTE:
|
||||
// Somehow, the left-hand evaluates to: [#00, #20, #21]
|
||||
// whereas the right-hand evaluates to: [#21, #20, #00]
|
||||
//
|
||||
test foo() {
|
||||
[0 - 2, 0 - 1, 0] == [-2, -1, 0]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
name = "acceptance_test_015"
|
||||
version = "0.0.0"
|
|
@ -0,0 +1,11 @@
|
|||
pub opaque type Map<key, value> {
|
||||
inner: List<#(key, value)>,
|
||||
}
|
||||
|
||||
pub fn new() {
|
||||
Map { inner: [] }
|
||||
}
|
||||
|
||||
test new_1() {
|
||||
new() == Map { inner: [] }
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
all:
|
||||
@for t in $(shell find . -regex ".*[0-9]\{3\}" -type d | sort); do \
|
||||
cargo run --quiet -- check -d $${t}; \
|
||||
echo ""; \
|
||||
done
|
Loading…
Reference in New Issue