Added benchmark keyword and unified Samplers and Fuzzers as Generator
This commit is contained in:
parent
d353e07ea1
commit
c0fabcd26a
|
@ -118,6 +118,7 @@ impl TypedModule {
|
|||
Definition::Use(_) => false,
|
||||
Definition::Test(_) => false,
|
||||
Definition::Validator(_) => false,
|
||||
Definition::Benchmark(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -134,6 +135,7 @@ impl TypedModule {
|
|||
Definition::Use(_) => false,
|
||||
Definition::Test(_) => false,
|
||||
Definition::Validator(_) => false,
|
||||
Definition::Benchmark(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -186,6 +188,16 @@ impl TypedModule {
|
|||
);
|
||||
}
|
||||
|
||||
Definition::Benchmark(benchmark) => {
|
||||
functions.insert(
|
||||
FunctionAccessKey {
|
||||
module_name: self.name.clone(),
|
||||
function_name: benchmark.name.clone(),
|
||||
},
|
||||
benchmark.clone().into(),
|
||||
);
|
||||
}
|
||||
|
||||
Definition::DataType(dt) => {
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
|
@ -246,6 +258,7 @@ fn str_to_keyword(word: &str) -> Option<Token> {
|
|||
"or" => Some(Token::Or),
|
||||
"validator" => Some(Token::Validator),
|
||||
"via" => Some(Token::Via),
|
||||
"benchmark" => Some(Token::Benchmark),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -832,6 +845,8 @@ pub enum Definition<T, Arg, Expr, PackageName> {
|
|||
|
||||
Test(Function<T, Expr, ArgVia<Arg, Expr>>),
|
||||
|
||||
Benchmark(Function<T, Expr, ArgVia<Arg, Expr>>),
|
||||
|
||||
Validator(Validator<T, Arg, Expr>),
|
||||
}
|
||||
|
||||
|
@ -844,6 +859,7 @@ impl<A, B, C, D> Definition<A, B, C, D> {
|
|||
| Definition::DataType(DataType { location, .. })
|
||||
| Definition::ModuleConstant(ModuleConstant { location, .. })
|
||||
| Definition::Validator(Validator { location, .. })
|
||||
| Definition::Benchmark(Function { location, .. })
|
||||
| Definition::Test(Function { location, .. }) => *location,
|
||||
}
|
||||
}
|
||||
|
@ -856,6 +872,7 @@ impl<A, B, C, D> Definition<A, B, C, D> {
|
|||
| Definition::DataType(DataType { doc, .. })
|
||||
| Definition::ModuleConstant(ModuleConstant { doc, .. })
|
||||
| Definition::Validator(Validator { doc, .. })
|
||||
| Definition::Benchmark(Function { doc, .. })
|
||||
| Definition::Test(Function { doc, .. }) => {
|
||||
let _ = std::mem::replace(doc, Some(new_doc));
|
||||
}
|
||||
|
@ -870,6 +887,7 @@ impl<A, B, C, D> Definition<A, B, C, D> {
|
|||
| Definition::DataType(DataType { doc, .. })
|
||||
| Definition::ModuleConstant(ModuleConstant { doc, .. })
|
||||
| Definition::Validator(Validator { doc, .. })
|
||||
| Definition::Benchmark(Function { doc, .. })
|
||||
| Definition::Test(Function { doc, .. }) => doc.clone(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@ pub const BOOL: &str = "Bool";
|
|||
pub const BOOL_CONSTRUCTORS: &[&str] = &["False", "True"];
|
||||
pub const BYTE_ARRAY: &str = "ByteArray";
|
||||
pub const DATA: &str = "Data";
|
||||
pub const FUZZER: &str = "Fuzzer";
|
||||
pub const SCALED_FUZZER: &str = "ScaledFuzzer";
|
||||
pub const GENERATOR: &str = "Generator";
|
||||
pub const G1_ELEMENT: &str = "G1Element";
|
||||
pub const G2_ELEMENT: &str = "G2Element";
|
||||
pub const INT: &str = "Int";
|
||||
|
@ -180,7 +179,7 @@ impl Type {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
|
||||
pub fn generator(c: Rc<Type>, a: Rc<Type>) -> Rc<Type> {
|
||||
let prng_annotation = Annotation::Constructor {
|
||||
location: Span::empty(),
|
||||
module: None,
|
||||
|
@ -189,64 +188,15 @@ impl Type {
|
|||
};
|
||||
|
||||
Rc::new(Type::Fn {
|
||||
args: vec![Type::prng()],
|
||||
args: vec![c, Type::prng()],
|
||||
ret: Type::option(Type::tuple(vec![Type::prng(), a])),
|
||||
alias: Some(
|
||||
TypeAliasAnnotation {
|
||||
alias: FUZZER.to_string(),
|
||||
parameters: vec!["a".to_string()],
|
||||
alias: GENERATOR.to_string(),
|
||||
parameters: vec!["c".to_string(), "a".to_string()],
|
||||
annotation: Annotation::Fn {
|
||||
location: Span::empty(),
|
||||
arguments: vec![prng_annotation.clone()],
|
||||
ret: Annotation::Constructor {
|
||||
location: Span::empty(),
|
||||
module: None,
|
||||
name: OPTION.to_string(),
|
||||
arguments: vec![Annotation::Tuple {
|
||||
location: Span::empty(),
|
||||
elems: vec![
|
||||
prng_annotation,
|
||||
Annotation::Var {
|
||||
location: Span::empty(),
|
||||
name: "a".to_string(),
|
||||
},
|
||||
],
|
||||
}],
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn scaled_fuzzer(a: Rc<Type>) -> Rc<Type> {
|
||||
let prng_annotation = Annotation::Constructor {
|
||||
location: Span::empty(),
|
||||
module: None,
|
||||
name: PRNG.to_string(),
|
||||
arguments: vec![],
|
||||
};
|
||||
|
||||
Rc::new(Type::Fn {
|
||||
args: vec![Type::prng(), Type::int()],
|
||||
ret: Type::option(Type::tuple(vec![Type::prng(), a])),
|
||||
alias: Some(
|
||||
TypeAliasAnnotation {
|
||||
alias: SCALED_FUZZER.to_string(),
|
||||
parameters: vec!["a".to_string()],
|
||||
annotation: Annotation::Fn {
|
||||
location: Span::empty(),
|
||||
arguments: vec![
|
||||
prng_annotation.clone(),
|
||||
Annotation::Constructor {
|
||||
location: Span::empty(),
|
||||
module: None,
|
||||
name: INT.to_string(),
|
||||
arguments: vec![],
|
||||
},
|
||||
],
|
||||
arguments: vec![Annotation::data(Span::empty()), prng_annotation.clone()],
|
||||
ret: Annotation::Constructor {
|
||||
location: Span::empty(),
|
||||
module: None,
|
||||
|
|
|
@ -477,33 +477,18 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
|
|||
),
|
||||
);
|
||||
|
||||
// Fuzzer
|
||||
// Generator
|
||||
//
|
||||
// pub type Fuzzer<a> =
|
||||
// fn(PRNG) -> Option<(PRNG, a)>
|
||||
let fuzzer_value = Type::generic_var(id_gen.next());
|
||||
// pub type Generator<c, a> =
|
||||
// fn(Data, PRNG) -> Option<(PRNG, a)>
|
||||
let generator_context = Type::generic_var(id_gen.next());
|
||||
let generator_value = Type::generic_var(id_gen.next());
|
||||
prelude.types.insert(
|
||||
well_known::FUZZER.to_string(),
|
||||
well_known::GENERATOR.to_string(),
|
||||
TypeConstructor {
|
||||
location: Span::empty(),
|
||||
parameters: vec![fuzzer_value.clone()],
|
||||
tipo: Type::fuzzer(fuzzer_value),
|
||||
module: "".to_string(),
|
||||
public: true,
|
||||
},
|
||||
);
|
||||
|
||||
// ScaledFuzzer
|
||||
//
|
||||
// pub type ScaledFuzzer<a> =
|
||||
// fn(PRNG, Int) -> Option<(PRNG, a)>
|
||||
let scaled_fuzzer_value = Type::generic_var(id_gen.next());
|
||||
prelude.types.insert(
|
||||
well_known::SCALED_FUZZER.to_string(),
|
||||
TypeConstructor {
|
||||
location: Span::empty(),
|
||||
parameters: vec![scaled_fuzzer_value.clone()],
|
||||
tipo: Type::scaled_fuzzer(scaled_fuzzer_value),
|
||||
parameters: vec![generator_context.clone(), generator_value.clone()],
|
||||
tipo: Type::generator(generator_context, generator_value),
|
||||
module: "".to_string(),
|
||||
public: true,
|
||||
},
|
||||
|
|
|
@ -258,6 +258,15 @@ impl<'comments> Formatter<'comments> {
|
|||
..
|
||||
}) => self.definition_test(name, args, body, *end_position, on_test_failure),
|
||||
|
||||
Definition::Benchmark(Function {
|
||||
name,
|
||||
arguments: args,
|
||||
body,
|
||||
end_position,
|
||||
on_test_failure,
|
||||
..
|
||||
}) => self.definition_benchmark(name, args, body, *end_position, on_test_failure),
|
||||
|
||||
Definition::TypeAlias(TypeAlias {
|
||||
alias,
|
||||
parameters: args,
|
||||
|
@ -631,6 +640,43 @@ impl<'comments> Formatter<'comments> {
|
|||
.append("}")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn definition_benchmark<'a>(
|
||||
&mut self,
|
||||
name: &'a str,
|
||||
args: &'a [UntypedArgVia],
|
||||
body: &'a UntypedExpr,
|
||||
end_location: usize,
|
||||
on_test_failure: &'a OnTestFailure,
|
||||
) -> Document<'a> {
|
||||
// Fn name and args
|
||||
let head = "benchmark "
|
||||
.to_doc()
|
||||
.append(name)
|
||||
.append(wrap_args(args.iter().map(|e| (self.fn_arg_via(e), false))))
|
||||
.append(match on_test_failure {
|
||||
OnTestFailure::FailImmediately => "",
|
||||
OnTestFailure::SucceedEventually => "",
|
||||
OnTestFailure::SucceedImmediately => "",
|
||||
})
|
||||
.group();
|
||||
|
||||
// Format body
|
||||
let body = self.expr(body, true);
|
||||
|
||||
// Add any trailing comments
|
||||
let body = match printed_comments(self.pop_comments(end_location), false) {
|
||||
Some(comments) => body.append(line()).append(comments),
|
||||
None => body,
|
||||
};
|
||||
|
||||
// Stick it all together
|
||||
head.append(" {")
|
||||
.append(line().append(body).nest(INDENT).group())
|
||||
.append(line())
|
||||
.append("}")
|
||||
}
|
||||
|
||||
fn definition_validator<'a>(
|
||||
&mut self,
|
||||
name: &'a str,
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
use crate::{
|
||||
ast,
|
||||
ast::OnTestFailure,
|
||||
expr::UntypedExpr,
|
||||
parser::{
|
||||
annotation,
|
||||
chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain},
|
||||
error::ParseError,
|
||||
expr::{self, bytearray, int as uint, list, string, tuple, var},
|
||||
pattern,
|
||||
token::Token,
|
||||
},
|
||||
};
|
||||
use chumsky::prelude::*;
|
||||
|
||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||
just(Token::Benchmark)
|
||||
.ignore_then(select! {Token::Name {name} => name})
|
||||
.then(
|
||||
via()
|
||||
.separated_by(just(Token::Comma))
|
||||
.allow_trailing()
|
||||
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
|
||||
)
|
||||
.then(
|
||||
just(Token::Fail)
|
||||
.ignore_then(just(Token::Once).ignored().or_not().map(|once| {
|
||||
once.map(|_| OnTestFailure::SucceedImmediately)
|
||||
.unwrap_or(OnTestFailure::SucceedEventually)
|
||||
}))
|
||||
.or_not(),
|
||||
)
|
||||
.map_with_span(|name, span| (name, span))
|
||||
.then(
|
||||
expr::sequence()
|
||||
.or_not()
|
||||
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
|
||||
)
|
||||
.map_with_span(|((((name, arguments), fail), span_end), body), span| {
|
||||
ast::UntypedDefinition::Benchmark(ast::Function {
|
||||
arguments,
|
||||
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
|
||||
doc: None,
|
||||
location: span_end,
|
||||
end_position: span.end - 1,
|
||||
name,
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
||||
choice((
|
||||
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Discarded {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
})
|
||||
}),
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, location| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location,
|
||||
})
|
||||
}),
|
||||
pattern().map(ast::ArgBy::ByPattern),
|
||||
))
|
||||
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||
.map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location))
|
||||
.then_ignore(just(Token::Via))
|
||||
.then(fuzzer())
|
||||
.map(|((by, annotation, location), via)| ast::ArgVia {
|
||||
arg: ast::UntypedArg {
|
||||
by,
|
||||
annotation,
|
||||
location,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
via,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fuzzer<'a>() -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
|
||||
recursive(|expression| {
|
||||
let chain = choice((
|
||||
tuple_index(),
|
||||
field_access::parser(),
|
||||
call(expression.clone()),
|
||||
));
|
||||
|
||||
let int = || {
|
||||
just(Token::Minus)
|
||||
.to(ast::UnOp::Negate)
|
||||
.map_with_span(|op, span| (op, span))
|
||||
.or_not()
|
||||
.then(uint())
|
||||
.map(|(op, value)| match op {
|
||||
None => value,
|
||||
Some((op, location)) => UntypedExpr::UnOp {
|
||||
op,
|
||||
location,
|
||||
value: Box::new(value),
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
choice((
|
||||
int(),
|
||||
string(),
|
||||
bytearray(),
|
||||
tuple(expression.clone()),
|
||||
list(expression.clone()),
|
||||
var(),
|
||||
))
|
||||
.then(chain.repeated())
|
||||
.foldl(|expr, chain| match chain {
|
||||
Chain::Call(args, span) => expr.call(args, span),
|
||||
Chain::FieldAccess(label, span) => expr.field_access(label, span),
|
||||
Chain::TupleIndex(index, span) => expr.tuple_index(index, span),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::assert_definition;
|
||||
|
||||
#[test]
|
||||
fn def_benchmark() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
benchmark foo(x via fuzz.any_int) {
|
||||
True
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_invalid_benchmark() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
benchmark foo(x via f, y via g) {
|
||||
True
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_benchmark_annotated_fuzzer() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
benchmark foo(x: Int via foo()) {
|
||||
True
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use chumsky::prelude::*;
|
||||
|
||||
pub mod benchmark;
|
||||
pub mod constant;
|
||||
mod data_type;
|
||||
mod function;
|
||||
|
@ -10,6 +11,7 @@ mod validator;
|
|||
|
||||
use super::{error::ParseError, token::Token};
|
||||
use crate::ast;
|
||||
pub use benchmark::parser as benchmark;
|
||||
pub use constant::parser as constant;
|
||||
pub use data_type::parser as data_type;
|
||||
pub use function::parser as function;
|
||||
|
@ -24,6 +26,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
|||
validator(),
|
||||
function(),
|
||||
test(),
|
||||
benchmark(),
|
||||
constant(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/parser/definition/benchmark.rs
|
||||
assertion_line: 136
|
||||
description: "Code:\n\nbenchmark foo(x via fuzz.any_int) {\n True\n}\n"
|
||||
snapshot_kind: text
|
||||
---
|
||||
Benchmark(
|
||||
Function {
|
||||
arguments: [
|
||||
ArgVia {
|
||||
arg: UntypedArg {
|
||||
by: ByName(
|
||||
Named {
|
||||
name: "x",
|
||||
label: "x",
|
||||
location: 14..15,
|
||||
},
|
||||
),
|
||||
location: 14..15,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
via: FieldAccess {
|
||||
location: 20..32,
|
||||
label: "any_int",
|
||||
container: Var {
|
||||
location: 20..24,
|
||||
name: "fuzz",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
body: Var {
|
||||
location: 40..44,
|
||||
name: "True",
|
||||
},
|
||||
doc: None,
|
||||
location: 0..33,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 45,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
54
crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap
vendored
Normal file
54
crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/parser/definition/benchmark.rs
|
||||
assertion_line: 158
|
||||
description: "Code:\n\nbenchmark foo(x: Int via foo()) {\n True\n}\n"
|
||||
snapshot_kind: text
|
||||
---
|
||||
Benchmark(
|
||||
Function {
|
||||
arguments: [
|
||||
ArgVia {
|
||||
arg: UntypedArg {
|
||||
by: ByName(
|
||||
Named {
|
||||
name: "x",
|
||||
label: "x",
|
||||
location: 14..15,
|
||||
},
|
||||
),
|
||||
location: 14..20,
|
||||
annotation: Some(
|
||||
Constructor {
|
||||
location: 17..20,
|
||||
module: None,
|
||||
name: "Int",
|
||||
arguments: [],
|
||||
},
|
||||
),
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
via: Call {
|
||||
arguments: [],
|
||||
fun: Var {
|
||||
location: 25..28,
|
||||
name: "foo",
|
||||
},
|
||||
location: 25..30,
|
||||
},
|
||||
},
|
||||
],
|
||||
body: Var {
|
||||
location: 38..42,
|
||||
name: "True",
|
||||
},
|
||||
doc: None,
|
||||
location: 0..31,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 43,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
62
crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap
vendored
Normal file
62
crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/parser/definition/benchmark.rs
|
||||
assertion_line: 147
|
||||
description: "Code:\n\nbenchmark foo(x via f, y via g) {\n True\n}\n"
|
||||
snapshot_kind: text
|
||||
---
|
||||
Benchmark(
|
||||
Function {
|
||||
arguments: [
|
||||
ArgVia {
|
||||
arg: UntypedArg {
|
||||
by: ByName(
|
||||
Named {
|
||||
name: "x",
|
||||
label: "x",
|
||||
location: 14..15,
|
||||
},
|
||||
),
|
||||
location: 14..15,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
via: Var {
|
||||
location: 20..21,
|
||||
name: "f",
|
||||
},
|
||||
},
|
||||
ArgVia {
|
||||
arg: UntypedArg {
|
||||
by: ByName(
|
||||
Named {
|
||||
name: "y",
|
||||
label: "y",
|
||||
location: 23..24,
|
||||
},
|
||||
),
|
||||
location: 23..24,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
via: Var {
|
||||
location: 29..30,
|
||||
name: "g",
|
||||
},
|
||||
},
|
||||
],
|
||||
body: Var {
|
||||
location: 38..42,
|
||||
name: "True",
|
||||
},
|
||||
doc: None,
|
||||
location: 0..31,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 43,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
|
@ -243,6 +243,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
|
|||
"when" => Token::When,
|
||||
"validator" => Token::Validator,
|
||||
"via" => Token::Via,
|
||||
"benchmark" => Token::Benchmark,
|
||||
_ => {
|
||||
if s.chars().next().map_or(false, |c| c.is_uppercase()) {
|
||||
Token::UpName {
|
||||
|
|
|
@ -73,6 +73,7 @@ pub enum Token {
|
|||
NewLine,
|
||||
// Keywords (alphabetically):
|
||||
As,
|
||||
Benchmark,
|
||||
Const,
|
||||
Fn,
|
||||
If,
|
||||
|
@ -182,6 +183,7 @@ impl fmt::Display for Token {
|
|||
Token::Once => "once",
|
||||
Token::Validator => "validator",
|
||||
Token::Via => "via",
|
||||
Token::Benchmark => "benchmark",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ use vec1::{vec1, Vec1};
|
|||
pub enum Test {
|
||||
UnitTest(UnitTest),
|
||||
PropertyTest(PropertyTest),
|
||||
Benchmark(Benchmark)
|
||||
}
|
||||
|
||||
unsafe impl Send for Test {}
|
||||
|
@ -442,7 +443,23 @@ impl PropertyTest {
|
|||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ----- Benchmark -----------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Benchmark {
|
||||
pub input_path: PathBuf,
|
||||
pub module: String,
|
||||
pub name: String,
|
||||
pub on_test_failure: OnTestFailure,
|
||||
pub program: Program<Name>,
|
||||
pub fuzzer: Fuzzer<Name>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Benchmark {}
|
||||
|
||||
impl Benchmark {
|
||||
pub fn benchmark(
|
||||
self,
|
||||
seed: u32,
|
||||
|
@ -484,6 +501,14 @@ impl PropertyTest {
|
|||
|
||||
results
|
||||
}
|
||||
|
||||
pub fn eval(&self, value: &PlutusData, plutus_version: &PlutusVersion) -> EvalResult {
|
||||
let program = self.program.apply_data(value.clone());
|
||||
|
||||
Program::<NamedDeBruijn>::try_from(program)
|
||||
.unwrap()
|
||||
.eval_version(ExBudget::max(), &plutus_version.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// ----- PRNG -----------------------------------------------------------------
|
||||
|
@ -558,7 +583,10 @@ impl Prng {
|
|||
choices: vec![],
|
||||
uplc: Data::constr(
|
||||
Prng::SEEDED,
|
||||
vec![Data::bytestring(digest.to_vec()), Data::bytestring(vec![])],
|
||||
vec![
|
||||
Data::bytestring(digest.to_vec()), // Prng's seed
|
||||
Data::bytestring(vec![]), // Random choices
|
||||
],
|
||||
),
|
||||
iteration: 0,
|
||||
}
|
||||
|
@ -585,35 +613,18 @@ impl Prng {
|
|||
fuzzer: &Program<Name>,
|
||||
iteration: usize,
|
||||
) -> Result<Option<(Prng, PlutusData)>, FuzzerError> {
|
||||
// First try evaluating as a regular fuzzer
|
||||
let program = Program::<NamedDeBruijn>::try_from(fuzzer.apply_data(self.uplc())).unwrap();
|
||||
let program_clone = program.clone();
|
||||
|
||||
let result = program.eval(ExBudget::max());
|
||||
|
||||
match result.result() {
|
||||
Ok(term) if matches!(term, Term::Constant(_)) => {
|
||||
// If we got a valid constant result, process it
|
||||
Ok(Prng::from_result(term, iteration))
|
||||
}
|
||||
_ => {
|
||||
// Use the cloned program for the second attempt
|
||||
let program_with_iteration = program_clone
|
||||
.apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64)));
|
||||
|
||||
let mut result = program_with_iteration.eval(ExBudget::max());
|
||||
match result.result() {
|
||||
Ok(term) if matches!(term, Term::Constant(_)) => {
|
||||
Ok(Prng::from_result(term, iteration))
|
||||
}
|
||||
Err(uplc_error) => Err(FuzzerError {
|
||||
traces: result.logs(),
|
||||
uplc_error,
|
||||
}),
|
||||
_ => unreachable!("Fuzzer returned a malformed result? {result:#?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
let program = Program::<NamedDeBruijn>::try_from(
|
||||
fuzzer
|
||||
.apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64)))
|
||||
.apply_data(self.uplc())).unwrap();
|
||||
let mut result = program.eval(ExBudget::max());
|
||||
result
|
||||
.result()
|
||||
.map_err(|uplc_error| FuzzerError {
|
||||
traces: result.logs(),
|
||||
uplc_error,
|
||||
})
|
||||
.map(|term| Prng::from_result(term, iteration))
|
||||
}
|
||||
|
||||
/// Obtain a Prng back from a fuzzer execution. As a reminder, fuzzers have the following
|
||||
|
@ -1431,7 +1442,7 @@ impl Assertion<UntypedExpr> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BenchmarkResult {
|
||||
pub test: PropertyTest,
|
||||
pub test: Benchmark,
|
||||
pub cost: ExBudget,
|
||||
pub success: bool,
|
||||
pub traces: Vec<String>,
|
||||
|
|
|
@ -1773,7 +1773,7 @@ fn pipe_wrong_arity_fully_saturated_return_fn() {
|
|||
#[test]
|
||||
fn fuzzer_ok_basic() {
|
||||
let source_code = r#"
|
||||
fn int() -> Fuzzer<Int> { todo }
|
||||
fn int() -> Generator<Void, Int> { todo }
|
||||
test prop(n via int()) { True }
|
||||
"#;
|
||||
|
||||
|
@ -1783,7 +1783,7 @@ fn fuzzer_ok_basic() {
|
|||
#[test]
|
||||
fn fuzzer_ok_explicit() {
|
||||
let source_code = r#"
|
||||
fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo }
|
||||
fn int(void: Void, prng: PRNG) -> Option<(PRNG, Int)> { todo }
|
||||
test prop(n via int) { Void }
|
||||
"#;
|
||||
|
||||
|
@ -1793,8 +1793,8 @@ fn fuzzer_ok_explicit() {
|
|||
#[test]
|
||||
fn fuzzer_ok_list() {
|
||||
let source_code = r#"
|
||||
fn int() -> Fuzzer<Int> { todo }
|
||||
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||
fn int() -> Generator<Void, Int> { todo }
|
||||
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo }
|
||||
|
||||
test prop(xs via list(int())) { True }
|
||||
"#;
|
||||
|
@ -1805,8 +1805,8 @@ fn fuzzer_ok_list() {
|
|||
#[test]
|
||||
fn fuzzer_err_unbound() {
|
||||
let source_code = r#"
|
||||
fn any() -> Fuzzer<a> { todo }
|
||||
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||
fn any() -> Generator<Void, a> { todo }
|
||||
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo }
|
||||
|
||||
test prop(xs via list(any())) { todo }
|
||||
"#;
|
||||
|
@ -1838,7 +1838,7 @@ fn fuzzer_err_unify_1() {
|
|||
#[test]
|
||||
fn fuzzer_err_unify_2() {
|
||||
let source_code = r#"
|
||||
fn any() -> Fuzzer<a> { todo }
|
||||
fn any() -> Generator<Void, a> { todo }
|
||||
test prop(xs via any) { todo }
|
||||
"#;
|
||||
|
||||
|
@ -1857,8 +1857,8 @@ fn fuzzer_err_unify_2() {
|
|||
#[test]
|
||||
fn fuzzer_err_unify_3() {
|
||||
let source_code = r#"
|
||||
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||
fn int() -> Fuzzer<Int> { todo }
|
||||
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo }
|
||||
fn int() -> Generator<Void, Int> { todo }
|
||||
|
||||
test prop(xs: Int via list(int())) { todo }
|
||||
"#;
|
||||
|
@ -1875,45 +1875,6 @@ fn fuzzer_err_unify_3() {
|
|||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaled_fuzzer_ok_basic() {
|
||||
let source_code = r#"
|
||||
fn int() -> ScaledFuzzer<Int> { todo }
|
||||
test prop(n via int()) { True }
|
||||
"#;
|
||||
|
||||
assert!(check(parse(source_code)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaled_fuzzer_ok_explicit() {
|
||||
let source_code = r#"
|
||||
fn int(prng: PRNG, complexity: Int) -> Option<(PRNG, Int)> { todo }
|
||||
test prop(n via int) { True }
|
||||
"#;
|
||||
|
||||
assert!(check(parse(source_code)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaled_fuzzer_err_unify() {
|
||||
let source_code = r#"
|
||||
fn int() -> ScaledFuzzer<Int> { todo }
|
||||
test prop(n: Bool via int()) { True }
|
||||
"#;
|
||||
|
||||
assert!(matches!(
|
||||
check(parse(source_code)),
|
||||
Err((
|
||||
_,
|
||||
Error::CouldNotUnify {
|
||||
situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch),
|
||||
..
|
||||
}
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf8_hex_literal_warning() {
|
||||
let source_code = r#"
|
||||
|
|
|
@ -367,6 +367,7 @@ impl<'a> Environment<'a> {
|
|||
| Definition::DataType { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::Test { .. }
|
||||
| Definition::Benchmark { .. }
|
||||
| Definition::ModuleConstant { .. }) => definition,
|
||||
}
|
||||
}
|
||||
|
@ -1061,6 +1062,7 @@ impl<'a> Environment<'a> {
|
|||
| Definition::Validator { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::ModuleConstant { .. }
|
||||
| Definition::Benchmark { .. }
|
||||
| Definition::Test { .. } => None,
|
||||
})
|
||||
.collect::<Vec<Span>>();
|
||||
|
@ -1184,6 +1186,7 @@ impl<'a> Environment<'a> {
|
|||
Definition::Fn { .. }
|
||||
| Definition::Validator { .. }
|
||||
| Definition::Test { .. }
|
||||
| Definition::Benchmark { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::ModuleConstant { .. } => {}
|
||||
}
|
||||
|
@ -1387,6 +1390,24 @@ impl<'a> Environment<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
Definition::Benchmark(benchmark) => {
|
||||
let arguments = benchmark
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|arg| arg.clone().into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.register_function(
|
||||
&benchmark.name,
|
||||
&arguments,
|
||||
&benchmark.return_annotation,
|
||||
module_name,
|
||||
hydrators,
|
||||
names,
|
||||
&benchmark.location,
|
||||
)?;
|
||||
}
|
||||
|
||||
Definition::Test(test) => {
|
||||
let arguments = test
|
||||
.arguments
|
||||
|
|
|
@ -81,6 +81,7 @@ impl UntypedModule {
|
|||
Definition::Validator { .. } => (),
|
||||
Definition::Fn { .. }
|
||||
| Definition::Test { .. }
|
||||
| Definition::Benchmark { .. }
|
||||
| Definition::TypeAlias { .. }
|
||||
| Definition::DataType { .. }
|
||||
| Definition::Use { .. } => not_consts.push(def),
|
||||
|
@ -364,15 +365,7 @@ fn infer_definition(
|
|||
&arg.via.location(),
|
||||
) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => match err {
|
||||
Error::CouldNotUnify { .. } => infer_scaled_fuzzer(
|
||||
environment,
|
||||
provided_inner_type.clone(),
|
||||
&typed_via.tipo(),
|
||||
&arg.via.location(),
|
||||
),
|
||||
_ => Err(err),
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
}?;
|
||||
|
||||
// Ensure that the annotation, if any, matches the type inferred from the
|
||||
|
@ -475,6 +468,142 @@ fn infer_definition(
|
|||
}))
|
||||
}
|
||||
|
||||
Definition::Benchmark(f) => {
|
||||
let (typed_via, annotation) = match f.arguments.first() {
|
||||
Some(arg) => {
|
||||
if f.arguments.len() > 1 {
|
||||
return Err(Error::IncorrectTestArity {
|
||||
count: f.arguments.len(),
|
||||
location: f
|
||||
.arguments
|
||||
.get(1)
|
||||
.expect("arguments.len() > 1")
|
||||
.arg
|
||||
.location,
|
||||
});
|
||||
}
|
||||
|
||||
let typed_via = ExprTyper::new(environment, tracing).infer(arg.via.clone())?;
|
||||
|
||||
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
||||
|
||||
let provided_inner_type = arg
|
||||
.arg
|
||||
.annotation
|
||||
.as_ref()
|
||||
.map(|ann| hydrator.type_from_annotation(ann, environment))
|
||||
.transpose()?;
|
||||
|
||||
let (inferred_annotation, inferred_inner_type) = match infer_sampler(
|
||||
environment,
|
||||
provided_inner_type.clone(),
|
||||
&typed_via.tipo(),
|
||||
&arg.via.location(),
|
||||
) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err(err)
|
||||
}?;
|
||||
|
||||
// Ensure that the annotation, if any, matches the type inferred from the
|
||||
// Fuzzer.
|
||||
if let Some(provided_inner_type) = provided_inner_type {
|
||||
if !arg
|
||||
.arg
|
||||
.annotation
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.is_logically_equal(&inferred_annotation)
|
||||
{
|
||||
return Err(Error::CouldNotUnify {
|
||||
location: arg.arg.location,
|
||||
expected: inferred_inner_type.clone(),
|
||||
given: provided_inner_type.clone(),
|
||||
situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch),
|
||||
rigid_type_names: hydrator.rigid_names(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the pre-registered type for the test function, to allow inferring
|
||||
// the function body with the right type arguments.
|
||||
let scope = environment
|
||||
.scope
|
||||
.get_mut(&f.name)
|
||||
.expect("Could not find preregistered type for benchmark");
|
||||
if let Type::Fn {
|
||||
ref ret,
|
||||
ref alias,
|
||||
args: _,
|
||||
} = scope.tipo.as_ref()
|
||||
{
|
||||
scope.tipo = Rc::new(Type::Fn {
|
||||
ret: ret.clone(),
|
||||
args: vec![inferred_inner_type.clone()],
|
||||
alias: alias.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok((
|
||||
Some((typed_via, inferred_inner_type)),
|
||||
Some(inferred_annotation),
|
||||
))
|
||||
}
|
||||
None => Ok((None, None)),
|
||||
}?;
|
||||
|
||||
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
|
||||
|
||||
let is_bool = environment.unify(
|
||||
typed_f.return_type.clone(),
|
||||
Type::bool(),
|
||||
typed_f.location,
|
||||
false,
|
||||
);
|
||||
|
||||
let is_void = environment.unify(
|
||||
typed_f.return_type.clone(),
|
||||
Type::void(),
|
||||
typed_f.location,
|
||||
false,
|
||||
);
|
||||
|
||||
if is_bool.or(is_void).is_err() {
|
||||
return Err(Error::IllegalTestType {
|
||||
location: typed_f.location,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Definition::Test(Function {
|
||||
doc: typed_f.doc,
|
||||
location: typed_f.location,
|
||||
name: typed_f.name,
|
||||
public: typed_f.public,
|
||||
arguments: match typed_via {
|
||||
Some((via, tipo)) => {
|
||||
let arg = typed_f
|
||||
.arguments
|
||||
.first()
|
||||
.expect("has exactly one argument")
|
||||
.to_owned();
|
||||
vec![ArgVia {
|
||||
arg: TypedArg {
|
||||
tipo,
|
||||
annotation,
|
||||
..arg
|
||||
},
|
||||
via,
|
||||
}]
|
||||
}
|
||||
None => vec![],
|
||||
},
|
||||
return_annotation: typed_f.return_annotation,
|
||||
return_type: typed_f.return_type,
|
||||
body: typed_f.body,
|
||||
on_test_failure: typed_f.on_test_failure,
|
||||
end_position: typed_f.end_position,
|
||||
}))
|
||||
}
|
||||
|
||||
Definition::TypeAlias(TypeAlias {
|
||||
doc,
|
||||
location,
|
||||
|
@ -709,7 +838,8 @@ fn infer_fuzzer(
|
|||
) -> Result<(Annotation, Rc<Type>), Error> {
|
||||
let could_not_unify = || Error::CouldNotUnify {
|
||||
location: *location,
|
||||
expected: Type::fuzzer(
|
||||
expected: Type::generator(
|
||||
Type::void(),
|
||||
expected_inner_type
|
||||
.clone()
|
||||
.unwrap_or_else(|| Type::generic_var(0)),
|
||||
|
@ -733,7 +863,7 @@ fn infer_fuzzer(
|
|||
contains_opaque: _,
|
||||
alias: _,
|
||||
} if module.is_empty() && name == "Option" && args.len() == 1 => {
|
||||
match args.first().expect("args.len() == 1").borrow() {
|
||||
match args.first().expect("args.len() == 2 && args[0].is_void()").borrow() {
|
||||
Type::Tuple { elems, .. } if elems.len() == 2 => {
|
||||
let wrapped = elems.get(1).expect("Tuple has two elements");
|
||||
|
||||
|
@ -748,7 +878,7 @@ fn infer_fuzzer(
|
|||
// `unify` now that we have figured out the type carried by the fuzzer.
|
||||
environment.unify(
|
||||
tipo.clone(),
|
||||
Type::fuzzer(wrapped.clone()),
|
||||
Type::generator(Type::void(), wrapped.clone()),
|
||||
*location,
|
||||
false,
|
||||
)?;
|
||||
|
@ -777,6 +907,75 @@ fn infer_fuzzer(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn infer_sampler(
|
||||
environment: &mut Environment<'_>,
|
||||
expected_inner_type: Option<Rc<Type>>,
|
||||
tipo: &Rc<Type>,
|
||||
location: &Span,
|
||||
) -> Result<(Annotation, Rc<Type>), Error> {
|
||||
let could_not_unify = || Error::CouldNotUnify {
|
||||
location: *location,
|
||||
expected: Type::generator(
|
||||
Type::int(),
|
||||
expected_inner_type
|
||||
.clone()
|
||||
.unwrap_or_else(|| Type::generic_var(0)),
|
||||
),
|
||||
given: tipo.clone(),
|
||||
situation: None,
|
||||
rigid_type_names: HashMap::new(),
|
||||
};
|
||||
|
||||
match tipo.borrow() {
|
||||
Type::Fn {
|
||||
ret,
|
||||
args: _,
|
||||
alias: _,
|
||||
} => match ret.borrow() {
|
||||
Type::App {
|
||||
module,
|
||||
name,
|
||||
args,
|
||||
public: _,
|
||||
contains_opaque: _,
|
||||
alias: _,
|
||||
} if module.is_empty() && name == "Option" && args.len() == 1 => {
|
||||
match args.first().expect("args.len() == 2 && args[0].is_int()").borrow() {
|
||||
Type::Tuple { elems, .. } if elems.len() == 2 => {
|
||||
let wrapped = elems.get(1).expect("Tuple has two elements");
|
||||
|
||||
environment.unify(
|
||||
tipo.clone(),
|
||||
Type::generator(Type::int(), wrapped.clone()),
|
||||
*location,
|
||||
false,
|
||||
)?;
|
||||
|
||||
Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone()))
|
||||
}
|
||||
_ => Err(could_not_unify()),
|
||||
}
|
||||
}
|
||||
_ => Err(could_not_unify()),
|
||||
},
|
||||
|
||||
Type::Var { tipo, alias } => match &*tipo.deref().borrow() {
|
||||
TypeVar::Link { tipo } => infer_sampler(
|
||||
environment,
|
||||
expected_inner_type,
|
||||
&Type::with_alias(tipo.clone(), alias.clone()),
|
||||
location,
|
||||
),
|
||||
_ => Err(Error::GenericLeftAtBoundary {
|
||||
location: *location,
|
||||
}),
|
||||
},
|
||||
|
||||
Type::App { .. } | Type::Tuple { .. } | Type::Pair { .. } => Err(could_not_unify()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
|
||||
match tipo {
|
||||
|
@ -837,75 +1036,6 @@ fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn infer_scaled_fuzzer(
|
||||
environment: &mut Environment<'_>,
|
||||
expected_inner_type: Option<Rc<Type>>,
|
||||
tipo: &Rc<Type>,
|
||||
location: &Span,
|
||||
) -> Result<(Annotation, Rc<Type>), Error> {
|
||||
let could_not_unify = || Error::CouldNotUnify {
|
||||
location: *location,
|
||||
expected: Type::scaled_fuzzer(
|
||||
expected_inner_type
|
||||
.clone()
|
||||
.unwrap_or_else(|| Type::generic_var(0)),
|
||||
),
|
||||
given: tipo.clone(),
|
||||
situation: None,
|
||||
rigid_type_names: Default::default(),
|
||||
};
|
||||
|
||||
match tipo.borrow() {
|
||||
Type::Fn { ret, args, .. } => {
|
||||
// Check if this is a ScaledFuzzer (fn(PRNG, Int) -> Option<(PRNG, a)>)
|
||||
if args.len() == 2 {
|
||||
match ret.borrow() {
|
||||
Type::App {
|
||||
module,
|
||||
name,
|
||||
args: ret_args,
|
||||
..
|
||||
} if module.is_empty() && name == "Option" && ret_args.len() == 1 => {
|
||||
if let Type::Tuple { elems, .. } = ret_args[0].borrow() {
|
||||
if elems.len() == 2 {
|
||||
let wrapped = &elems[1];
|
||||
|
||||
// Unify with expected ScaledFuzzer type
|
||||
environment.unify(
|
||||
tipo.clone(),
|
||||
Type::scaled_fuzzer(wrapped.clone()),
|
||||
*location,
|
||||
false,
|
||||
)?;
|
||||
|
||||
return Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Err(could_not_unify())
|
||||
}
|
||||
|
||||
Type::Var { tipo, alias } => match &*tipo.deref().borrow() {
|
||||
TypeVar::Link { tipo } => infer_scaled_fuzzer(
|
||||
environment,
|
||||
expected_inner_type,
|
||||
&Type::with_alias(tipo.clone(), alias.clone()),
|
||||
location,
|
||||
),
|
||||
_ => Err(Error::GenericLeftAtBoundary {
|
||||
location: *location,
|
||||
}),
|
||||
},
|
||||
|
||||
Type::App { .. } | Type::Tuple { .. } | Type::Pair { .. } => Err(could_not_unify()),
|
||||
}
|
||||
}
|
||||
|
||||
fn put_params_in_scope<'a>(
|
||||
name: &'_ str,
|
||||
environment: &'a mut Environment,
|
||||
|
|
|
@ -540,6 +540,7 @@ fn find_data_type(name: &str, definitions: &[TypedDefinition]) -> Option<TypedDa
|
|||
| Definition::TypeAlias { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::ModuleConstant { .. }
|
||||
| Definition::Benchmark { .. }
|
||||
| Definition::Test { .. } => continue,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ impl Error {
|
|||
test.input_path.to_path_buf(),
|
||||
test.program.to_pretty(),
|
||||
),
|
||||
TestResult::Benchmark(_) => ("benchmark".to_string(), PathBuf::new(), String::new()),
|
||||
TestResult::Benchmark(_) => ("benchmark".to_string(), PathBuf::new(), String::new()), // todo
|
||||
};
|
||||
|
||||
Error::TestFailure {
|
||||
|
|
|
@ -302,7 +302,7 @@ where
|
|||
match_tests: Option<Vec<String>>,
|
||||
exact_match: bool,
|
||||
seed: u32,
|
||||
property_max_success: usize,
|
||||
times_to_run: usize,
|
||||
env: Option<String>,
|
||||
output: PathBuf,
|
||||
) -> Result<(), Vec<Error>> {
|
||||
|
@ -313,7 +313,7 @@ where
|
|||
match_tests,
|
||||
exact_match,
|
||||
seed,
|
||||
property_max_success,
|
||||
times_to_run,
|
||||
output,
|
||||
},
|
||||
blueprint_path: self.blueprint_path(None),
|
||||
|
@ -470,16 +470,17 @@ where
|
|||
match_tests,
|
||||
exact_match,
|
||||
seed,
|
||||
property_max_success,
|
||||
times_to_run,
|
||||
output,
|
||||
} => {
|
||||
let tests = self.collect_tests(false, match_tests, exact_match, options.tracing)?;
|
||||
// todo - collect benchmarks
|
||||
let tests = self.collect_benchmarks(false, match_tests, exact_match, options.tracing)?;
|
||||
|
||||
if !tests.is_empty() {
|
||||
self.event_listener.handle_event(Event::RunningBenchmarks);
|
||||
}
|
||||
|
||||
let tests = self.run_benchmarks(tests, seed, property_max_success);
|
||||
let tests = self.run_benchmarks(tests, seed, times_to_run);
|
||||
|
||||
let errors: Vec<Error> = tests
|
||||
.iter()
|
||||
|
@ -994,6 +995,107 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_benchmarks(
|
||||
&mut self,
|
||||
verbose: bool,
|
||||
match_tests: Option<Vec<String>>,
|
||||
exact_match: bool,
|
||||
tracing: Tracing,
|
||||
) -> Result<Vec<Test>, Error> {
|
||||
let mut scripts = Vec::new();
|
||||
|
||||
let match_tests = match_tests.map(|mt| {
|
||||
mt.into_iter()
|
||||
.map(|match_test| {
|
||||
let mut match_split_dot = match_test.split('.');
|
||||
|
||||
let match_module = if match_test.contains('.') || match_test.contains('/') {
|
||||
match_split_dot.next().unwrap_or("")
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let match_names = match_split_dot.next().map(|names| {
|
||||
let names = names.replace(&['{', '}'][..], "");
|
||||
|
||||
let names_split_comma = names.split(',');
|
||||
|
||||
names_split_comma.map(str::to_string).collect()
|
||||
});
|
||||
|
||||
(match_module.to_string(), match_names)
|
||||
})
|
||||
.collect::<Vec<(String, Option<Vec<String>>)>>()
|
||||
});
|
||||
|
||||
for checked_module in self.checked_modules.values() {
|
||||
if checked_module.package != self.config.name.to_string() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for def in checked_module.ast.definitions() {
|
||||
if let Definition::Benchmark(func) = def {
|
||||
if let Some(match_tests) = &match_tests {
|
||||
let is_match = match_tests.iter().any(|(module, names)| {
|
||||
let matched_module =
|
||||
module.is_empty() || checked_module.name.contains(module);
|
||||
|
||||
let matched_name = match names {
|
||||
None => true,
|
||||
Some(names) => names.iter().any(|name| {
|
||||
if exact_match {
|
||||
name == &func.name
|
||||
} else {
|
||||
func.name.contains(name)
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
matched_module && matched_name
|
||||
});
|
||||
|
||||
if is_match {
|
||||
scripts.push((
|
||||
checked_module.input_path.clone(),
|
||||
checked_module.name.clone(),
|
||||
func,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
scripts.push((
|
||||
checked_module.input_path.clone(),
|
||||
checked_module.name.clone(),
|
||||
func,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut generator = self.new_generator(tracing);
|
||||
|
||||
let mut tests = Vec::new();
|
||||
|
||||
for (input_path, module_name, test) in scripts.into_iter() {
|
||||
if verbose {
|
||||
// TODO: We may want to handle the event listener differently for benchmarks
|
||||
self.event_listener.handle_event(Event::GeneratingUPLCFor {
|
||||
name: test.name.clone(),
|
||||
path: input_path.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
tests.push(Test::from_function_definition(
|
||||
&mut generator,
|
||||
test.to_owned(),
|
||||
module_name,
|
||||
input_path,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(tests)
|
||||
}
|
||||
|
||||
fn collect_tests(
|
||||
&mut self,
|
||||
verbose: bool,
|
||||
|
@ -1113,6 +1215,7 @@ where
|
|||
Test::PropertyTest(property_test) => {
|
||||
property_test.run(seed, property_max_success, plutus_version)
|
||||
}
|
||||
Test::Benchmark(_) => unreachable!("Benchmarks cannot be run in PBT.")
|
||||
})
|
||||
.collect::<Vec<TestResult<(Constant, Rc<Type>), PlutusData>>>()
|
||||
.into_iter()
|
||||
|
@ -1134,8 +1237,8 @@ where
|
|||
tests
|
||||
.into_par_iter()
|
||||
.flat_map(|test| match test {
|
||||
Test::UnitTest(_) => Vec::new(),
|
||||
Test::PropertyTest(property_test) => property_test
|
||||
Test::UnitTest(_) | Test::PropertyTest(_) => unreachable!("Tests cannot be ran during benchmarking."),
|
||||
Test::Benchmark(benchmark) => benchmark
|
||||
.benchmark(seed, property_max_success, plutus_version)
|
||||
.into_iter()
|
||||
.map(TestResult::Benchmark)
|
||||
|
|
|
@ -33,7 +33,7 @@ pub enum CodeGenMode {
|
|||
match_tests: Option<Vec<String>>,
|
||||
exact_match: bool,
|
||||
seed: u32,
|
||||
property_max_success: usize,
|
||||
times_to_run: usize,
|
||||
output: PathBuf,
|
||||
},
|
||||
NoOp,
|
||||
|
|
|
@ -112,8 +112,10 @@ mod test {
|
|||
|
||||
const max_int: Int = 255
|
||||
|
||||
pub type Fuzzer<a> = Generator<Void, a>
|
||||
|
||||
pub fn int() -> Fuzzer<Int> {
|
||||
fn(prng: PRNG) -> Option<(PRNG, Int)> {
|
||||
fn(v: Void, prng: PRNG) -> Option<(PRNG, Int)> {
|
||||
when prng is {
|
||||
Seeded { seed, choices } -> {
|
||||
let choice =
|
||||
|
@ -161,21 +163,21 @@ mod test {
|
|||
}
|
||||
|
||||
pub fn constant(a: a) -> Fuzzer<a> {
|
||||
fn(s0) { Some((s0, a)) }
|
||||
fn(v, s0) { Some((s0, a)) }
|
||||
}
|
||||
|
||||
pub fn and_then(fuzz_a: Fuzzer<a>, f: fn(a) -> Fuzzer<b>) -> Fuzzer<b> {
|
||||
fn(s0) {
|
||||
when fuzz_a(s0) is {
|
||||
Some((s1, a)) -> f(a)(s1)
|
||||
fn(v, s0) {
|
||||
when fuzz_a(v, s0) is {
|
||||
Some((s1, a)) -> f(a)(v, s1)
|
||||
None -> None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map(fuzz_a: Fuzzer<a>, f: fn(a) -> b) -> Fuzzer<b> {
|
||||
fn(s0) {
|
||||
when fuzz_a(s0) is {
|
||||
fn(v, s0) {
|
||||
when fuzz_a(v, s0) is {
|
||||
Some((s1, a)) -> Some((s1, f(a)))
|
||||
None -> None
|
||||
}
|
||||
|
@ -183,10 +185,10 @@ mod test {
|
|||
}
|
||||
|
||||
pub fn map2(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<b>, f: fn(a, b) -> c) -> Fuzzer<c> {
|
||||
fn(s0) {
|
||||
when fuzz_a(s0) is {
|
||||
fn(v, s0) {
|
||||
when fuzz_a(v, s0) is {
|
||||
Some((s1, a)) ->
|
||||
when fuzz_b(s1) is {
|
||||
when fuzz_b(Void, s1) is {
|
||||
Some((s2, b)) -> Some((s2, f(a, b)))
|
||||
None -> None
|
||||
}
|
||||
|
@ -214,6 +216,9 @@ mod test {
|
|||
(Test::UnitTest(..), _) => {
|
||||
panic!("Expected to yield a PropertyTest but found a UnitTest")
|
||||
}
|
||||
(Test::Benchmark(..), _) => {
|
||||
panic!("Expected to yield a PropertyTest but found a Benchmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ pub struct Args {
|
|||
#[clap(long)]
|
||||
seed: Option<u32>,
|
||||
|
||||
/// Maximum number of successful test run for considering a property-based test valid.
|
||||
/// How many times we will run each benchmark in the relevant project.
|
||||
#[clap(long, default_value_t = PropertyTest::DEFAULT_MAX_SUCCESS)]
|
||||
max_success: usize,
|
||||
times_to_run: usize,
|
||||
|
||||
/// Only run tests if they match any of these strings.
|
||||
/// You can match a module with `-m aiken/list` or `-m list`.
|
||||
|
@ -46,7 +46,7 @@ pub fn exec(
|
|||
match_tests,
|
||||
exact_match,
|
||||
seed,
|
||||
max_success,
|
||||
times_to_run,
|
||||
env,
|
||||
output,
|
||||
}: Args,
|
||||
|
@ -65,7 +65,7 @@ pub fn exec(
|
|||
match_tests.clone(),
|
||||
exact_match,
|
||||
seed,
|
||||
max_success,
|
||||
times_to_run,
|
||||
env.clone(),
|
||||
output.clone(),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue