use std::fmt::Debug;
use test_better_core::{ContextFrame, ErrorKind, TestError, TestResult};
use crate::{PropertyFailure, Strategy, for_all};
#[doc(hidden)]
pub fn run_property<T, S, F>(strategy: S, property: F) -> TestResult
where
S: Strategy<T>,
T: Clone + Debug,
F: FnMut(T) -> TestResult,
{
match for_all(strategy, property) {
Ok(()) => Ok(()),
Err(failure) => Err(render_failure(failure)),
}
}
#[doc(hidden)]
pub fn render_failure<T: Debug>(failure: PropertyFailure<T>) -> TestError {
let PropertyFailure {
original,
shrunk,
failure,
cases,
} = failure;
let plural = if cases == 1 { "" } else { "s" };
let mut error = failure;
error.kind = ErrorKind::Property;
error.push_context(ContextFrame::new(format!(
"checking a property; it failed after {cases} generated case{plural}"
)));
error.push_context(ContextFrame::new(format!(
"the original failing input was {original:?}"
)));
error.push_context(ContextFrame::new(format!(
"the shrunk (minimal) input is {shrunk:?}"
)));
error
}
#[macro_export]
macro_rules! property {
(| $name:ident : $ty:ty | $body:block) => {
$crate::run_property($crate::any::<$ty>(), |$name: $ty| $body)
};
(| $name:ident : $ty:ty | $body:block using $strategy:expr) => {
$crate::run_property($strategy, |$name: $ty| $body)
};
(| $name:ident | $body:block using $strategy:expr) => {
$crate::run_property($strategy, |$name| $body)
};
}
#[cfg(test)]
mod tests {
use test_better_core::{OrFail, TestResult};
use test_better_matchers::{check, eq, ge, is_true, lt};
#[test]
fn an_inferred_strategy_property_that_holds_passes() -> TestResult {
property!(|n: u8| { check!(u16::from(n) + 1).satisfies(ge(1u16)) })
}
#[test]
fn a_using_clause_names_the_strategy_explicitly() -> TestResult {
property!(|n| {
check!(n).satisfies(lt(50u64))
} using 0u64..50)
}
#[test]
fn a_failing_property_renders_a_property_kind_error_naming_the_shrunk_input() -> TestResult {
let error = property!(|n: u32| {
check!(n).satisfies(lt(100u32))
} using proptest::num::u32::ANY)
.err()
.or_fail_with("a property false for most u32 must fail")?;
let rendered = error.to_string();
check!(rendered.contains("the shrunk (minimal) input is 100")).satisfies(is_true())?;
check!(rendered.contains("the original failing input was")).satisfies(is_true())?;
check!(rendered.contains("less than 100")).satisfies(is_true())?;
check!(error.kind).satisfies(eq(test_better_core::ErrorKind::Property))
}
}