Skip to main content

hegel/
test_case.rs

1use crate::control::{
2    AssumeFailed, InternalError, InvalidArgument, LoopDone, StopTest, hegel_internal_assert,
3    hegel_internal_error, raise_control,
4};
5use crate::ffi::CTestCase;
6use crate::generators::Generator;
7use crate::runner::Mode;
8use parking_lot::Mutex;
9use std::cell::RefCell;
10use std::collections::{HashMap, HashSet};
11use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
12use std::sync::Arc;
13
14#[diagnostic::on_unimplemented(
15    message = "The first parameter in a #[composite] generator must have type TestCase.",
16    label = "This type does not match `TestCase`."
17)]
18pub trait __IsTestCase {}
19impl __IsTestCase for TestCase {}
20pub fn __assert_is_test_case<T: __IsTestCase>() {}
21
22/// Raise an invalid-argument (usage) error carrying `message`.
23///
24/// The same usage error can be detected either while a test case is running
25/// (e.g. an inline `tc.draw(gs::sampled_from(&[]))`, or a bound check inside
26/// a draw) or up front, before any run (constructing a generator and
27/// validating its arguments eagerly). To read cleanly in both cases:
28///
29/// - **Inside a test context**, the error unwinds as a typed
30///   [`InvalidArgument`] control payload so the lifecycle aborts the run
31///   with the message rather than shrinking it as a counterexample.
32/// - **Outside any test run**, there is no lifecycle to catch a payload, so
33///   the message is panicked directly.
34///
35/// Either way the user sees only the bare message. Prefer the
36/// [`invalid_argument!`] macro, which formats its arguments.
37#[track_caller]
38pub(crate) fn raise_invalid_argument(message: std::fmt::Arguments<'_>) -> ! {
39    if crate::control::currently_in_test_context() {
40        raise_control(InvalidArgument(message.to_string()));
41    } else {
42        panic!("{message}");
43    }
44}
45
46/// Raise an invalid-argument (usage) error, formatting like [`format!`].
47///
48/// Use this for every caller-configuration mistake a generator or
49/// `tc.target()` detects, in place of a bare `panic!`. See
50/// [`raise_invalid_argument`] for how the message is surfaced in and out of a
51/// test run.
52macro_rules! invalid_argument {
53    ($($arg:tt)*) => {
54        $crate::test_case::raise_invalid_argument(::std::format_args!($($arg)*))
55    };
56}
57pub(crate) use invalid_argument;
58
59/// Translate a non-`HEGEL_OK` libhegel result code into the matching
60/// control-flow unwind. Mirrors the previous `DataSourceError` mapping, but
61/// over the C ABI's `hegel_result_t` codes:
62///
63/// - `HEGEL_E_STOP_TEST` — the engine ran out of data for this case.
64/// - `HEGEL_E_ASSUME` — the engine rejected the draw (an assumption failed).
65/// - `HEGEL_E_INVALID_ARG` — a caller-supplied argument (typically a
66///   generator argument) was semantically invalid; the diagnostic is read
67///   synchronously from this thread's libhegel error context.
68/// - `HEGEL_E_ALREADY_COMPLETE` — the test case has finished. Unreachable
69///   from a test body (the outcome is reported only after the body returns),
70///   so it means a `TestCase` outlived its test — typically moved to a thread
71///   that was never joined — and the panic message says so.
72/// - anything else — an engine/framework invariant we don't expect on the hot
73///   path; treat it as an internal error rather than a shrinkable failure.
74///   This includes `HEGEL_E_CONCURRENT_USE`: the frontend never drives one
75///   handle from two threads (`clone` forks a fresh handle, `TestCase` is
76///   `!Sync`, and `hegel_mark_complete` waits instead of erroring), so it
77///   cannot arise here in correct use.
78#[track_caller]
79pub(crate) fn raise_for_rc(rc: hegel_c::hegel_result_t) -> ! {
80    use hegel_c::hegel_result_t::*;
81    match rc {
82        HEGEL_E_STOP_TEST => raise_control(StopTest),
83        HEGEL_E_ASSUME => raise_control(AssumeFailed), // nocov
84        HEGEL_E_INVALID_ARG => invalid_argument!("{}", crate::ffi::last_error_string()),
85        HEGEL_E_ALREADY_COMPLETE => panic!(
86            "this test case has already finished; was the TestCase moved to a \
87             thread that outlived the test? Join any thread that draws before \
88             the test returns."
89        ),
90        other => hegel_internal_error!(
91            "libhegel returned unexpected code {}: {}",
92            other as i32,
93            crate::ffi::last_error_string()
94        ),
95    }
96}
97
98pub(crate) struct TestCaseGlobalData {
99    mode: Mode,
100    /// Whether drawn-value records and notes are surfaced for this test case
101    /// (true on the final replay of a failure — unless quiet — or when
102    /// verbose output is on).
103    /// When false `on_draw` is a no-op, so the draw-recording bookkeeping in
104    /// [`TestCase::record_named_draw`] (display-name allocation + `Debug`
105    /// rendering of the value) can be skipped entirely.
106    emit: bool,
107    /// Draw-name bookkeeping shared between every clone of a `TestCase`,
108    /// behind a blocking, non-reentrant mutex. The backend handle is no longer
109    /// shared here — each `TestCase` instance owns its own libhegel handle (so
110    /// clones can be driven concurrently) — so this lock only serialises the
111    /// frontend's own draw-name accounting, never backend traffic. No method
112    /// holds it while calling back into `TestCase`.
113    draw_state: Mutex<DrawState>,
114}
115
116pub(crate) struct DrawState {
117    named_draw_counts: HashMap<String, usize>,
118    named_draw_repeatable: HashMap<String, bool>,
119    allocated_display_names: HashSet<String>,
120}
121
122#[derive(Clone)]
123pub(crate) struct TestCaseLocalData {
124    span_depth: usize,
125    indent: usize,
126    on_draw: OutputSink,
127}
128
129/// A handle to the current test case.
130///
131/// This is passed to `#[hegel::test]` functions and provides methods
132/// for drawing values, making assumptions, and recording notes.
133///
134/// # Example
135///
136/// ```no_run
137/// use hegel::generators as gs;
138///
139/// #[hegel::test]
140/// fn my_test(tc: hegel::TestCase) {
141///     let x: i32 = tc.draw(gs::integers());
142///     tc.assume(x > 0);
143///     tc.note(&format!("x = {}", x));
144/// }
145/// ```
146///
147/// # Threading
148///
149/// `TestCase` is `Send` but not `Sync`. To drive generation from another
150/// thread, clone the test case and move the clone. Each clone generates
151/// from its own *independent stream* of choices: draws on one clone never
152/// perturb the values any other clone (or the original) produces, so
153/// several threads can generate concurrently and the test stays fully
154/// deterministic — the same seed replays the same values on every stream,
155/// failures shrink normally, and the shrunk counterexample replays exactly.
156///
157/// ```no_run
158/// use hegel::generators as gs;
159///
160/// #[hegel::test]
161/// fn my_test(tc: hegel::TestCase) {
162///     let tc_worker = tc.clone();
163///     let handle = std::thread::spawn(move || {
164///         tc_worker.draw(gs::integers::<i32>())
165///     });
166///     let _b: bool = tc.draw(gs::booleans());
167///     let n = handle.join().unwrap();
168///     let _ = n;
169/// }
170/// ```
171///
172/// ## What is guaranteed
173///
174/// Each clone owns its own stream, so a clone may be moved to and driven
175/// from another thread freely, concurrently with every other clone. A
176/// *single* clone may only be driven by one thread at a time — the backend
177/// rejects concurrent use of one handle outright — which is why you `clone`
178/// to hand work to a thread rather than sharing one `TestCase` across
179/// threads (the type is `!Sync`, so the compiler enforces this too).
180///
181/// The clones share the test case's *outcome*: the whole family passes,
182/// fails, or is rejected as one test case, and the choice budget is shared
183/// across all streams. Everything else about generation is per-stream.
184///
185/// ## What is not guaranteed
186///
187/// Determinism extends exactly as far as your own code's determinism. If
188/// threads race on *your* state — for example, which of two clones first
189/// consumes a value from a shared queue — Hegel replays each stream's
190/// values faithfully, but your test may still behave differently run to
191/// run, and such failures may not reproduce or shrink well.
192///
193/// Variable pools and engine-managed collections are shared across clones
194/// (an id from one clone works on any other). Using one such object from
195/// two threads *at the same time* makes the affected draws depend on
196/// scheduling order, which brings back the same replay caveat.
197///
198/// ## Panics inside spawned threads
199///
200/// If a worker thread panics with an assumption failure or a backend
201/// `StopTest`, that panic stays inside the thread's `JoinHandle` until
202/// the main thread joins it. The main thread is responsible for
203/// propagating (or suppressing) the panic — typically by calling
204/// `handle.join().unwrap()`, which resumes the panic on the main thread
205/// so Hegel's runner can observe it.
206pub struct TestCase {
207    global: Arc<TestCaseGlobalData>,
208    local: RefCell<TestCaseLocalData>,
209    /// This instance's libhegel handle, shared through the `Arc` with the
210    /// lifecycle that created it and with any [`child`](TestCase::child)
211    /// instances, so a `TestCase` that escapes its test (moved to a thread
212    /// that is never joined) keeps the handle alive rather than dangling —
213    /// its later draws fail cleanly because the case has finished.
214    /// [`clone`](TestCase::clone) instead gets a fresh handle
215    /// (`hegel_test_case_clone`) onto an independent stream of the same
216    /// test case, so two clones can be driven from different threads
217    /// concurrently without perturbing each other's values.
218    handle: Arc<CTestCase>,
219}
220
221impl Clone for TestCase {
222    fn clone(&self) -> Self {
223        TestCase {
224            global: self.global.clone(),
225            local: RefCell::new(self.local.borrow().clone()),
226            handle: Arc::new(self.handle.clone_handle()),
227        }
228    }
229}
230
231impl std::fmt::Debug for TestCase {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("TestCase").finish_non_exhaustive()
234    }
235}
236
237/// A callback invoked for each line of draw/note output during the final replay.
238pub(crate) type OutputSink = Arc<dyn Fn(&str) + Send + Sync>;
239
240thread_local! {
241    static OUTPUT_OVERRIDE: RefCell<Option<OutputSink>> = const { RefCell::new(None) };
242}
243
244/// Install a custom output sink for the duration of `f`, replacing the usual
245/// `eprintln!` behavior of draw and note output. Intended for tests that want
246/// to capture what a test case would print.
247///
248/// While active, notes and draws from the final replay go to `sink` instead of
249/// stderr. Non-final test cases still drop their draw/note output as usual.
250#[doc(hidden)]
251pub fn with_output_override<R>(sink: OutputSink, f: impl FnOnce() -> R) -> R {
252    struct Restore(Option<OutputSink>);
253    impl Drop for Restore {
254        fn drop(&mut self) {
255            OUTPUT_OVERRIDE.with(|cell| *cell.borrow_mut() = self.0.take());
256        }
257    }
258    let _restore = Restore(OUTPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(sink)));
259    f()
260}
261
262/// Return a clone of the currently-installed output sink, if any. Lets the
263/// run lifecycle's verbose output (stop-reason lines, per-test-case panic
264/// diagnostics) flow through `with_output_override` so tests can capture
265/// them in-process without having to spawn a subprocess.
266pub(crate) fn current_output_sink() -> Option<OutputSink> {
267    OUTPUT_OVERRIDE.with(|cell| cell.borrow().clone())
268}
269
270/// Emit a single line of verbose runner output, going through the
271/// installed output sink if there is one and otherwise to stderr.
272pub(crate) fn emit_verbose_line(msg: &str) {
273    if let Some(sink) = current_output_sink() {
274        sink(msg);
275    } else {
276        eprintln!("{}", msg);
277    }
278}
279
280impl TestCase {
281    /// `emit` is decided by the lifecycle (`run_lifecycle::run_test_case`):
282    /// true on a non-quiet final replay or in verbose mode, where drawn
283    /// values and notes should be surfaced.
284    pub(crate) fn new(handle: Arc<CTestCase>, emit: bool, mode: Mode) -> Self {
285        let override_sink = current_output_sink();
286        let on_draw: OutputSink = match override_sink {
287            Some(sink) if emit => sink,
288            _ if emit => Arc::new(|msg| eprintln!("{}", msg)),
289            _ => Arc::new(|_| {}),
290        };
291        TestCase {
292            global: Arc::new(TestCaseGlobalData {
293                mode,
294                emit,
295                draw_state: Mutex::new(DrawState {
296                    named_draw_counts: HashMap::new(),
297                    named_draw_repeatable: HashMap::new(),
298                    allocated_display_names: HashSet::new(),
299                }),
300            }),
301            local: RefCell::new(TestCaseLocalData {
302                span_depth: 0,
303                indent: 0,
304                on_draw,
305            }),
306            handle,
307        }
308    }
309
310    pub(crate) fn mode(&self) -> Mode {
311        self.global.mode
312    }
313
314    /// Acquire the shared draw-name bookkeeping for the duration of `f`.
315    ///
316    /// Held briefly around draw-state updates, never around whole user-visible
317    /// operations. The mutex is non-reentrant, so `f` must not call any other
318    /// method that also acquires it.
319    pub(crate) fn with_draw_state<R>(&self, f: impl FnOnce(&mut DrawState) -> R) -> R {
320        let mut guard = self.global.draw_state.lock();
321        f(&mut guard)
322    }
323
324    /// Draw a value from a generator.
325    ///
326    /// # Example
327    ///
328    /// ```no_run
329    /// use hegel::generators as gs;
330    ///
331    /// #[hegel::test]
332    /// fn my_test(tc: hegel::TestCase) {
333    ///     let x: i32 = tc.draw(gs::integers());
334    ///     let s: String = tc.draw(gs::text());
335    /// }
336    /// ```
337    ///
338    /// Note: when run inside a `#[hegel::test]`, `draw()` will typically be
339    /// rewritten to `__draw_named()` with an appropriate variable name
340    /// in order to give better test output.
341    pub fn draw<T: std::fmt::Debug>(&self, generator: impl Generator<T>) -> T {
342        self.__draw_named(generator, "draw", true)
343    }
344
345    /// Draw a value from a generator with a specific name for output.
346    ///
347    /// When `repeatable` is true, a counter suffix is appended (e.g. `x_1`, `x_2`).
348    /// When `repeatable` is false, reusing the same name panics.
349    ///
350    /// Using the same name with different values of `repeatable` is an error.
351    ///
352    /// On the final replay of a failing test case, this prints:
353    /// - `let name = value;` (when not repeatable)
354    /// - `let name_N = value;` (when repeatable)
355    ///
356    /// Not intended for direct use. This is the target that `#[hegel::test]` rewrites `draw()`
357    /// calls to where appropriate.
358    pub fn __draw_named<T: std::fmt::Debug>(
359        &self,
360        generator: impl Generator<T>,
361        name: &str,
362        repeatable: bool,
363    ) -> T {
364        let value = generator.do_draw(self);
365        if self.local.borrow().span_depth == 0 {
366            self.record_named_draw(&value, name, repeatable);
367        }
368        value
369    }
370
371    /// Draw a value from a generator without recording it in the output.
372    ///
373    /// Unlike [`draw`](Self::draw), this does not require `T: Debug` and
374    /// will not print the value in the failing-test summary.
375    pub fn draw_silent<T>(&self, generator: impl Generator<T>) -> T {
376        generator.do_draw(self)
377    }
378
379    /// Assume a condition is true. If false, reject the current test input.
380    ///
381    /// # Example
382    ///
383    /// ```no_run
384    /// use hegel::generators as gs;
385    ///
386    /// #[hegel::test]
387    /// fn my_test(tc: hegel::TestCase) {
388    ///     let age: u32 = tc.draw(gs::integers());
389    ///     tc.assume(age >= 18);
390    /// }
391    /// ```
392    pub fn assume(&self, condition: bool) {
393        if !condition {
394            self.reject();
395        }
396    }
397
398    /// Reject the current test input unconditionally.
399    ///
400    /// Equivalent to `assume(false)`, but with a `!` return type so that code
401    /// following the call is statically known to be unreachable.
402    ///
403    /// # Example
404    ///
405    /// ```no_run
406    /// use hegel::generators as gs;
407    ///
408    /// #[hegel::test]
409    /// fn my_test(tc: hegel::TestCase) {
410    ///     let n: i32 = tc.draw(gs::integers());
411    ///     let positive: u32 = match u32::try_from(n) {
412    ///         Ok(v) => v,
413    ///         Err(_) => tc.reject(),
414    ///     };
415    ///     let _ = positive;
416    /// }
417    /// ```
418    pub fn reject(&self) -> ! {
419        raise_control(AssumeFailed);
420    }
421
422    /// Note a message which will be displayed with the reported failing test case.
423    ///
424    /// At the default verbosity, only prints during the final replay of a
425    /// failing test case. At [`Verbose`](crate::Verbosity::Verbose) or
426    /// higher, prints on every test case.
427    ///
428    /// # Example
429    ///
430    /// ```no_run
431    /// use hegel::generators as gs;
432    ///
433    /// #[hegel::test]
434    /// fn my_test(tc: hegel::TestCase) {
435    ///     let x: i32 = tc.draw(gs::integers());
436    ///     tc.note(&format!("Generated x = {}", x));
437    /// }
438    /// ```
439    pub fn note(&self, message: &str) {
440        let local = self.local.borrow();
441        let indent = local.indent;
442        (local.on_draw)(&format!("{:indent$}{}", "", message, indent = indent));
443    }
444
445    /// Record a targeting observation to help the engine find extreme inputs.
446    ///
447    /// Call this inside a test body to guide generation toward inputs that
448    /// maximise `score`. Inside a `#[hegel::test]`, `#[hegel::main]`, or
449    /// `#[hegel::standalone_function]` body, `tc.target(expr)` is rewritten
450    /// to call [`target_labelled`](Self::target_labelled) with the source
451    /// text of `expr` as the label, so different targeting expressions are
452    /// tracked separately by default. Outside that rewrite, `tc.target(score)`
453    /// uses the empty label.
454    ///
455    /// Has no effect during replays or if the test case has been aborted.
456    ///
457    /// # Example
458    ///
459    /// ```no_run
460    /// use hegel::generators as gs;
461    ///
462    /// #[hegel::test]
463    /// fn my_test(tc: hegel::TestCase) {
464    ///     let n: u32 = tc.draw(gs::integers::<u32>());
465    ///     tc.target(n as f64);
466    /// }
467    /// ```
468    pub fn target(&self, score: f64) {
469        self.target_labelled(score, "");
470    }
471
472    /// Record a targeting observation under an explicit label.
473    ///
474    /// The label distinguishes multiple simultaneous targeting goals.
475    /// Use this directly when you want a specific label string;
476    /// [`target`](Self::target) is the usual entry point and will be
477    /// rewritten to call this with the source expression as the label
478    /// inside a `#[hegel::test]` body.
479    ///
480    /// Has no effect during replays or if the test case has been aborted.
481    pub fn target_labelled(&self, score: f64, label: impl Into<String>) {
482        let label = label.into();
483        let outcome = self.with_ctc(|ctc| ctc.target(score, &label));
484        if let Err(rc) = outcome {
485            raise_for_rc(rc);
486        }
487    }
488
489    /// Run `body` in a loop that should runs "logically infinitely" or until
490    /// error. Roughly equivalent to a `loop` but with better interaction with
491    /// the test runner: This loop will never exit until the test case completes.
492    ///
493    /// At the start of each iteration a `// Loop iteration N` note is emitted
494    /// into the failing-test replay output.
495    ///
496    /// # Example
497    ///
498    /// ```no_run
499    /// use hegel::generators as gs;
500    ///
501    /// #[hegel::test]
502    /// fn my_test(tc: hegel::TestCase) {
503    ///     let mut total: i32 = 0;
504    ///     tc.repeat(|| {
505    ///         let n: i32 = tc.draw(gs::integers().min_value(0).max_value(10));
506    ///         total += n;
507    ///         assert!(total >= 0);
508    ///     });
509    /// }
510    /// ```
511    pub fn repeat<F: FnMut()>(&self, mut body: F) -> ! {
512        if self.global.mode == Mode::SingleTestCase {
513            self.repeat_single_test_case(&mut body);
514        }
515        self.repeat_property_test(&mut body);
516    }
517
518    fn repeat_single_test_case(&self, body: &mut dyn FnMut()) -> ! {
519        let mut iteration: u64 = 0;
520        loop {
521            iteration += 1;
522            self.note(&format!("// Repetition #{}", iteration));
523
524            let prev_indent = self.local.borrow().indent;
525            self.local.borrow_mut().indent = prev_indent + 2;
526            let result = catch_unwind(AssertUnwindSafe(&mut *body));
527            self.local.borrow_mut().indent = prev_indent;
528
529            match result {
530                Ok(()) => {}
531                Err(e) if e.downcast_ref::<AssumeFailed>().is_some() => {}
532                Err(e) => resume_unwind(e),
533            }
534        }
535    }
536
537    fn repeat_property_test(&self, body: &mut dyn FnMut()) -> ! {
538        use crate::generators::{booleans, integers};
539
540        let max_safe_min_size = usize::try_from(1u64 << 40).unwrap_or(usize::MAX / 2);
541        let min_size = self.draw_silent(integers::<usize>().max_value(max_safe_min_size));
542
543        let mut collection = Collection::new(self, min_size, None);
544        let mut iteration: u64 = 0;
545
546        while collection.more() {
547            iteration += 1;
548            self.note(&format!("// Repetition #{}", iteration));
549
550            let prev_indent = self.local.borrow().indent;
551            self.local.borrow_mut().indent = prev_indent + 2;
552            let result = catch_unwind(AssertUnwindSafe(&mut *body));
553            self.local.borrow_mut().indent = prev_indent;
554
555            match result {
556                Ok(()) => {}
557                Err(e) if e.downcast_ref::<AssumeFailed>().is_some() => {}
558                Err(e)
559                    if e.downcast_ref::<StopTest>().is_some()
560                        || e.downcast_ref::<InvalidArgument>().is_some()
561                        || e.downcast_ref::<InternalError>().is_some() =>
562                {
563                    resume_unwind(e);
564                }
565                Err(e) => {
566                    self.draw_silent(booleans());
567                    resume_unwind(e);
568                }
569            }
570        }
571
572        raise_control(LoopDone);
573    }
574
575    pub(crate) fn child(&self, extra_indent: usize) -> Self {
576        let local = self.local.borrow();
577        TestCase {
578            global: self.global.clone(),
579            local: RefCell::new(TestCaseLocalData {
580                span_depth: 0,
581                indent: local.indent + extra_indent,
582                on_draw: local.on_draw.clone(),
583            }),
584            handle: Arc::clone(&self.handle),
585        }
586    }
587
588    fn record_named_draw<T: std::fmt::Debug>(&self, value: &T, name: &str, repeatable: bool) {
589        let emit = self.global.emit;
590
591        let display_name = self.with_draw_state(|draw_state| {
592            match draw_state.named_draw_repeatable.get(name) {
593                Some(&prev) if prev != repeatable => {
594                    hegel_internal_error!(
595                        "__draw_named: name {:?} used with inconsistent repeatable flag \
596                         (was {}, now {})",
597                        name,
598                        prev,
599                        repeatable
600                    );
601                }
602                Some(_) => {}
603                None => {
604                    draw_state
605                        .named_draw_repeatable
606                        .insert(name.to_string(), repeatable);
607                }
608            }
609
610            let current_count = match draw_state.named_draw_counts.get_mut(name) {
611                Some(count) => {
612                    *count += 1;
613                    *count
614                }
615                None => {
616                    draw_state.named_draw_counts.insert(name.to_string(), 1);
617                    1
618                }
619            };
620
621            if !repeatable && current_count > 1 {
622                hegel_internal_error!(
623                    "__draw_named: name {:?} used more than once but repeatable is false",
624                    name
625                );
626            }
627
628            if !emit {
629                return None;
630            }
631
632            let display = if repeatable {
633                let mut candidate = current_count;
634                loop {
635                    let name = format!("{}_{}", name, candidate);
636                    if draw_state.allocated_display_names.insert(name.clone()) {
637                        break name;
638                    }
639                    candidate += 1;
640                }
641            } else {
642                let name = name.to_string();
643                draw_state.allocated_display_names.insert(name.clone());
644                name
645            };
646            Some(display)
647        });
648
649        let Some(display_name) = display_name else {
650            return;
651        };
652
653        let local = self.local.borrow();
654        let indent = local.indent;
655
656        (local.on_draw)(&format!(
657            "{:indent$}let {} = {:?};",
658            "",
659            display_name,
660            value,
661            indent = indent
662        ));
663    }
664
665    /// Run `f` with this instance's own libhegel handle.
666    ///
667    /// Each `TestCase` instance owns its handle, so there is no shared lock to
668    /// take here: libhegel serialises a single handle against concurrent use
669    /// itself (returning `HEGEL_E_CONCURRENT_USE`), and clones each carry their
670    /// own handle and lock.
671    pub(crate) fn with_ctc<R>(&self, f: impl FnOnce(&CTestCase) -> R) -> R {
672        f(&self.handle)
673    }
674
675    #[doc(hidden)]
676    pub fn start_span(&self, label: u64) {
677        self.local.borrow_mut().span_depth += 1;
678        if let Err(rc) = self.with_ctc(|ctc| ctc.start_span(label)) {
679            let mut local = self.local.borrow_mut();
680            hegel_internal_assert!(local.span_depth > 0);
681            local.span_depth -= 1;
682            drop(local);
683            raise_for_rc(rc);
684        }
685    }
686
687    #[doc(hidden)]
688    pub fn stop_span(&self, discard: bool) {
689        {
690            let mut local = self.local.borrow_mut();
691            hegel_internal_assert!(local.span_depth > 0);
692            local.span_depth -= 1;
693        }
694        if let Err(rc) = self.with_ctc(|ctc| ctc.stop_span(discard)) {
695            raise_for_rc(rc);
696        }
697    }
698}
699
700impl TestCase {
701    /// Run a draw against this instance's libhegel handle, raising the
702    /// appropriate control-flow payload on failure.
703    fn draw_or_raise<T>(
704        &self,
705        f: impl FnOnce(&CTestCase) -> Result<T, hegel_c::hegel_result_t>,
706    ) -> T {
707        self.with_ctc(f).unwrap_or_else(|rc| raise_for_rc(rc))
708    }
709
710    /// Draw an integer in `[min_value, max_value]` (both within `i64`).
711    pub(crate) fn generate_integer_i64(&self, min_value: i64, max_value: i64) -> i64 {
712        self.draw_or_raise(|ctc| ctc.generate_integer(min_value, max_value))
713    }
714
715    /// Draw an integer with bounds given as two's-complement little-endian
716    /// byte encodings, returning the value's encoding sign-extended to 17
717    /// bytes.
718    pub(crate) fn generate_integer_le17(&self, min_value: &[u8], max_value: &[u8]) -> [u8; 17] {
719        self.draw_or_raise(|ctc| ctc.generate_integer_big(min_value, max_value))
720    }
721
722    /// Draw a float according to the full libhegel spec.
723    #[allow(clippy::too_many_arguments)]
724    pub(crate) fn generate_float(
725        &self,
726        width: u32,
727        min_value: f64,
728        max_value: f64,
729        allow_nan: bool,
730        allow_infinity: bool,
731        exclude_min: bool,
732        exclude_max: bool,
733        smallest_nonzero_magnitude: f64,
734    ) -> f64 {
735        self.draw_or_raise(|ctc| {
736            ctc.generate_float(
737                width,
738                min_value,
739                max_value,
740                allow_nan,
741                allow_infinity,
742                exclude_min,
743                exclude_max,
744                smallest_nonzero_magnitude,
745            )
746        })
747    }
748
749    /// Draw a boolean that is `true` with probability `p`.
750    pub(crate) fn generate_boolean(&self, p: f64) -> bool {
751        self.draw_or_raise(|ctc| ctc.generate_boolean(p))
752    }
753
754    /// Draw a byte string with length in `[min_size, max_size]`.
755    pub(crate) fn generate_bytes(&self, min_size: usize, max_size: usize) -> Vec<u8> {
756        self.draw_or_raise(|ctc| ctc.generate_bytes(min_size as u64, max_size as u64))
757    }
758
759    /// Draw a string described by a prebuilt libhegel string generator.
760    pub(crate) fn generate_string(&self, generator: &crate::ffi::StringGenerator) -> String {
761        self.draw_or_raise(|ctc| ctc.generate_string(generator))
762    }
763
764    /// Draw a Gregorian calendar date in `[min, max]`.
765    pub(crate) fn generate_date(
766        &self,
767        min: hegel_c::hegel_date_t,
768        max: hegel_c::hegel_date_t,
769    ) -> hegel_c::hegel_date_t {
770        self.draw_or_raise(|ctc| ctc.generate_date(min, max))
771    }
772
773    /// Draw a time of day in `[min, max]`.
774    pub(crate) fn generate_time(
775        &self,
776        min: hegel_c::hegel_time_t,
777        max: hegel_c::hegel_time_t,
778    ) -> hegel_c::hegel_time_t {
779        self.draw_or_raise(|ctc| ctc.generate_time(min, max))
780    }
781
782    /// Draw a naive datetime in `[min, max]`.
783    pub(crate) fn generate_datetime(
784        &self,
785        min: hegel_c::hegel_datetime_t,
786        max: hegel_c::hegel_datetime_t,
787    ) -> hegel_c::hegel_datetime_t {
788        self.draw_or_raise(|ctc| ctc.generate_datetime(min, max))
789    }
790
791    /// Draw a UUID's 16 big-endian bytes, optionally forcing the version.
792    pub(crate) fn generate_uuid(&self, version: Option<u8>) -> [u8; 16] {
793        self.draw_or_raise(|ctc| ctc.generate_uuid(version))
794    }
795
796    /// Draw an IPv4 address.
797    pub(crate) fn generate_ipv4(&self) -> std::net::Ipv4Addr {
798        self.draw_or_raise(|ctc| ctc.generate_ipv4())
799    }
800
801    /// Draw an IPv6 address.
802    pub(crate) fn generate_ipv6(&self) -> std::net::Ipv6Addr {
803        self.draw_or_raise(|ctc| ctc.generate_ipv6())
804    }
805}
806
807/// Uses the backend to determine collection sizing.
808///
809/// The backend-side collection object is created lazily on the first call to
810/// [`more()`](Collection::more).
811pub struct Collection<'a> {
812    tc: &'a TestCase,
813    min_size: usize,
814    max_size: Option<usize>,
815    handle: Option<i64>,
816    finished: bool,
817}
818
819impl<'a> Collection<'a> {
820    /// Create a new backend-managed collection.
821    pub fn new(tc: &'a TestCase, min_size: usize, max_size: Option<usize>) -> Self {
822        Collection {
823            tc,
824            min_size,
825            max_size,
826            handle: None,
827            finished: false,
828        }
829    }
830
831    fn ensure_initialized(&mut self) -> i64 {
832        if self.handle.is_none() {
833            let result = self.tc.with_ctc(|ctc| {
834                ctc.new_collection(self.min_size as u64, self.max_size.map(|m| m as u64))
835            });
836            let id = match result {
837                Ok(id) => id,
838                Err(rc) => raise_for_rc(rc), // nocov
839            };
840            self.handle = Some(id);
841        }
842        self.handle.unwrap()
843    }
844
845    /// Ask the backend whether to produce another element.
846    pub fn more(&mut self) -> bool {
847        if self.finished {
848            return false;
849        }
850        let handle = self.ensure_initialized();
851        let result = match self.tc.with_ctc(|ctc| ctc.collection_more(handle)) {
852            Ok(b) => b,
853            Err(rc) => {
854                self.finished = true;
855                raise_for_rc(rc);
856            }
857        };
858        if !result {
859            self.finished = true;
860        }
861        result
862    }
863
864    /// Reject the last element (don't count it towards the size budget).
865    pub fn reject(&mut self, why: Option<&str>) {
866        if self.finished {
867            return;
868        }
869        let handle = self.ensure_initialized();
870        let _ = self.tc.with_ctc(|ctc| ctc.collection_reject(handle, why));
871    }
872}
873
874#[doc(hidden)]
875pub mod labels {
876    use hegel_c::hegel_label_t;
877
878    pub const LIST: u64 = hegel_label_t::HEGEL_LABEL_LIST as u64;
879    pub const LIST_ELEMENT: u64 = hegel_label_t::HEGEL_LABEL_LIST_ELEMENT as u64;
880    pub const SET: u64 = hegel_label_t::HEGEL_LABEL_SET as u64;
881    pub const SET_ELEMENT: u64 = hegel_label_t::HEGEL_LABEL_SET_ELEMENT as u64;
882    pub const MAP: u64 = hegel_label_t::HEGEL_LABEL_MAP as u64;
883    pub const MAP_ENTRY: u64 = hegel_label_t::HEGEL_LABEL_MAP_ENTRY as u64;
884    pub const TUPLE: u64 = hegel_label_t::HEGEL_LABEL_TUPLE as u64;
885    pub const ONE_OF: u64 = hegel_label_t::HEGEL_LABEL_ONE_OF as u64;
886    pub const OPTIONAL: u64 = hegel_label_t::HEGEL_LABEL_OPTIONAL as u64;
887    pub const FIXED_DICT: u64 = hegel_label_t::HEGEL_LABEL_FIXED_DICT as u64;
888    pub const FLAT_MAP: u64 = hegel_label_t::HEGEL_LABEL_FLAT_MAP as u64;
889    pub const FILTER: u64 = hegel_label_t::HEGEL_LABEL_FILTER as u64;
890    pub const MAPPED: u64 = hegel_label_t::HEGEL_LABEL_MAPPED as u64;
891    pub const SAMPLED_FROM: u64 = hegel_label_t::HEGEL_LABEL_SAMPLED_FROM as u64;
892    pub const ENUM_VARIANT: u64 = hegel_label_t::HEGEL_LABEL_ENUM_VARIANT as u64;
893    pub const FEATURE_FLAG: u64 = hegel_label_t::HEGEL_LABEL_FEATURE_FLAG as u64;
894}
895
896#[cfg(test)]
897#[path = "../tests/embedded/test_case_tests.rs"]
898mod tests;
899
900/// The conventional full ranges for the structured draws: years 1..=9999
901/// (what Hypothesis's `dates()` spans) and the whole microsecond-resolution
902/// day.
903pub(crate) mod full_ranges {
904    pub(crate) const MIN_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
905        year: 1,
906        month: 1,
907        day: 1,
908    };
909    pub(crate) const MAX_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
910        year: 9999,
911        month: 12,
912        day: 31,
913    };
914    pub(crate) const MIDNIGHT: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
915        hour: 0,
916        minute: 0,
917        second: 0,
918        microsecond: 0,
919    };
920    pub(crate) const LAST_MICROSECOND: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
921        hour: 23,
922        minute: 59,
923        second: 59,
924        microsecond: 999_999,
925    };
926    pub(crate) const MIN_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
927        date: MIN_DATE,
928        time: MIDNIGHT,
929    };
930    pub(crate) const MAX_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
931        date: MAX_DATE,
932        time: LAST_MICROSECOND,
933    };
934}