use crate::control::{
AssumeFailed, InternalError, InvalidArgument, LeafBudgetExceeded, LoopDone, StopTest,
hegel_internal_assert, hegel_internal_error, raise_control,
};
use crate::ffi::CTestCase;
use crate::ffi::sys as hegel_c;
use crate::generators::{Generator, PrintableGenerator};
use crate::pretty::{PrettyPrinter, tolerate};
use parking_lot::Mutex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::sync::Arc;
#[diagnostic::on_unimplemented(
message = "The first parameter in a #[composite] generator must have type TestCase.",
label = "This type does not match `TestCase`."
)]
pub trait __IsTestCase {}
impl __IsTestCase for TestCase {}
pub fn __assert_is_test_case<T: __IsTestCase>() {}
#[track_caller]
pub(crate) fn raise_invalid_argument(message: std::fmt::Arguments<'_>) -> ! {
if crate::control::currently_in_test_context() {
raise_control(InvalidArgument(message.to_string()));
} else {
panic!("{message}");
}
}
macro_rules! invalid_argument {
($($arg:tt)*) => {
$crate::test_case::raise_invalid_argument(::std::format_args!($($arg)*))
};
}
pub(crate) use invalid_argument;
#[track_caller]
pub(crate) fn raise_for_rc(rc: hegel_c::hegel_result_t) -> ! {
use hegel_c::hegel_result_t::*;
match rc {
HEGEL_E_STOP_TEST => raise_control(StopTest),
HEGEL_E_ASSUME => raise_control(AssumeFailed),
HEGEL_E_RETRY => raise_control(LeafBudgetExceeded),
HEGEL_E_INVALID_ARG => invalid_argument!("{}", crate::ffi::last_error_string()),
HEGEL_E_ALREADY_COMPLETE => panic!(
"this test case has already finished; was the TestCase moved to a \
thread that outlived the test? Join any thread that draws before \
the test returns."
),
other => hegel_internal_error!(
"libhegel returned unexpected code {}: {}",
other as i32,
crate::ffi::last_error_string()
),
}
}
pub(crate) struct TestCaseGlobalData {
emit: bool,
}
const PRINTER_MAX_WIDTH: u64 = crate::pretty::DEFAULT_MAX_WIDTH;
struct PrintingDrawScope<'a> {
tc: &'a TestCase,
}
impl<'a> PrintingDrawScope<'a> {
fn new(tc: &'a TestCase) -> Self {
tc.local.borrow_mut().printing_depth += 1;
PrintingDrawScope { tc }
}
}
impl Drop for PrintingDrawScope<'_> {
fn drop(&mut self) {
self.tc.local.borrow_mut().printing_depth -= 1;
}
}
#[derive(Default)]
pub(crate) struct DrawState {
named_draw_counts: HashMap<String, usize>,
named_draw_repeatable: HashMap<String, bool>,
allocated_display_names: HashSet<String>,
}
#[derive(Clone)]
pub(crate) struct TestCaseLocalData {
span_depth: usize,
printing_depth: usize,
on_draw: OutputSink,
}
pub struct TestCase {
global: Arc<TestCaseGlobalData>,
local: RefCell<TestCaseLocalData>,
handle: RefCell<Arc<CTestCase>>,
printer: RefCell<Option<PrettyPrinter>>,
draw_state: Arc<Mutex<DrawState>>,
}
impl Clone for TestCase {
fn clone(&self) -> Self {
TestCase {
global: self.global.clone(),
local: RefCell::new(self.local.borrow().clone()),
handle: RefCell::new(Arc::new(self.handle.borrow().clone_handle())),
printer: RefCell::new(None),
draw_state: Arc::clone(&self.draw_state),
}
}
}
impl std::fmt::Debug for TestCase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TestCase").finish_non_exhaustive()
}
}
pub(crate) struct OutputReporter {
emit: bool,
handle: Arc<CTestCase>,
sink: OutputSink,
}
impl OutputReporter {
pub(crate) fn emit_rendered_output(&self) {
if !self.emit {
return;
}
let output = PrettyPrinter::from_handle(self.handle.printer(PRINTER_MAX_WIDTH)).try_value();
match output {
Ok(output) => {
for line in output.lines() {
(self.sink)(line);
}
}
Err(message) => (self.sink)(&format!(
"Failed to render this test case's drawn values ({message}). This \
indicates a bug in printing code the test uses: check any \
hand-written PrintableGenerator impl or print_with closure for \
unbalanced begin_group/end_group calls."
)),
}
}
}
pub(crate) type OutputSink = Arc<dyn Fn(&str) + Send + Sync>;
thread_local! {
static OUTPUT_OVERRIDE: RefCell<Option<OutputSink>> = const { RefCell::new(None) };
}
#[doc(hidden)]
pub fn with_output_override<R>(sink: OutputSink, f: impl FnOnce() -> R) -> R {
struct Restore(Option<OutputSink>);
impl Drop for Restore {
fn drop(&mut self) {
OUTPUT_OVERRIDE.with(|cell| *cell.borrow_mut() = self.0.take());
}
}
let _restore = Restore(OUTPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(sink)));
f()
}
pub(crate) fn current_output_sink() -> Option<OutputSink> {
OUTPUT_OVERRIDE.with(|cell| cell.borrow().clone())
}
pub(crate) fn emit_verbose_line(msg: &str) {
if let Some(sink) = current_output_sink() {
sink(msg);
} else {
eprintln!("{}", msg);
}
}
#[derive(Clone)]
pub(crate) struct RunOutput {
sink: Option<OutputSink>,
}
impl RunOutput {
pub(crate) fn resolve() -> Self {
RunOutput {
sink: current_output_sink(),
}
}
pub(crate) fn sink(&self) -> Option<&OutputSink> {
self.sink.as_ref()
}
pub(crate) fn line(&self, msg: &str) {
match &self.sink {
Some(sink) => sink(msg),
None => eprintln!("{msg}"),
}
}
pub(crate) fn block(&self, text: &str) {
match &self.sink {
Some(sink) => {
for line in text.trim_end_matches('\n').split('\n') {
sink(line);
}
}
None => eprint!("{text}"),
}
}
}
impl TestCase {
pub(crate) fn new(handle: Arc<CTestCase>, emit: bool, sink: Option<OutputSink>) -> Self {
let on_draw: OutputSink = if emit {
sink.unwrap_or_else(|| Arc::new(|msg| eprintln!("{}", msg)))
} else {
Arc::new(|_| {})
};
TestCase {
global: Arc::new(TestCaseGlobalData { emit }),
local: RefCell::new(TestCaseLocalData {
span_depth: 0,
printing_depth: 0,
on_draw,
}),
handle: RefCell::new(handle),
printer: RefCell::new(None),
draw_state: Arc::new(Mutex::new(DrawState::default())),
}
}
pub(crate) fn with_draw_state<R>(&self, f: impl FnOnce(&mut DrawState) -> R) -> R {
let mut guard = self.draw_state.lock();
f(&mut guard)
}
pub fn draw<T>(&self, generator: impl PrintableGenerator<T>) -> T {
self.__draw_named(generator, "draw", true)
}
pub fn draw_named<T>(&self, name: &str, generator: impl PrintableGenerator<T>) -> T {
self.__draw_named(generator, name, true)
}
pub fn __draw_named<T>(
&self,
generator: impl PrintableGenerator<T>,
name: &str,
repeatable: bool,
) -> T {
let mid_draw = {
let local = self.local.borrow();
local.span_depth > 0 || local.printing_depth > 0
};
if mid_draw {
return self.draw_silent(generator);
}
let Some(display_name) = self.allocate_display_name(name, repeatable) else {
return self.draw_silent(generator);
};
let _printing = PrintingDrawScope::new(self);
self.with_printer(|printer| {
let mut speculation = printer.speculate();
let printer = speculation.printer();
printer.text(&format!("let {display_name} = "));
let value = self.draw_and_print(&generator, printer);
printer.text(";");
printer.hard_break();
speculation.commit();
value
})
}
pub fn draw_silent<T>(&self, generator: impl Generator<T>) -> T {
self.start_span(generator.label());
let value = generator.do_draw(self);
self.stop_span(false);
value
}
pub fn draw_and_print<T>(
&self,
generator: impl PrintableGenerator<T>,
printer: &mut PrettyPrinter,
) -> T {
self.start_span(generator.label());
let value = generator.do_draw_and_print(self, printer);
self.stop_span(false);
value
}
pub fn assume(&self, condition: bool) {
if !condition {
self.reject();
}
}
pub fn reject(&self) -> ! {
raise_control(AssumeFailed);
}
pub fn note(&self, message: &str) {
if !self.global.emit {
return;
}
tolerate(self.with_ctc(|ctc| ctc.note(message)));
}
pub fn target(&self, score: f64) {
self.target_labelled(score, "");
}
pub fn target_labelled(&self, score: f64, label: impl Into<String>) {
let label = label.into();
let outcome = self.with_ctc(|ctc| ctc.target(score, &label));
if let Err(rc) = outcome {
raise_for_rc(rc);
}
}
pub fn event(&self, label: impl AsRef<str>) {
self.record_event(|ctc| ctc.event(label.as_ref()));
}
pub fn event_value(&self, label: impl AsRef<str>, value: f64) {
self.record_event(|ctc| ctc.event_value(label.as_ref(), value));
}
fn record_event(&self, record: impl FnOnce(&CTestCase) -> Result<(), hegel_c::hegel_result_t>) {
if let Err(rc) = self.with_ctc(record) {
raise_for_rc(rc);
}
}
pub fn repeat<F: FnMut()>(&self, mut body: F) -> ! {
use crate::generators::{booleans, integers};
let max_safe_min_size = usize::try_from(1u64 << 40).unwrap_or(usize::MAX / 2);
let min_size = self.draw_silent(integers::<usize>().max_value(max_safe_min_size));
let mut collection = Collection::new(self, min_size, None);
let mut iteration: u64 = 0;
while collection.more() {
iteration += 1;
self.note(&format!("// Repetition #{}", iteration));
let outer = self.enter_block(2);
let result = catch_unwind(AssertUnwindSafe(&mut body));
self.restore_handle(outer);
match result {
Ok(()) => {}
Err(e) if e.downcast_ref::<AssumeFailed>().is_some() => {}
Err(e)
if e.downcast_ref::<StopTest>().is_some()
|| e.downcast_ref::<InvalidArgument>().is_some()
|| e.downcast_ref::<InternalError>().is_some() =>
{
resume_unwind(e);
}
Err(e) => {
self.draw_silent(booleans());
resume_unwind(e);
}
}
}
raise_control(LoopDone);
}
pub(crate) fn child(&self, extra_indent: usize) -> Self {
let local = self.local.borrow();
let block = self.handle.borrow().block_handle(extra_indent as u64);
TestCase {
global: self.global.clone(),
local: RefCell::new(TestCaseLocalData {
span_depth: 0,
printing_depth: 0,
on_draw: local.on_draw.clone(),
}),
handle: RefCell::new(Arc::new(block)),
printer: RefCell::new(None),
draw_state: Arc::new(Mutex::new(DrawState::default())),
}
}
fn enter_block(&self, indent: usize) -> Arc<CTestCase> {
let block = Arc::new(self.handle.borrow().block_handle(indent as u64));
self.restore_handle(block)
}
fn restore_handle(&self, handle: Arc<CTestCase>) -> Arc<CTestCase> {
*self.printer.borrow_mut() = None;
std::mem::replace(&mut *self.handle.borrow_mut(), handle)
}
fn with_printer<R>(&self, f: impl FnOnce(&mut PrettyPrinter) -> R) -> R {
let mut printer = self.printer.borrow_mut();
let printer = printer.get_or_insert_with(|| {
PrettyPrinter::from_handle(self.with_ctc(|ctc| ctc.printer(PRINTER_MAX_WIDTH)))
});
f(printer)
}
pub(crate) fn reporter(&self) -> OutputReporter {
OutputReporter {
emit: self.global.emit,
handle: Arc::clone(&self.handle.borrow()),
sink: self.local.borrow().on_draw.clone(),
}
}
fn allocate_display_name(&self, name: &str, repeatable: bool) -> Option<String> {
let emit = self.global.emit;
self.with_draw_state(|draw_state| {
match draw_state.named_draw_repeatable.get(name) {
Some(&prev) if prev != repeatable => {
hegel_internal_error!(
"__draw_named: name {:?} used with inconsistent repeatable flag \
(was {}, now {})",
name,
prev,
repeatable
);
}
Some(_) => {}
None => {
draw_state
.named_draw_repeatable
.insert(name.to_string(), repeatable);
}
}
let current_count = match draw_state.named_draw_counts.get_mut(name) {
Some(count) => {
*count += 1;
*count
}
None => {
draw_state.named_draw_counts.insert(name.to_string(), 1);
1
}
};
if !repeatable && current_count > 1 {
hegel_internal_error!(
"__draw_named: name {:?} used more than once but repeatable is false",
name
);
}
if !emit {
return None;
}
let display = if repeatable {
let mut candidate = current_count;
loop {
let name = format!("{}_{}", name, candidate);
if draw_state.allocated_display_names.insert(name.clone()) {
break name;
}
candidate += 1;
}
} else {
let name = name.to_string();
draw_state.allocated_display_names.insert(name.clone());
name
};
Some(display)
})
}
pub(crate) fn with_ctc<R>(&self, f: impl FnOnce(&CTestCase) -> R) -> R {
f(&self.handle.borrow())
}
pub(crate) fn open_span_depth(&self) -> usize {
self.local.borrow().span_depth
}
pub(crate) fn reset_open_spans_to(&self, depth: usize) {
let mut local = self.local.borrow_mut();
hegel_internal_assert!(local.span_depth >= depth);
local.span_depth = depth;
}
#[doc(hidden)]
pub fn start_span(&self, label: u64) {
self.local.borrow_mut().span_depth += 1;
if let Err(rc) = self.with_ctc(|ctc| ctc.start_span(label)) {
let mut local = self.local.borrow_mut();
hegel_internal_assert!(local.span_depth > 0);
local.span_depth -= 1;
drop(local);
raise_for_rc(rc);
}
}
#[doc(hidden)]
pub fn stop_span(&self, discard: bool) {
{
let mut local = self.local.borrow_mut();
hegel_internal_assert!(local.span_depth > 0);
local.span_depth -= 1;
}
if let Err(rc) = self.with_ctc(|ctc| ctc.stop_span(discard)) {
raise_for_rc(rc);
}
}
}
impl TestCase {
fn draw_or_raise<T>(
&self,
f: impl FnOnce(&CTestCase) -> Result<T, hegel_c::hegel_result_t>,
) -> T {
self.with_ctc(f).unwrap_or_else(|rc| raise_for_rc(rc))
}
pub(crate) fn generate_integer_i64(&self, min_value: i64, max_value: i64) -> i64 {
self.draw_or_raise(|ctc| ctc.generate_integer(min_value, max_value))
}
pub(crate) fn generate_integer_le17(&self, min_value: &[u8], max_value: &[u8]) -> [u8; 17] {
self.draw_or_raise(|ctc| ctc.generate_integer_big(min_value, max_value))
}
pub(crate) fn generate_float(
&self,
width: u32,
min_value: f64,
max_value: f64,
allow_nan: bool,
allow_infinity: bool,
exclude_min: bool,
exclude_max: bool,
smallest_nonzero_magnitude: f64,
) -> f64 {
self.draw_or_raise(|ctc| {
ctc.generate_float(
width,
min_value,
max_value,
allow_nan,
allow_infinity,
exclude_min,
exclude_max,
smallest_nonzero_magnitude,
)
})
}
pub(crate) fn generate_boolean(&self, p: f64) -> bool {
self.draw_or_raise(|ctc| ctc.generate_boolean(p))
}
pub(crate) fn generate_bytes(&self, min_size: usize, max_size: usize) -> Vec<u8> {
self.draw_or_raise(|ctc| ctc.generate_bytes(min_size as u64, max_size as u64))
}
pub(crate) fn generate_string(&self, generator: &crate::ffi::StringGenerator) -> String {
self.draw_or_raise(|ctc| ctc.generate_string(generator))
}
pub(crate) fn generate_grammar(&self, grammar: &crate::ffi::Grammar, max_size: u64) -> String {
self.draw_or_raise(|ctc| ctc.generate_grammar(grammar, max_size))
}
pub(crate) fn generate_date(
&self,
min: hegel_c::hegel_date_t,
max: hegel_c::hegel_date_t,
) -> hegel_c::hegel_date_t {
self.draw_or_raise(|ctc| ctc.generate_date(min, max))
}
pub(crate) fn generate_time(
&self,
min: hegel_c::hegel_time_t,
max: hegel_c::hegel_time_t,
) -> hegel_c::hegel_time_t {
self.draw_or_raise(|ctc| ctc.generate_time(min, max))
}
pub(crate) fn generate_datetime(
&self,
min: hegel_c::hegel_datetime_t,
max: hegel_c::hegel_datetime_t,
) -> hegel_c::hegel_datetime_t {
self.draw_or_raise(|ctc| ctc.generate_datetime(min, max))
}
pub(crate) fn generate_uuid(&self, version: Option<u8>) -> [u8; 16] {
self.draw_or_raise(|ctc| ctc.generate_uuid(version))
}
pub(crate) fn generate_ipv4(&self) -> std::net::Ipv4Addr {
self.draw_or_raise(|ctc| ctc.generate_ipv4())
}
pub(crate) fn generate_ipv6(&self) -> std::net::Ipv6Addr {
self.draw_or_raise(|ctc| ctc.generate_ipv6())
}
}
pub struct Collection<'a> {
tc: &'a TestCase,
min_size: usize,
max_size: Option<usize>,
handle: Option<crate::ffi::CollectionHandle>,
finished: bool,
}
impl<'a> Collection<'a> {
pub fn new(tc: &'a TestCase, min_size: usize, max_size: Option<usize>) -> Self {
Collection {
tc,
min_size,
max_size,
handle: None,
finished: false,
}
}
fn ensure_initialized(&mut self) {
if self.handle.is_none() {
let result = self.tc.with_ctc(|ctc| {
ctc.new_collection(self.min_size as u64, self.max_size.map(|m| m as u64))
});
let handle = match result {
Ok(handle) => handle,
Err(rc) => raise_for_rc(rc), };
self.handle = Some(handle);
}
}
pub fn more(&mut self) -> bool {
if self.finished {
return false;
}
self.ensure_initialized();
let handle = self.handle.as_ref().unwrap();
let result = match self.tc.with_ctc(|ctc| ctc.collection_more(handle)) {
Ok(b) => b,
Err(rc) => {
self.finished = true;
raise_for_rc(rc);
}
};
if !result {
self.finished = true;
}
result
}
pub fn reject(&mut self, why: Option<&str>) {
if self.finished {
return;
}
self.ensure_initialized();
let handle = self.handle.as_ref().unwrap();
let _ = self.tc.with_ctc(|ctc| ctc.collection_reject(handle, why));
}
}
#[cfg(test)]
#[path = "../tests/embedded/test_case_tests.rs"]
mod tests;
pub(crate) mod full_ranges {
use crate::ffi::sys as hegel_c;
pub(crate) const MIN_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
year: 1,
month: 1,
day: 1,
};
pub(crate) const MAX_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
year: 9999,
month: 12,
day: 31,
};
pub(crate) const MIDNIGHT: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
};
pub(crate) const LAST_NANOSECOND: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
hour: 23,
minute: 59,
second: 59,
nanosecond: 999_999_999,
};
pub(crate) const MIN_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
date: MIN_DATE,
time: MIDNIGHT,
};
pub(crate) const MAX_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
date: MAX_DATE,
time: LAST_NANOSECOND,
};
}