Allow testing blueprint generation from Aiken programs
This is quite something, because now we have a testing pipeline that can also be used for testing other compiler-related stuff such as the type-checker or the code generator.
This commit is contained in:
parent
b93e14659c
commit
d2cc44e5f4
|
@ -112,6 +112,7 @@ version = "0.0.28"
|
|||
dependencies = [
|
||||
"aiken-lang",
|
||||
"askama",
|
||||
"assert-json-diff",
|
||||
"dirs",
|
||||
"fslock",
|
||||
"futures",
|
||||
|
@ -197,6 +198,16 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
|
|
@ -5,8 +5,8 @@ use strum::IntoEnumIterator;
|
|||
use uplc::builtins::DefaultFunction;
|
||||
|
||||
use crate::{
|
||||
ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedFunction, UnOp},
|
||||
builder::FunctionAccessKey,
|
||||
ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedDataType, TypedFunction, UnOp},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
expr::TypedExpr,
|
||||
tipo::{
|
||||
fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor,
|
||||
|
@ -800,6 +800,22 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> HashMap<FunctionAccessKey, Typ
|
|||
functions
|
||||
}
|
||||
|
||||
pub fn prelude_data_types(id_gen: &IdGenerator) -> HashMap<DataTypeKey, TypedDataType> {
|
||||
let mut data_types = HashMap::new();
|
||||
|
||||
// Option
|
||||
let option_data_type = TypedDataType::option(generic_var(id_gen.next()));
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
option_data_type,
|
||||
);
|
||||
|
||||
data_types
|
||||
}
|
||||
|
||||
pub fn int() -> Arc<Type> {
|
||||
Arc::new(Type::App {
|
||||
public: true,
|
||||
|
|
|
@ -11,6 +11,7 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
|||
[dependencies]
|
||||
aiken-lang = { path = "../aiken-lang", version = "0.0.28" }
|
||||
askama = "0.10.5"
|
||||
assert-json-diff = "2.0.2"
|
||||
dirs = "4.0.0"
|
||||
fslock = "0.2.1"
|
||||
futures = "0.3.25"
|
||||
|
|
|
@ -5,7 +5,7 @@ pub mod validator;
|
|||
use crate::{config::Config, module::CheckedModules};
|
||||
use aiken_lang::uplc::CodeGenerator;
|
||||
use error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use validator::Validator;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
|
||||
|
@ -14,6 +14,16 @@ pub struct Blueprint {
|
|||
pub validators: Vec<validator::Validator>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
|
||||
pub struct Preamble {
|
||||
pub title: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub license: Option<String>,
|
||||
}
|
||||
|
||||
impl Blueprint {
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
|
@ -36,14 +46,11 @@ impl Blueprint {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
|
||||
pub struct Preamble {
|
||||
pub title: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub license: Option<String>,
|
||||
impl Display for Blueprint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
|
||||
f.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Preamble {
|
||||
|
|
|
@ -60,6 +60,13 @@ impl Serialize for Validator {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Validator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
|
||||
f.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
pub fn from_checked_module(
|
||||
modules: &CheckedModules,
|
||||
|
@ -128,51 +135,141 @@ impl From<String> for Purpose {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::schema::{Constructor, Data, Schema};
|
||||
use super::*;
|
||||
use crate::{module::ParsedModule, PackageName};
|
||||
use aiken_lang::{
|
||||
self,
|
||||
ast::{ModuleKind, TypedDataType, TypedFunction},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
builtins, parser,
|
||||
tipo::TypeInfo,
|
||||
IdGenerator,
|
||||
};
|
||||
use assert_json_diff::assert_json_eq;
|
||||
use serde_json::{self, json};
|
||||
use uplc::parser;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
// TODO: Possible refactor this out of the module and have it used by `Project`. The idea would
|
||||
// be to make this struct below the actual project, and wrap it in another metadata struct
|
||||
// which contains all the config and I/O stuff regarding the project.
|
||||
struct TestProject {
|
||||
package: PackageName,
|
||||
id_gen: IdGenerator,
|
||||
module_types: HashMap<String, TypeInfo>,
|
||||
functions: HashMap<FunctionAccessKey, TypedFunction>,
|
||||
data_types: HashMap<DataTypeKey, TypedDataType>,
|
||||
}
|
||||
|
||||
impl TestProject {
|
||||
fn new() -> Self {
|
||||
let id_gen = IdGenerator::new();
|
||||
|
||||
let package = PackageName {
|
||||
owner: "test".to_owned(),
|
||||
repo: "project".to_owned(),
|
||||
};
|
||||
|
||||
let mut module_types = HashMap::new();
|
||||
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
||||
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
||||
|
||||
let functions = builtins::prelude_functions(&id_gen);
|
||||
let data_types = builtins::prelude_data_types(&id_gen);
|
||||
|
||||
TestProject {
|
||||
package,
|
||||
id_gen,
|
||||
module_types,
|
||||
functions,
|
||||
data_types,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(&self, source_code: &str) -> ParsedModule {
|
||||
let kind = ModuleKind::Validator;
|
||||
let (ast, extra) = parser::module(source_code, kind).unwrap();
|
||||
let mut module = ParsedModule {
|
||||
kind,
|
||||
ast,
|
||||
code: source_code.to_string(),
|
||||
name: "test".to_owned(),
|
||||
path: PathBuf::new(),
|
||||
extra,
|
||||
package: self.package.to_string(),
|
||||
};
|
||||
module.attach_doc_and_module_comments();
|
||||
module
|
||||
}
|
||||
|
||||
fn check(&self, module: ParsedModule) -> CheckedModule {
|
||||
let mut warnings = vec![];
|
||||
|
||||
let ast = module
|
||||
.ast
|
||||
.infer(
|
||||
&self.id_gen,
|
||||
module.kind,
|
||||
&self.package.to_string(),
|
||||
&self.module_types,
|
||||
&mut warnings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
CheckedModule {
|
||||
kind: module.kind,
|
||||
extra: module.extra,
|
||||
name: module.name,
|
||||
code: module.code,
|
||||
package: module.package,
|
||||
input_path: module.path,
|
||||
ast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_validator(source_code: &str, json: serde_json::Value) {
|
||||
let project = TestProject::new();
|
||||
|
||||
let modules = CheckedModules::singleton(project.check(project.parse(source_code)));
|
||||
let mut generator = modules.new_generator(
|
||||
&project.functions,
|
||||
&project.data_types,
|
||||
&project.module_types,
|
||||
);
|
||||
|
||||
let (validator, def) = modules
|
||||
.validators()
|
||||
.next()
|
||||
.expect("source code did no yield any validator");
|
||||
|
||||
let validator =
|
||||
Validator::from_checked_module(&modules, &mut generator, validator, def).unwrap();
|
||||
|
||||
println!("{}", validator);
|
||||
assert_json_eq!(serde_json::to_value(&validator).unwrap(), json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let program = parser::program("(program 1.0.0 (con integer 42))")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let validator = Validator {
|
||||
title: "foo".to_string(),
|
||||
description: Some("Lorem ipsum".to_string()),
|
||||
purpose: Purpose::Spend,
|
||||
datum: None,
|
||||
redeemer: Annotated {
|
||||
title: Some("Bar".to_string()),
|
||||
description: None,
|
||||
annotated: Schema::Data(Some(Data::AnyOf(vec![Constructor {
|
||||
index: 0,
|
||||
fields: vec![Data::Bytes.into()],
|
||||
}
|
||||
.into()]))),
|
||||
},
|
||||
program,
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&validator).unwrap(),
|
||||
fn validator_1() {
|
||||
assert_validator(
|
||||
r#"
|
||||
fn spend(datum: Data, redeemer: Data, ctx: Data) {
|
||||
True
|
||||
}
|
||||
"#,
|
||||
json!({
|
||||
"title": "foo",
|
||||
"purpose": "spend",
|
||||
"hash": "27dc8e44c17b4ae5f4b9286ab599fffe70e61b49dec61eaca1fc5898",
|
||||
"description": "Lorem ipsum",
|
||||
"redeemer": {
|
||||
"title": "Bar",
|
||||
"anyOf": [{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [{
|
||||
"dataType": "bytes"
|
||||
}]
|
||||
}],
|
||||
},
|
||||
"compiledCode": "46010000481501"
|
||||
"title": "test",
|
||||
"purpose": "spend",
|
||||
"hash": "cf2cd3bed32615bfecbd280618c1c1bec2198fc0f72b04f323a8a0d2",
|
||||
"datum": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"compiledCode": "58250100002105646174756d00210872656465656d657200210363747800533357349445261601"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::blueprint::Blueprint;
|
|||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
builtins::{self, generic_var},
|
||||
builtins,
|
||||
tipo::TypeInfo,
|
||||
IdGenerator,
|
||||
};
|
||||
|
@ -81,20 +81,9 @@ where
|
|||
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
||||
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
||||
|
||||
let mut functions = HashMap::new();
|
||||
for (access_key, func) in builtins::prelude_functions(&id_gen).into_iter() {
|
||||
functions.insert(access_key.to_owned(), func);
|
||||
}
|
||||
let functions = builtins::prelude_functions(&id_gen);
|
||||
|
||||
let mut data_types = HashMap::new();
|
||||
let option_data_type = TypedDataType::option(generic_var(id_gen.next()));
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
option_data_type,
|
||||
);
|
||||
let data_types = builtins::prelude_data_types(&id_gen);
|
||||
|
||||
let config = Config::load(&root)?;
|
||||
|
||||
|
|
|
@ -244,6 +244,12 @@ impl<'a> From<&'a CheckedModules> for &'a HashMap<String, CheckedModule> {
|
|||
}
|
||||
|
||||
impl CheckedModules {
|
||||
pub fn singleton(module: CheckedModule) -> Self {
|
||||
let mut modules = Self::default();
|
||||
modules.insert(module.name.clone(), module);
|
||||
modules
|
||||
}
|
||||
|
||||
pub fn validators(&self) -> impl Iterator<Item = (&CheckedModule, &TypedFunction)> {
|
||||
let mut items = vec![];
|
||||
for validator in self.0.values().filter(|module| module.kind.is_validator()) {
|
||||
|
|
Loading…
Reference in New Issue