Introduce 'fail once' and alter behavior of 'fail' keyword for properties.

This commit is contained in:
KtorZ 2024-05-30 17:18:50 +02:00
parent 28515e70ec
commit 5694d9f9cb
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
26 changed files with 141 additions and 94 deletions

View File

@ -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

View File

@ -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,
} }
} }

View File

@ -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(),

View File

@ -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

View File

@ -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,
}) })
}, },
) )

View File

@ -54,6 +54,6 @@ Test(
), ),
return_type: (), return_type: (),
end_position: 38, end_position: 38,
can_error: false, on_test_failure: FailImmediately,
}, },
) )

View File

@ -43,6 +43,6 @@ Test(
), ),
return_type: (), return_type: (),
end_position: 40, end_position: 40,
can_error: false, on_test_failure: FailImmediately,
}, },
) )

View File

@ -50,6 +50,6 @@ Test(
), ),
return_type: (), return_type: (),
end_position: 38, end_position: 38,
can_error: false, on_test_failure: FailImmediately,
}, },
) )

View File

@ -23,6 +23,6 @@ Test(
), ),
return_type: (), return_type: (),
end_position: 22, end_position: 22,
can_error: false, on_test_failure: FailImmediately,
}, },
) )

View File

@ -54,6 +54,6 @@ Test(
), ),
return_type: (), return_type: (),
end_position: 60, end_position: 60,
can_error: true, on_test_failure: SucceedEventually,
}, },
) )

View File

@ -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,

View File

@ -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,
}, },
) )

View File

@ -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,
}, },
) )

View File

@ -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,
}, },
) )

View File

@ -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,

View File

@ -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> {

View File

@ -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,

View File

@ -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",
}; };

View File

@ -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,
}, },
), ),
], ],

View File

@ -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,
}, },
), ),
], ],

View File

@ -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,
}, },
), ),
], ],

View File

@ -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 {

View File

@ -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,
}; };

View File

@ -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,
})) }))
} }

View File

@ -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,
}
),
); );
} }
_ => (), _ => (),

View File

@ -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(),
},
} }
} }