mod common;
use hegel::generators::{self as gs, Generator};
use hegel::{Hegel, Settings};
use std::time::Duration;
fn capture_run_panic(body: impl FnMut(hegel::TestCase) + std::panic::UnwindSafe) -> String {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
Hegel::new(body)
.settings(Settings::new().test_cases(50).database(None))
.run();
}));
let payload = result.expect_err("expected the run to abort with a usage error");
payload
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_default()
}
fn assert_clean_usage_error(msg: &str, expected: &str) {
assert!(
msg.contains(expected),
"message {msg:?} is missing {expected:?}"
);
assert!(
!msg.contains("Property test failed"),
"a usage error must abort the run cleanly, not be reported as a property failure: {msg:?}"
);
assert!(
!msg.contains("__HEGEL"),
"internal sentinel leaked into the user-facing message: {msg:?}"
);
}
#[test]
fn target_non_finite_score_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| tc.target(f64::NAN));
assert_clean_usage_error(&msg, "finite");
let msg = capture_run_panic(|tc| tc.target(f64::INFINITY));
assert_clean_usage_error(&msg, "finite");
}
#[test]
fn target_duplicate_label_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
tc.target_labelled(1.0, "dup");
tc.target_labelled(2.0, "dup");
});
assert_clean_usage_error(&msg, "at most once");
}
#[test]
fn float_range_with_no_values_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: f64 = tc.draw(
gs::floats::<f64>()
.min_value(f64::INFINITY)
.exclude_min(true),
);
});
assert_clean_usage_error(&msg, "InvalidArgument");
}
#[test]
fn text_with_inverted_codepoint_range_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: String = tc.draw(gs::text().min_codepoint(200).max_codepoint(100));
});
assert_clean_usage_error(&msg, "InvalidArgument");
}
#[test]
fn integer_max_below_min_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: i32 = tc.draw(gs::integers::<i32>().min_value(5).max_value(3));
});
assert_clean_usage_error(&msg, "Cannot have max_value < min_value");
}
#[test]
fn vec_max_size_below_min_size_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: Vec<bool> = tc.draw(gs::vecs(gs::booleans()).min_size(5).max_size(2));
});
assert_clean_usage_error(&msg, "Cannot have max_size < min_size");
}
#[test]
fn sampled_from_empty_drawn_inline_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: i32 = tc.draw(gs::sampled_from(Vec::<i32>::new()));
});
assert_clean_usage_error(&msg, "cannot be empty");
}
#[test]
fn one_of_empty_drawn_inline_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: i32 = tc.draw(gs::one_of(
Vec::<hegel::generators::BoxedGenerator<'_, i32>>::new(),
));
});
assert_clean_usage_error(&msg, "requires at least one generator");
}
#[test]
fn duration_max_below_min_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: Duration = tc.draw(
gs::durations()
.min_value(Duration::from_secs(10))
.max_value(Duration::from_secs(1)),
);
});
assert_clean_usage_error(&msg, "Cannot have max_value < min_value");
}
#[test]
fn unsatisfiable_filter_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
let _: i64 = tc.draw(gs::sampled_from(vec![0_i64, 1]).filter(|x: &i64| *x < 0));
});
assert_clean_usage_error(&msg, "Unsatisfiable filter");
}
#[test]
fn usage_error_inside_repeat_is_a_clean_usage_error() {
let msg = capture_run_panic(|tc| {
tc.repeat(|| {
let _: i32 = tc.draw(gs::integers::<i32>().min_value(5).max_value(3));
});
});
assert_clean_usage_error(&msg, "Cannot have max_value < min_value");
}