Introduce 'fail once' and alter behavior of 'fail' keyword for properties.
This commit is contained in:
parent
28515e70ec
commit
5694d9f9cb
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.29-alpha - UNRELEASED
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **aiken-lang**: the keyword `fail` on property-based test semantic has changed and now consider a test to succeed only if **every** execution of the test failed (instead of just one). The previous behavior can be recovered by adding the keyword `once` after `fail`. @KtorZ
|
||||||
|
|
||||||
## v1.0.28-alpha - 2024-05-23
|
## v1.0.28-alpha - 2024-05-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -223,6 +223,13 @@ pub type UntypedFunction = Function<(), UntypedExpr, UntypedArg>;
|
||||||
pub type TypedTest = Function<Rc<Type>, TypedExpr, TypedArgVia>;
|
pub type TypedTest = Function<Rc<Type>, TypedExpr, TypedArgVia>;
|
||||||
pub type UntypedTest = Function<(), UntypedExpr, UntypedArgVia>;
|
pub type UntypedTest = Function<(), UntypedExpr, UntypedArgVia>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum OnTestFailure {
|
||||||
|
FailImmediately,
|
||||||
|
SucceedImmediately,
|
||||||
|
SucceedEventually,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Function<T, Expr, Arg> {
|
pub struct Function<T, Expr, Arg> {
|
||||||
pub arguments: Vec<Arg>,
|
pub arguments: Vec<Arg>,
|
||||||
|
@ -234,7 +241,7 @@ pub struct Function<T, Expr, Arg> {
|
||||||
pub return_annotation: Option<Annotation>,
|
pub return_annotation: Option<Annotation>,
|
||||||
pub return_type: T,
|
pub return_type: T,
|
||||||
pub end_position: usize,
|
pub end_position: usize,
|
||||||
pub can_error: bool,
|
pub on_test_failure: OnTestFailure,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedFunction {
|
impl TypedFunction {
|
||||||
|
@ -279,7 +286,7 @@ impl From<UntypedTest> for UntypedFunction {
|
||||||
return_annotation: f.return_annotation,
|
return_annotation: f.return_annotation,
|
||||||
return_type: f.return_type,
|
return_type: f.return_type,
|
||||||
body: f.body,
|
body: f.body,
|
||||||
can_error: f.can_error,
|
on_test_failure: f.on_test_failure,
|
||||||
end_position: f.end_position,
|
end_position: f.end_position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,7 +303,7 @@ impl From<TypedTest> for TypedFunction {
|
||||||
return_annotation: f.return_annotation,
|
return_annotation: f.return_annotation,
|
||||||
return_type: f.return_type,
|
return_type: f.return_type,
|
||||||
body: f.body,
|
body: f.body,
|
||||||
can_error: f.can_error,
|
on_test_failure: f.on_test_failure,
|
||||||
end_position: f.end_position,
|
end_position: f.end_position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind,
|
Annotation, Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind,
|
||||||
Span, TypedDataType, TypedFunction, UnOp,
|
OnTestFailure, Span, TypedDataType, TypedFunction, UnOp,
|
||||||
},
|
},
|
||||||
expr::TypedExpr,
|
expr::TypedExpr,
|
||||||
tipo::{
|
tipo::{
|
||||||
|
@ -944,7 +944,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
|
||||||
annotation: None,
|
annotation: None,
|
||||||
tipo: bool(),
|
tipo: bool(),
|
||||||
}],
|
}],
|
||||||
can_error: false,
|
on_test_failure: OnTestFailure::FailImmediately,
|
||||||
doc: Some(
|
doc: Some(
|
||||||
indoc::indoc! {
|
indoc::indoc! {
|
||||||
r#"
|
r#"
|
||||||
|
@ -1001,7 +1001,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
|
||||||
doc: None,
|
doc: None,
|
||||||
tipo: a_var.clone(),
|
tipo: a_var.clone(),
|
||||||
}],
|
}],
|
||||||
can_error: false,
|
on_test_failure: OnTestFailure::FailImmediately,
|
||||||
body: TypedExpr::Var {
|
body: TypedExpr::Var {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
constructor: ValueConstructor {
|
constructor: ValueConstructor {
|
||||||
|
@ -1043,7 +1043,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
|
||||||
function_name: "always".to_string(),
|
function_name: "always".to_string(),
|
||||||
},
|
},
|
||||||
Function {
|
Function {
|
||||||
can_error: false,
|
on_test_failure: OnTestFailure::FailImmediately,
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Arg {
|
Arg {
|
||||||
arg_name: ArgName::Named {
|
arg_name: ArgName::Named {
|
||||||
|
@ -1121,7 +1121,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
|
||||||
function_name: "flip".to_string(),
|
function_name: "flip".to_string(),
|
||||||
},
|
},
|
||||||
Function {
|
Function {
|
||||||
can_error: false,
|
on_test_failure: OnTestFailure::FailImmediately,
|
||||||
arguments: vec![Arg {
|
arguments: vec![Arg {
|
||||||
arg_name: ArgName::Named {
|
arg_name: ArgName::Named {
|
||||||
name: "f".to_string(),
|
name: "f".to_string(),
|
||||||
|
|
|
@ -2,11 +2,11 @@ use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, Arg, ArgName, ArgVia, AssignmentKind, AssignmentPattern, BinOp,
|
Annotation, Arg, ArgName, ArgVia, AssignmentKind, AssignmentPattern, BinOp,
|
||||||
ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, CurveType, DataType, Definition,
|
ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, CurveType, DataType, Definition,
|
||||||
Function, IfBranch, LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor,
|
Function, IfBranch, LogicalOpChainKind, ModuleConstant, OnTestFailure, Pattern,
|
||||||
RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp,
|
RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias,
|
||||||
UnqualifiedImport, UntypedArg, UntypedArgVia, UntypedAssignmentKind, UntypedClause,
|
TypedArg, UnOp, UnqualifiedImport, UntypedArg, UntypedArgVia, UntypedAssignmentKind,
|
||||||
UntypedClauseGuard, UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern,
|
UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedFunction, UntypedModule,
|
||||||
UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE,
|
UntypedPattern, UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE,
|
||||||
},
|
},
|
||||||
docvec,
|
docvec,
|
||||||
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||||
|
@ -247,9 +247,9 @@ impl<'comments> Formatter<'comments> {
|
||||||
arguments: args,
|
arguments: args,
|
||||||
body,
|
body,
|
||||||
end_position,
|
end_position,
|
||||||
can_error,
|
on_test_failure,
|
||||||
..
|
..
|
||||||
}) => self.definition_test(name, args, body, *end_position, *can_error),
|
}) => self.definition_test(name, args, body, *end_position, on_test_failure),
|
||||||
|
|
||||||
Definition::TypeAlias(TypeAlias {
|
Definition::TypeAlias(TypeAlias {
|
||||||
alias,
|
alias,
|
||||||
|
@ -547,14 +547,18 @@ impl<'comments> Formatter<'comments> {
|
||||||
args: &'a [UntypedArgVia],
|
args: &'a [UntypedArgVia],
|
||||||
body: &'a UntypedExpr,
|
body: &'a UntypedExpr,
|
||||||
end_location: usize,
|
end_location: usize,
|
||||||
can_error: bool,
|
on_test_failure: &'a OnTestFailure,
|
||||||
) -> Document<'a> {
|
) -> Document<'a> {
|
||||||
// Fn name and args
|
// Fn name and args
|
||||||
let head = "test "
|
let head = "test "
|
||||||
.to_doc()
|
.to_doc()
|
||||||
.append(name)
|
.append(name)
|
||||||
.append(wrap_args(args.iter().map(|e| (self.fn_arg_via(e), false))))
|
.append(wrap_args(args.iter().map(|e| (self.fn_arg_via(e), false))))
|
||||||
.append(if can_error { " fail" } else { "" })
|
.append(match on_test_failure {
|
||||||
|
OnTestFailure::FailImmediately => "",
|
||||||
|
OnTestFailure::SucceedEventually => " fail",
|
||||||
|
OnTestFailure::SucceedImmediately => " fail once",
|
||||||
|
})
|
||||||
.group();
|
.group();
|
||||||
|
|
||||||
// Format body
|
// Format body
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use chumsky::prelude::*;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast,
|
ast,
|
||||||
expr::UntypedExpr,
|
expr::UntypedExpr,
|
||||||
parser::{annotation, error::ParseError, expr, token::Token, utils},
|
parser::{annotation, error::ParseError, expr, token::Token, utils},
|
||||||
};
|
};
|
||||||
|
use chumsky::prelude::*;
|
||||||
|
|
||||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||||
utils::optional_flag(Token::Pub)
|
utils::optional_flag(Token::Pub)
|
||||||
|
@ -41,7 +40,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
public,
|
public,
|
||||||
return_annotation,
|
return_annotation,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
can_error: true,
|
on_test_failure: ast::OnTestFailure::FailImmediately,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,6 +54,6 @@ Test(
|
||||||
),
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 38,
|
end_position: 38,
|
||||||
can_error: false,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -43,6 +43,6 @@ Test(
|
||||||
),
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 40,
|
end_position: 40,
|
||||||
can_error: false,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -50,6 +50,6 @@ Test(
|
||||||
),
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 38,
|
end_position: 38,
|
||||||
can_error: false,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,6 @@ Test(
|
||||||
),
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 22,
|
end_position: 22,
|
||||||
can_error: false,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,6 +54,6 @@ Test(
|
||||||
),
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 60,
|
end_position: 60,
|
||||||
can_error: true,
|
on_test_failure: SucceedEventually,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,7 +56,7 @@ Validator(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 52,
|
end_position: 52,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
other_fun: Some(
|
other_fun: Some(
|
||||||
Function {
|
Function {
|
||||||
|
@ -97,7 +97,7 @@ Validator(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 88,
|
end_position: 88,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
location: 0..9,
|
location: 0..9,
|
||||||
|
|
|
@ -46,6 +46,6 @@ Fn(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 27,
|
end_position: 27,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,6 @@ Fn(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 14,
|
end_position: 14,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,6 @@ Fn(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 10,
|
end_position: 10,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,7 +56,7 @@ Validator(
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 52,
|
end_position: 52,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
other_fun: None,
|
other_fun: None,
|
||||||
location: 0..9,
|
location: 0..9,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast,
|
ast,
|
||||||
|
ast::OnTestFailure,
|
||||||
expr::UntypedExpr,
|
expr::UntypedExpr,
|
||||||
parser::{
|
parser::{
|
||||||
annotation,
|
annotation,
|
||||||
|
@ -12,27 +13,29 @@ use crate::{
|
||||||
use chumsky::prelude::*;
|
use chumsky::prelude::*;
|
||||||
|
|
||||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||||
// TODO: can remove Token::Bang after a few releases (curr v1.0.11)
|
just(Token::Test)
|
||||||
just(Token::Bang)
|
.ignore_then(select! {Token::Name {name} => name})
|
||||||
.ignored()
|
|
||||||
.or_not()
|
|
||||||
.then_ignore(just(Token::Test))
|
|
||||||
.then(select! {Token::Name {name} => name})
|
|
||||||
.then(
|
.then(
|
||||||
via()
|
via()
|
||||||
.separated_by(just(Token::Comma))
|
.separated_by(just(Token::Comma))
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
|
||||||
)
|
)
|
||||||
.then(just(Token::Fail).ignored().or_not())
|
.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))
|
.map_with_span(|name, span| (name, span))
|
||||||
.then(
|
.then(
|
||||||
expr::sequence()
|
expr::sequence()
|
||||||
.or_not()
|
.or_not()
|
||||||
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
|
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
|
||||||
)
|
)
|
||||||
.map_with_span(
|
.map_with_span(|((((name, arguments), fail), span_end), body), span| {
|
||||||
|(((((old_fail, name), arguments), fail), span_end), body), span| {
|
|
||||||
ast::UntypedDefinition::Test(ast::Function {
|
ast::UntypedDefinition::Test(ast::Function {
|
||||||
arguments,
|
arguments,
|
||||||
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
|
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
|
||||||
|
@ -43,10 +46,9 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(ast::Annotation::boolean(span)),
|
return_annotation: Some(ast::Annotation::boolean(span)),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
can_error: fail.is_some() || old_fail.is_some(),
|
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
||||||
|
|
|
@ -223,6 +223,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
|
||||||
// TODO: remove this in a future release
|
// TODO: remove this in a future release
|
||||||
"error" => Token::Fail,
|
"error" => Token::Fail,
|
||||||
"fail" => Token::Fail,
|
"fail" => Token::Fail,
|
||||||
|
"once" => Token::Once,
|
||||||
"as" => Token::As,
|
"as" => Token::As,
|
||||||
"and" => Token::And,
|
"and" => Token::And,
|
||||||
"or" => Token::Or,
|
"or" => Token::Or,
|
||||||
|
|
|
@ -78,6 +78,7 @@ pub enum Token {
|
||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
Fail,
|
Fail,
|
||||||
|
Once,
|
||||||
Expect,
|
Expect,
|
||||||
Is,
|
Is,
|
||||||
Let,
|
Let,
|
||||||
|
@ -178,6 +179,7 @@ impl fmt::Display for Token {
|
||||||
Token::Type => "type",
|
Token::Type => "type",
|
||||||
Token::Test => "test",
|
Token::Test => "test",
|
||||||
Token::Fail => "fail",
|
Token::Fail => "fail",
|
||||||
|
Token::Once => "once",
|
||||||
Token::Validator => "validator",
|
Token::Validator => "validator",
|
||||||
Token::Via => "via",
|
Token::Via => "via",
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 34,
|
end_position: 34,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Fn(
|
Fn(
|
||||||
|
@ -94,7 +94,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 71,
|
end_position: 71,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Fn(
|
Fn(
|
||||||
|
@ -141,7 +141,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 104,
|
end_position: 104,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Fn(
|
Fn(
|
||||||
|
@ -221,7 +221,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 154,
|
end_position: 154,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -51,7 +51,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 31,
|
end_position: 31,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -49,7 +49,7 @@ Module {
|
||||||
return_annotation: None,
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 29,
|
end_position: 29,
|
||||||
can_error: true,
|
on_test_failure: FailImmediately,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -218,7 +218,7 @@ impl<'a> Environment<'a> {
|
||||||
return_annotation,
|
return_annotation,
|
||||||
return_type,
|
return_type,
|
||||||
end_position,
|
end_position,
|
||||||
can_error,
|
on_test_failure,
|
||||||
}) => {
|
}) => {
|
||||||
// Lookup the inferred function information
|
// Lookup the inferred function information
|
||||||
let function = self
|
let function = self
|
||||||
|
@ -263,7 +263,7 @@ impl<'a> Environment<'a> {
|
||||||
return_type,
|
return_type,
|
||||||
body,
|
body,
|
||||||
end_position,
|
end_position,
|
||||||
can_error,
|
on_test_failure,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Definition::Validator(Validator {
|
Definition::Validator(Validator {
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub(crate) fn infer_function(
|
||||||
body,
|
body,
|
||||||
return_annotation,
|
return_annotation,
|
||||||
end_position,
|
end_position,
|
||||||
can_error,
|
on_test_failure,
|
||||||
return_type: _,
|
return_type: _,
|
||||||
} = fun;
|
} = fun;
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ pub(crate) fn infer_function(
|
||||||
.return_type()
|
.return_type()
|
||||||
.expect("Could not find return type for fn"),
|
.expect("Could not find return type for fn"),
|
||||||
body,
|
body,
|
||||||
can_error: *can_error,
|
on_test_failure: on_test_failure.clone(),
|
||||||
end_position: *end_position,
|
end_position: *end_position,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,7 @@ fn infer_definition(
|
||||||
return_annotation: typed_f.return_annotation,
|
return_annotation: typed_f.return_annotation,
|
||||||
return_type: typed_f.return_type,
|
return_type: typed_f.return_type,
|
||||||
body: typed_f.body,
|
body: typed_f.body,
|
||||||
can_error: typed_f.can_error,
|
on_test_failure: typed_f.on_test_failure,
|
||||||
end_position: typed_f.end_position,
|
end_position: typed_f.end_position,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
pretty,
|
pretty,
|
||||||
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
||||||
};
|
};
|
||||||
use aiken_lang::{expr::UntypedExpr, format::Formatter};
|
use aiken_lang::{ast::OnTestFailure, expr::UntypedExpr, format::Formatter};
|
||||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||||
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
||||||
use uplc::machine::cost_model::ExBudget;
|
use uplc::machine::cost_model::ExBudget;
|
||||||
|
@ -338,7 +338,14 @@ fn fmt_test(
|
||||||
}) if !result.is_success() => {
|
}) if !result.is_success() => {
|
||||||
test = format!(
|
test = format!(
|
||||||
"{test}\n{}",
|
"{test}\n{}",
|
||||||
assertion.to_string(Stderr, unit_test.can_error),
|
assertion.to_string(
|
||||||
|
Stderr,
|
||||||
|
match unit_test.on_test_failure {
|
||||||
|
OnTestFailure::FailImmediately => false,
|
||||||
|
OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately =>
|
||||||
|
true,
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use aiken_lang::{
|
use aiken_lang::ast::OnTestFailure;
|
||||||
|
pub(crate) use aiken_lang::{
|
||||||
ast::{Arg, BinOp, DataTypeKey, IfBranch, Span, TypedDataType, TypedTest},
|
ast::{Arg, BinOp, DataTypeKey, IfBranch, Span, TypedDataType, TypedTest},
|
||||||
builtins::bool,
|
builtins::bool,
|
||||||
expr::{TypedExpr, UntypedExpr},
|
expr::{TypedExpr, UntypedExpr},
|
||||||
|
@ -88,7 +89,7 @@ impl Test {
|
||||||
name: test.name,
|
name: test.name,
|
||||||
program,
|
program,
|
||||||
assertion,
|
assertion,
|
||||||
can_error: test.can_error,
|
on_test_failure: test.on_test_failure,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ impl Test {
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
module: String,
|
module: String,
|
||||||
name: String,
|
name: String,
|
||||||
can_error: bool,
|
on_test_failure: OnTestFailure,
|
||||||
program: Program<Name>,
|
program: Program<Name>,
|
||||||
fuzzer: Fuzzer<Name>,
|
fuzzer: Fuzzer<Name>,
|
||||||
) -> Test {
|
) -> Test {
|
||||||
|
@ -105,7 +106,7 @@ impl Test {
|
||||||
module,
|
module,
|
||||||
name,
|
name,
|
||||||
program,
|
program,
|
||||||
can_error,
|
on_test_failure,
|
||||||
fuzzer,
|
fuzzer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,7 +146,7 @@ impl Test {
|
||||||
input_path,
|
input_path,
|
||||||
module_name,
|
module_name,
|
||||||
test.name,
|
test.name,
|
||||||
test.can_error,
|
test.on_test_failure,
|
||||||
program,
|
program,
|
||||||
Fuzzer {
|
Fuzzer {
|
||||||
program: fuzzer,
|
program: fuzzer,
|
||||||
|
@ -164,7 +165,7 @@ pub struct UnitTest {
|
||||||
pub input_path: PathBuf,
|
pub input_path: PathBuf,
|
||||||
pub module: String,
|
pub module: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub can_error: bool,
|
pub on_test_failure: OnTestFailure,
|
||||||
pub program: Program<Name>,
|
pub program: Program<Name>,
|
||||||
pub assertion: Option<Assertion<(Constant, Rc<Type>)>>,
|
pub assertion: Option<Assertion<(Constant, Rc<Type>)>>,
|
||||||
}
|
}
|
||||||
|
@ -177,7 +178,10 @@ impl UnitTest {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval_version(ExBudget::max(), &plutus_version.into());
|
.eval_version(ExBudget::max(), &plutus_version.into());
|
||||||
|
|
||||||
let success = !eval_result.failed(self.can_error);
|
let success = !eval_result.failed(match self.on_test_failure {
|
||||||
|
OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately => true,
|
||||||
|
OnTestFailure::FailImmediately => false,
|
||||||
|
});
|
||||||
|
|
||||||
TestResult::UnitTestResult(UnitTestResult {
|
TestResult::UnitTestResult(UnitTestResult {
|
||||||
success,
|
success,
|
||||||
|
@ -196,7 +200,7 @@ pub struct PropertyTest {
|
||||||
pub input_path: PathBuf,
|
pub input_path: PathBuf,
|
||||||
pub module: String,
|
pub module: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub can_error: bool,
|
pub on_test_failure: OnTestFailure,
|
||||||
pub program: Program<Name>,
|
pub program: Program<Name>,
|
||||||
pub fuzzer: Fuzzer<Name>,
|
pub fuzzer: Fuzzer<Name>,
|
||||||
}
|
}
|
||||||
|
@ -250,7 +254,7 @@ impl PropertyTest {
|
||||||
.filter(|s| PropertyTest::extract_label(s).is_none())
|
.filter(|s| PropertyTest::extract_label(s).is_none())
|
||||||
.collect(),
|
.collect(),
|
||||||
Ok(Some(counterexample.value)),
|
Ok(Some(counterexample.value)),
|
||||||
n - remaining + 1,
|
n - remaining,
|
||||||
),
|
),
|
||||||
Err(FuzzerError { traces, uplc_error }) => (
|
Err(FuzzerError { traces, uplc_error }) => (
|
||||||
traces
|
traces
|
||||||
|
@ -258,7 +262,7 @@ impl PropertyTest {
|
||||||
.filter(|s| PropertyTest::extract_label(s).is_none())
|
.filter(|s| PropertyTest::extract_label(s).is_none())
|
||||||
.collect(),
|
.collect(),
|
||||||
Err(uplc_error),
|
Err(uplc_error),
|
||||||
0,
|
n - remaining + 1,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -295,6 +299,8 @@ impl PropertyTest {
|
||||||
labels: &mut BTreeMap<String, usize>,
|
labels: &mut BTreeMap<String, usize>,
|
||||||
plutus_version: &'a PlutusVersion,
|
plutus_version: &'a PlutusVersion,
|
||||||
) -> Result<(Prng, Option<Counterexample<'a>>), FuzzerError> {
|
) -> Result<(Prng, Option<Counterexample<'a>>), FuzzerError> {
|
||||||
|
use OnTestFailure::*;
|
||||||
|
|
||||||
let (next_prng, value) = prng
|
let (next_prng, value) = prng
|
||||||
.sample(&self.fuzzer.program)?
|
.sample(&self.fuzzer.program)?
|
||||||
.expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors.");
|
.expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors.");
|
||||||
|
@ -313,10 +319,16 @@ impl PropertyTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We do NOT pass self.can_error here, because when searching for
|
let is_failure = result.failed(false);
|
||||||
// failing properties, we do want to _keep running_ until we find a
|
|
||||||
// a failing case. It may not occur on the first run.
|
let is_success = !is_failure;
|
||||||
if result.failed(false) {
|
|
||||||
|
let keep_counterexample = match self.on_test_failure {
|
||||||
|
FailImmediately | SucceedImmediately => is_failure,
|
||||||
|
SucceedEventually => is_success,
|
||||||
|
};
|
||||||
|
|
||||||
|
if keep_counterexample {
|
||||||
let mut counterexample = Counterexample {
|
let mut counterexample = Counterexample {
|
||||||
value,
|
value,
|
||||||
choices: next_prng.choices(),
|
choices: next_prng.choices(),
|
||||||
|
@ -327,19 +339,27 @@ impl PropertyTest {
|
||||||
Ok(Some((_, value))) => {
|
Ok(Some((_, value))) => {
|
||||||
let result = self.eval(&value, plutus_version);
|
let result = self.eval(&value, plutus_version);
|
||||||
|
|
||||||
let is_failure = result.failed(self.can_error);
|
let is_failure = result.failed(false);
|
||||||
|
|
||||||
let expect_failure = self.can_error;
|
match self.on_test_failure {
|
||||||
|
FailImmediately | SucceedImmediately => {
|
||||||
|
if is_failure {
|
||||||
|
Status::Keep(value)
|
||||||
|
} else {
|
||||||
|
Status::Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the test no longer fails, it isn't better as we're only
|
SucceedEventually => {
|
||||||
// interested in counterexamples.
|
if is_failure {
|
||||||
if (expect_failure && is_failure) || (!expect_failure && !is_failure) {
|
|
||||||
Status::Ignore
|
Status::Ignore
|
||||||
} else {
|
} else {
|
||||||
Status::Keep(value)
|
Status::Keep(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -883,13 +903,12 @@ impl<U, T> TestResult<U, T> {
|
||||||
counterexample: Ok(counterexample),
|
counterexample: Ok(counterexample),
|
||||||
test,
|
test,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => match test.on_test_failure {
|
||||||
if test.can_error {
|
OnTestFailure::FailImmediately | OnTestFailure::SucceedEventually => {
|
||||||
counterexample.is_some()
|
|
||||||
} else {
|
|
||||||
counterexample.is_none()
|
counterexample.is_none()
|
||||||
}
|
}
|
||||||
}
|
OnTestFailure::SucceedImmediately => counterexample.is_some(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue