Skip to main content

hegel/
test_case.rs

1use crate::control::{
2    AssumeFailed, InternalError, InvalidArgument, LeafBudgetExceeded, LoopDone, StopTest,
3    hegel_internal_assert, hegel_internal_error, raise_control,
4};
5use crate::ffi::CTestCase;
6use crate::generators::{Generator, PrintableGenerator};
7use crate::pretty::PrettyPrinter;
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_RETRY` — a recursive generation attempt outgrew its leaf
66///   budget; unwinds to the `RecursiveGenerator` draw that opened the
67///   recursion scope, which discards the attempt and retries.
68/// - `HEGEL_E_INVALID_ARG` — a caller-supplied argument (typically a
69///   generator argument) was semantically invalid; the diagnostic is read
70///   synchronously from this thread's libhegel error context.
71/// - `HEGEL_E_ALREADY_COMPLETE` — the test case has finished. Unreachable
72///   from a test body (the outcome is reported only after the body returns),
73///   so it means a `TestCase` outlived its test — typically moved to a thread
74///   that was never joined — and the panic message says so.
75/// - anything else — an engine/framework invariant we don't expect on the hot
76///   path; treat it as an internal error rather than a shrinkable failure.
77///   This includes `HEGEL_E_CONCURRENT_USE`: the frontend never drives one
78///   handle from two threads (`clone` forks a fresh handle, `TestCase` is
79///   `!Sync`, and `hegel_mark_complete` waits instead of erroring), so it
80///   cannot arise here in correct use.
81#[track_caller]
82pub(crate) fn raise_for_rc(rc: hegel_c::hegel_result_t) -> ! {
83    use hegel_c::hegel_result_t::*;
84    match rc {
85        HEGEL_E_STOP_TEST => raise_control(StopTest),
86        HEGEL_E_ASSUME => raise_control(AssumeFailed),
87        HEGEL_E_RETRY => raise_control(LeafBudgetExceeded),
88        HEGEL_E_INVALID_ARG => invalid_argument!("{}", crate::ffi::last_error_string()),
89        HEGEL_E_ALREADY_COMPLETE => panic!(
90            "this test case has already finished; was the TestCase moved to a \
91             thread that outlived the test? Join any thread that draws before \
92             the test returns."
93        ),
94        other => hegel_internal_error!(
95            "libhegel returned unexpected code {}: {}",
96            other as i32,
97            crate::ffi::last_error_string()
98        ),
99    }
100}
101
102pub(crate) struct TestCaseGlobalData {
103    /// Whether drawn-value records and notes are surfaced for this test case
104    /// (true on the final replay of a failure — unless quiet — or when
105    /// verbose output is on, and for every non-final case of a run already
106    /// known to be nondeterministic, whose failures are reported from the
107    /// discovering execution).
108    /// When false `on_draw` is a no-op, so the draw-recording bookkeeping in
109    /// [`TestCase::record_named_draw`] (display-name allocation + `Debug`
110    /// rendering of the value) can be skipped entirely.
111    emit: bool,
112    /// When this test case started, shared by every clone so the
113    /// `[worker N +X.XXXms]` offsets stamped on concurrent workers' output
114    /// lines are comparable across workers.
115    case_start: std::time::Instant,
116}
117
118/// The width drawn-value documents are laid out to.
119const PRINTER_MAX_WIDTH: u64 = crate::pretty::DEFAULT_MAX_WIDTH;
120
121/// Marks a printed draw as in progress: `printing_depth` is raised for the
122/// duration of the enclosing `draw_and_print` call so that a `tc.note()` or
123/// nested `tc.draw` made by a hand-written generator body behaves exactly as
124/// it does inside a combinator span — the note buffers, the nested draw
125/// stays silent — instead of re-entering the printer lock the enclosing draw
126/// already holds. Dropping the scope restores the depth and flushes the
127/// buffered notes, so they land right after their draw's line even when the
128/// draw unwinds (a failed assumption, a budget stop). This scope is the only
129/// thing that defers a note: outside it, notes and draw lines write to the
130/// instance's print region directly, in call order.
131struct PrintingDrawScope<'a> {
132    tc: &'a TestCase,
133}
134
135impl<'a> PrintingDrawScope<'a> {
136    fn new(tc: &'a TestCase) -> Self {
137        tc.local.borrow_mut().printing_depth += 1;
138        PrintingDrawScope { tc }
139    }
140}
141
142impl Drop for PrintingDrawScope<'_> {
143    fn drop(&mut self) {
144        self.tc.local.borrow_mut().printing_depth -= 1;
145        self.tc.flush_pending_notes();
146    }
147}
148
149/// Emit one note line: the worker attribution `prefix` (empty outside
150/// concurrent workers), an indent prefix, the message (with any embedded
151/// newlines breaking at the note's indentation), and a closing line break.
152fn emit_note_line(printer: &mut PrettyPrinter, prefix: &str, indent: usize, message: &str) {
153    printer.text(prefix);
154    printer.text(&" ".repeat(indent));
155    printer.shift_indent(indent as isize);
156    printer.text(message);
157    printer.shift_indent(-(indent as isize));
158    printer.hard_break();
159}
160
161#[derive(Default)]
162pub(crate) struct DrawState {
163    named_draw_counts: HashMap<String, usize>,
164    named_draw_repeatable: HashMap<String, bool>,
165    allocated_display_names: HashSet<String>,
166}
167
168#[derive(Clone)]
169pub(crate) struct TestCaseLocalData {
170    /// Engine spans currently open on this instance (`start_span` without a
171    /// matching `stop_span`). It silences nested named draws and never
172    /// defers notes, which is `printing_depth`'s job.
173    span_depth: usize,
174    /// Printed draws currently in progress on this instance (see
175    /// [`PrintingDrawScope`]).
176    printing_depth: usize,
177    indent: usize,
178    on_draw: OutputSink,
179}
180
181/// A handle to the current test case.
182///
183/// This is passed to `#[hegel::test]` functions and provides methods
184/// for drawing values, making assumptions, and recording notes.
185///
186/// # Example
187///
188/// ```no_run
189/// use hegel::generators as gs;
190///
191/// #[hegel::test]
192/// fn my_test(tc: hegel::TestCase) {
193///     let x: i32 = tc.draw(gs::integers());
194///     tc.assume(x > 0);
195///     tc.note(&format!("x = {}", x));
196/// }
197/// ```
198///
199/// # Threading
200///
201/// `TestCase` is `Send` but not `Sync`. To drive generation from another
202/// thread, clone the test case and move the clone. Each clone generates
203/// from its own *independent stream* of choices: draws on one clone never
204/// perturb the values any other clone (or the original) produces, so
205/// several threads can generate concurrently and the test stays fully
206/// deterministic — the same seed replays the same values on every stream,
207/// failures shrink normally, and the shrunk counterexample replays exactly.
208///
209/// ```no_run
210/// use hegel::generators as gs;
211///
212/// #[hegel::test]
213/// fn my_test(tc: hegel::TestCase) {
214///     let tc_worker = tc.clone();
215///     let handle = std::thread::spawn(move || {
216///         tc_worker.draw(gs::integers::<i32>())
217///     });
218///     let _b: bool = tc.draw(gs::booleans());
219///     let n = handle.join().unwrap();
220///     let _ = n;
221/// }
222/// ```
223///
224/// ## What is guaranteed
225///
226/// Each clone owns its own stream, so a clone may be moved to and driven
227/// from another thread freely, concurrently with every other clone. A
228/// *single* clone may only be driven by one thread at a time — the backend
229/// rejects concurrent use of one handle outright — which is why you `clone`
230/// to hand work to a thread rather than sharing one `TestCase` across
231/// threads (the type is `!Sync`, so the compiler enforces this too).
232///
233/// The clones share the test case's *outcome*: the whole family passes,
234/// fails, or is rejected as one test case, and the choice budget is shared
235/// across all streams. Everything else about generation is per-stream.
236///
237/// ## What is not guaranteed
238///
239/// Determinism extends exactly as far as your own code's determinism. If
240/// threads race on *your* state — for example, which of two clones first
241/// consumes a value from a shared queue — Hegel replays each stream's
242/// values faithfully, but your test may still behave differently run to
243/// run, and such failures may not reproduce or shrink well.
244///
245/// Variable pools and engine-managed collections are shared across clones
246/// (one created through any clone works on any other). Using one such
247/// object from two threads *at the same time* makes the affected draws
248/// depend on scheduling order, which brings back the same replay caveat.
249///
250/// ## Panics inside spawned threads
251///
252/// If a worker thread panics with an assumption failure or a backend
253/// `StopTest`, that panic stays inside the thread's `JoinHandle` until
254/// the main thread joins it. The main thread is responsible for
255/// propagating (or suppressing) the panic — typically by calling
256/// `handle.join().unwrap()`, which resumes the panic on the main thread
257/// so Hegel's runner can observe it.
258pub struct TestCase {
259    global: Arc<TestCaseGlobalData>,
260    local: RefCell<TestCaseLocalData>,
261    /// This instance's libhegel handle, shared through the `Arc` with the
262    /// lifecycle that created it and with any [`child`](TestCase::child)
263    /// instances, so a `TestCase` that escapes its test (moved to a thread
264    /// that is never joined) keeps the handle alive rather than dangling —
265    /// its later draws fail cleanly because the case has finished.
266    /// [`clone`](TestCase::clone) instead gets a fresh handle
267    /// (`hegel_test_case_clone`) onto an independent stream of the same
268    /// test case, so two clones can be driven from different threads
269    /// concurrently without perturbing each other's values.
270    handle: Arc<CTestCase>,
271    /// This instance's printer onto its own region of the family document,
272    /// fetched on first use. The engine anchors a clone's region when the
273    /// clone is made, so where this instance's output appears is fixed even
274    /// though the handle is fetched lazily — and since each instance owns
275    /// its region outright, no lock is ever held across a draw: concurrent
276    /// clones write concurrently, and the document assembles deterministically
277    /// by anchor position.
278    printer: RefCell<Option<PrettyPrinter>>,
279    /// Notes recorded while this instance was printing a draw
280    /// (`printing_depth > 0`, e.g. from inside a composite body). Emitting
281    /// them inline would splice text into the middle of the draw's
282    /// `let … = …;` line, so they are buffered here and flushed, in order,
283    /// by the [`PrintingDrawScope`] that deferred them once the draw's line
284    /// is done. A note can only buffer during a printed draw on this same
285    /// instance, so the buffer is instance-local and no other instance can
286    /// flush this one's notes into its own region out of order.
287    pending_notes: RefCell<Vec<(String, usize, String)>>,
288    /// Draw-name bookkeeping for this instance's naming scope, behind a
289    /// blocking, non-reentrant mutex that only serialises the frontend's own
290    /// accounting (no method holds it while calling back into `TestCase`).
291    /// Shared with [`clone`](TestCase::clone) instances (a clone draws for
292    /// the same test body, so its names live in the same scope) but fresh
293    /// for every [`child`](TestCase::child), which opens a new scope: a
294    /// stateful rule's names are scoped to that one invocation.
295    draw_state: Arc<Mutex<DrawState>>,
296}
297
298impl Clone for TestCase {
299    fn clone(&self) -> Self {
300        TestCase {
301            global: self.global.clone(),
302            local: RefCell::new(self.local.borrow().clone()),
303            handle: Arc::new(self.handle.clone_handle()),
304            printer: RefCell::new(None),
305            pending_notes: RefCell::new(Vec::new()),
306            draw_state: Arc::clone(&self.draw_state),
307        }
308    }
309}
310
311impl std::fmt::Debug for TestCase {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        f.debug_struct("TestCase").finish_non_exhaustive()
314    }
315}
316
317/// A callback invoked for each line of run output — engine progress lines,
318/// draw/note output, verbose diagnostics, and the final failure report.
319pub(crate) type OutputSink = Arc<dyn Fn(&str) + Send + Sync>;
320
321thread_local! {
322    static OUTPUT_OVERRIDE: RefCell<Option<OutputSink>> = const { RefCell::new(None) };
323}
324
325/// Install a custom output sink for the duration of `f`, replacing stderr as
326/// the destination for all of Hegel's run output. Intended for tests that
327/// want to capture what a test run would print.
328///
329/// A run started while the override is active resolves it once, at start,
330/// and routes everything through it for the run's lifetime: the engine's own
331/// progress lines (emitted while the engine runs between test cases), draw
332/// and note output —
333/// including from clones driven on other threads — verbose per-case
334/// diagnostics and stop reasons, and the final failure report with its
335/// reproducer line. Which of those exist at all is still governed by
336/// [`Verbosity`](crate::Verbosity); the override only changes where they go.
337#[doc(hidden)]
338pub fn with_output_override<R>(sink: OutputSink, f: impl FnOnce() -> R) -> R {
339    struct Restore(Option<OutputSink>);
340    impl Drop for Restore {
341        fn drop(&mut self) {
342            OUTPUT_OVERRIDE.with(|cell| *cell.borrow_mut() = self.0.take());
343        }
344    }
345    let _restore = Restore(OUTPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(sink)));
346    f()
347}
348
349/// Return a clone of the currently-installed output sink, if any. Lets the
350/// run lifecycle's verbose output (stop-reason lines, per-test-case panic
351/// diagnostics) flow through `with_output_override` so tests can capture
352/// them in-process without having to spawn a subprocess.
353pub(crate) fn current_output_sink() -> Option<OutputSink> {
354    OUTPUT_OVERRIDE.with(|cell| cell.borrow().clone())
355}
356
357/// Emit a single line of verbose runner output, going through the
358/// installed output sink if there is one and otherwise to stderr.
359///
360/// Resolves the sink thread-locally at emit time, so it is only correct for
361/// output produced synchronously on the thread that installed the override
362/// (e.g. an explicit test case). A run resolves its destination once up
363/// front instead — see [`RunOutput`].
364pub(crate) fn emit_verbose_line(msg: &str) {
365    if let Some(sink) = current_output_sink() {
366        sink(msg);
367    } else {
368        eprintln!("{}", msg);
369    }
370}
371
372/// The output destination a run resolved when it started.
373///
374/// Resolved exactly once, on the thread that starts the run, from the
375/// installed override ([`with_output_override`]) — and then carried by the
376/// run itself. Everything the run emits later flows through this value,
377/// wherever it happens: clones driven on other threads (and a run pulled
378/// from a different thread than the one that started it) never see the
379/// starting thread's thread-local override, so resolving lazily at emit
380/// time would send their output to the wrong place.
381#[derive(Clone)]
382pub(crate) struct RunOutput {
383    sink: Option<OutputSink>,
384}
385
386impl RunOutput {
387    /// Resolve the destination for a run starting now on this thread: the
388    /// installed override if there is one, stderr otherwise.
389    pub(crate) fn resolve() -> Self {
390        RunOutput {
391            sink: current_output_sink(),
392        }
393    }
394
395    /// The resolved sink, for handing to the engine and to test cases;
396    /// `None` means stderr.
397    pub(crate) fn sink(&self) -> Option<&OutputSink> {
398        self.sink.as_ref()
399    }
400
401    /// Emit one line of output (no trailing newline).
402    pub(crate) fn line(&self, msg: &str) {
403        match &self.sink {
404            Some(sink) => sink(msg),
405            None => eprintln!("{msg}"),
406        }
407    }
408
409    /// Emit a pre-rendered, newline-terminated block exactly as it would
410    /// appear on stderr; the sink receives it as individual lines.
411    pub(crate) fn block(&self, text: &str) {
412        match &self.sink {
413            Some(sink) => {
414                for line in text.trim_end_matches('\n').split('\n') {
415                    sink(line);
416                }
417            }
418            None => eprint!("{text}"),
419        }
420    }
421}
422
423impl TestCase {
424    /// `emit` is decided by the lifecycle (`run_lifecycle::run_test_case`):
425    /// true on a non-quiet final replay, in verbose mode, or for a non-final
426    /// case the engine stamped as belonging to a nondeterministic run —
427    /// wherever drawn values and notes should be surfaced. `sink` is the run's resolved
428    /// output destination ([`RunOutput::sink`]) — passed in rather than read
429    /// from the thread-local override so that a test case created here and
430    /// then driven from another thread still prints to the right place.
431    pub(crate) fn new(handle: Arc<CTestCase>, emit: bool, sink: Option<OutputSink>) -> Self {
432        let on_draw: OutputSink = if emit {
433            sink.unwrap_or_else(|| Arc::new(|msg| eprintln!("{}", msg)))
434        } else {
435            Arc::new(|_| {})
436        };
437        TestCase {
438            global: Arc::new(TestCaseGlobalData {
439                emit,
440                case_start: std::time::Instant::now(),
441            }),
442            local: RefCell::new(TestCaseLocalData {
443                span_depth: 0,
444                printing_depth: 0,
445                indent: 0,
446                on_draw,
447            }),
448            handle,
449            printer: RefCell::new(None),
450            pending_notes: RefCell::new(Vec::new()),
451            draw_state: Arc::new(Mutex::new(DrawState::default())),
452        }
453    }
454
455    /// Acquire this scope's draw-name bookkeeping for the duration of `f`.
456    ///
457    /// Held briefly around draw-state updates, never around whole user-visible
458    /// operations. The mutex is non-reentrant, so `f` must not call any other
459    /// method that also acquires it.
460    pub(crate) fn with_draw_state<R>(&self, f: impl FnOnce(&mut DrawState) -> R) -> R {
461        let mut guard = self.draw_state.lock();
462        f(&mut guard)
463    }
464
465    /// Draw a value from a generator.
466    ///
467    /// # Example
468    ///
469    /// ```no_run
470    /// use hegel::generators as gs;
471    ///
472    /// #[hegel::test]
473    /// fn my_test(tc: hegel::TestCase) {
474    ///     let x: i32 = tc.draw(gs::integers());
475    ///     let s: String = tc.draw(gs::text());
476    /// }
477    /// ```
478    ///
479    /// Note: when run inside a `#[hegel::test]`, `draw()` will typically be
480    /// rewritten to `__draw_named()` with an appropriate variable name
481    /// in order to give better test output.
482    ///
483    /// Requires a [`PrintableGenerator`] so the drawn value's representation
484    /// can be reported with a failing test case; to draw from a plain
485    /// [`Generator`], use [`draw_silent`](Self::draw_silent), or make the
486    /// generator printable with
487    /// [`print_as_value`](crate::generators::Generator::print_as_value),
488    /// [`print_as_debug`](crate::generators::Generator::print_as_debug), or
489    /// [`print_with`](crate::generators::Generator::print_with). The
490    /// [`pretty`](crate::pretty) module docs explain the printing system and
491    /// how to make your own types printable.
492    pub fn draw<T>(&self, generator: impl PrintableGenerator<T>) -> T {
493        self.__draw_named(generator, "draw", true)
494    }
495
496    /// Draw a value from a generator, reporting it under `name`.
497    ///
498    /// Like [`draw`](Self::draw), but the failing-example line prints as
499    /// `let name_N = value;`, numbered per call under that name. Use it in
500    /// helper functions: `#[hegel::test]` captures binding names only for
501    /// `draw` calls written directly in the test (or rule) body, so a draw
502    /// inside a helper otherwise reports as the anonymous `draw_N`. To name
503    /// every draw in a helper after its binding instead, mark the helper
504    /// [`#[hegel::test_helper]`](macro@crate::test_helper).
505    ///
506    /// ```no_run
507    /// use hegel::generators as gs;
508    ///
509    /// fn draw_index(tc: &hegel::TestCase, len: usize) -> usize {
510    ///     tc.draw_named("index", gs::integers::<usize>().max_value(len - 1))
511    /// }
512    /// ```
513    pub fn draw_named<T>(&self, name: &str, generator: impl PrintableGenerator<T>) -> T {
514        self.__draw_named(generator, name, true)
515    }
516
517    /// Draw a value from a generator with a specific name for output.
518    ///
519    /// When `repeatable` is true, a counter suffix is appended (e.g. `x_1`, `x_2`).
520    /// When `repeatable` is false, reusing the same name panics.
521    ///
522    /// Using the same name with different values of `repeatable` is an error.
523    ///
524    /// On the final replay of a failing test case, this prints:
525    /// - `let name = value;` (when not repeatable)
526    /// - `let name_N = value;` (when repeatable)
527    ///
528    /// Not intended for direct use. This is the target that `#[hegel::test]` rewrites `draw()`
529    /// calls to where appropriate.
530    pub fn __draw_named<T>(
531        &self,
532        generator: impl PrintableGenerator<T>,
533        name: &str,
534        repeatable: bool,
535    ) -> T {
536        let mid_draw = {
537            let local = self.local.borrow();
538            local.span_depth > 0 || local.printing_depth > 0
539        };
540        if mid_draw {
541            return generator.do_draw(self);
542        }
543        let Some(display_name) = self.allocate_display_name(name, repeatable) else {
544            return generator.do_draw(self);
545        };
546        let indent = self.local.borrow().indent;
547        let prefix = self.worker_line_prefix();
548        let _printing = PrintingDrawScope::new(self);
549        self.with_printer(|printer| {
550            let mut speculation = printer.speculate();
551            let printer = speculation.printer();
552            printer.text(&prefix);
553            printer.text(&" ".repeat(indent));
554            printer.shift_indent(indent as isize);
555            printer.text(&format!("let {display_name} = "));
556            let value = self.draw_and_print(&generator, printer);
557            printer.text(";");
558            printer.shift_indent(-(indent as isize));
559            printer.hard_break();
560            speculation.commit();
561            value
562        })
563    }
564
565    /// Draw a value from a generator without recording it in the output.
566    ///
567    /// Unlike [`draw`](Self::draw), this accepts any plain [`Generator`] —
568    /// no printability required — and will not print the value in the
569    /// failing-test summary.
570    pub fn draw_silent<T>(&self, generator: impl Generator<T>) -> T {
571        generator.do_draw(self)
572    }
573
574    /// Draw a value from a generator, printing its representation to
575    /// `printer` as it is drawn.
576    ///
577    /// This is how a compositional [`PrintableGenerator`] draws an inner
578    /// generator from its own
579    /// [`do_draw_and_print`](PrintableGenerator::do_draw_and_print) (and how
580    /// [`draw`](Self::draw) runs its argument): routing every inner draw
581    /// through this one entry point keeps the printed region of a draw a
582    /// framework concern rather than something each generator re-implements.
583    ///
584    /// A generator that merely forwards to an inner printable generator
585    /// without printing or drawing anything itself should call the inner
586    /// generator's `do_draw_and_print` directly instead, so the forwarding
587    /// layer doesn't register as a second region.
588    pub fn draw_and_print<T>(
589        &self,
590        generator: impl PrintableGenerator<T>,
591        printer: &mut PrettyPrinter,
592    ) -> T {
593        generator.do_draw_and_print(self, printer)
594    }
595
596    /// Assume a condition is true. If false, reject the current test input.
597    ///
598    /// # Example
599    ///
600    /// ```no_run
601    /// use hegel::generators as gs;
602    ///
603    /// #[hegel::test]
604    /// fn my_test(tc: hegel::TestCase) {
605    ///     let age: u32 = tc.draw(gs::integers());
606    ///     tc.assume(age >= 18);
607    /// }
608    /// ```
609    pub fn assume(&self, condition: bool) {
610        if !condition {
611            self.reject();
612        }
613    }
614
615    /// Reject the current test input unconditionally.
616    ///
617    /// Equivalent to `assume(false)`, but with a `!` return type so that code
618    /// following the call is statically known to be unreachable.
619    ///
620    /// # Example
621    ///
622    /// ```no_run
623    /// use hegel::generators as gs;
624    ///
625    /// #[hegel::test]
626    /// fn my_test(tc: hegel::TestCase) {
627    ///     let n: i32 = tc.draw(gs::integers());
628    ///     let positive: u32 = match u32::try_from(n) {
629    ///         Ok(v) => v,
630    ///         Err(_) => tc.reject(),
631    ///     };
632    ///     let _ = positive;
633    /// }
634    /// ```
635    pub fn reject(&self) -> ! {
636        raise_control(AssumeFailed);
637    }
638
639    /// Note a message which will be displayed with the reported failing test case.
640    ///
641    /// At the default verbosity, only prints during the final replay of a
642    /// failing test case. At [`Verbose`](crate::Verbosity::Verbose) or
643    /// higher, prints on every test case.
644    ///
645    /// # Example
646    ///
647    /// ```no_run
648    /// use hegel::generators as gs;
649    ///
650    /// #[hegel::test]
651    /// fn my_test(tc: hegel::TestCase) {
652    ///     let x: i32 = tc.draw(gs::integers());
653    ///     tc.note(&format!("Generated x = {}", x));
654    /// }
655    /// ```
656    pub fn note(&self, message: &str) {
657        if !self.global.emit {
658            return;
659        }
660        let (indent, mid_draw) = {
661            let local = self.local.borrow();
662            (local.indent, local.printing_depth > 0)
663        };
664        let prefix = self.worker_line_prefix();
665        if mid_draw {
666            self.pending_notes
667                .borrow_mut()
668                .push((prefix, indent, message.to_string()));
669        } else {
670            self.with_printer(|printer| emit_note_line(printer, &prefix, indent, message));
671        }
672    }
673
674    /// Record a targeting observation to help the engine find extreme inputs.
675    ///
676    /// Call this inside a test body to guide generation toward inputs that
677    /// maximise `score`. Inside a `#[hegel::test]`, `#[hegel::main]`, or
678    /// `#[hegel::standalone_function]` body, `tc.target(expr)` is rewritten
679    /// to call [`target_labelled`](Self::target_labelled) with the source
680    /// text of `expr` as the label, so different targeting expressions are
681    /// tracked separately by default. Outside that rewrite, `tc.target(score)`
682    /// uses the empty label.
683    ///
684    /// Has no effect during replays or if the test case has been aborted.
685    ///
686    /// # Example
687    ///
688    /// ```no_run
689    /// use hegel::generators as gs;
690    ///
691    /// #[hegel::test]
692    /// fn my_test(tc: hegel::TestCase) {
693    ///     let n: u32 = tc.draw(gs::integers::<u32>());
694    ///     tc.target(n as f64);
695    /// }
696    /// ```
697    pub fn target(&self, score: f64) {
698        self.target_labelled(score, "");
699    }
700
701    /// Record a targeting observation under an explicit label.
702    ///
703    /// The label distinguishes multiple simultaneous targeting goals.
704    /// Use this directly when you want a specific label string;
705    /// [`target`](Self::target) is the usual entry point and will be
706    /// rewritten to call this with the source expression as the label
707    /// inside a `#[hegel::test]` body.
708    ///
709    /// Has no effect during replays or if the test case has been aborted.
710    pub fn target_labelled(&self, score: f64, label: impl Into<String>) {
711        let label = label.into();
712        let outcome = self.with_ctc(|ctc| ctc.target(score, &label));
713        if let Err(rc) = outcome {
714            raise_for_rc(rc);
715        }
716    }
717
718    /// Record an event for the end-of-run statistics report.
719    ///
720    /// With statistics enabled
721    /// ([`Settings::show_statistics`](crate::Settings::show_statistics), or
722    /// the `HEGEL_STATISTICS` environment variable), the end of the run
723    /// reports, per label, the fraction of generation-phase test cases in
724    /// which the label was recorded at least once — a quick check that the
725    /// interesting situations in a test actually occur. With statistics
726    /// off, events cost almost nothing and report nothing.
727    ///
728    /// # Example
729    ///
730    /// ```no_run
731    /// use hegel::generators as gs;
732    ///
733    /// #[hegel::test]
734    /// fn my_test(tc: hegel::TestCase) {
735    ///     let xs: Vec<u8> = tc.draw(gs::vecs(gs::integers()));
736    ///     if xs.is_empty() {
737    ///         tc.event("empty input");
738    ///     }
739    /// }
740    /// ```
741    pub fn event(&self, label: impl AsRef<str>) {
742        self.record_event(|ctc| ctc.event(label.as_ref()));
743    }
744
745    /// Record a numeric observation under `label` for the end-of-run
746    /// statistics report.
747    ///
748    /// With statistics enabled
749    /// ([`Settings::show_statistics`](crate::Settings::show_statistics), or
750    /// the `HEGEL_STATISTICS` environment variable), the end of the run
751    /// reports, per label, a summary of the observed distribution — count,
752    /// min, median, mean, p90, max — over generation-phase test cases, so a test
753    /// can check the sizes or shapes it actually exercises. Unlike
754    /// [`target`](Self::target), a label may be observed any number of
755    /// times per test case, and the observations do not steer generation.
756    ///
757    /// `value` must be finite.
758    ///
759    /// # Example
760    ///
761    /// ```no_run
762    /// use hegel::generators as gs;
763    ///
764    /// #[hegel::test]
765    /// fn my_test(tc: hegel::TestCase) {
766    ///     let xs: Vec<u8> = tc.draw(gs::vecs(gs::integers()));
767    ///     tc.event_value("input length", xs.len() as f64);
768    /// }
769    /// ```
770    pub fn event_value(&self, label: impl AsRef<str>, value: f64) {
771        self.record_event(|ctc| ctc.event_value(label.as_ref(), value));
772    }
773
774    /// Shared body of [`event`](Self::event) and
775    /// [`event_value`](Self::event_value).
776    fn record_event(&self, record: impl FnOnce(&CTestCase) -> Result<(), hegel_c::hegel_result_t>) {
777        if let Err(rc) = self.with_ctc(record) {
778            raise_for_rc(rc);
779        }
780    }
781
782    /// Run `body` in a loop that should runs "logically infinitely" or until
783    /// error. Roughly equivalent to a `loop` but with better interaction with
784    /// the test runner: This loop will never exit until the test case completes.
785    ///
786    /// At the start of each iteration a `// Loop iteration N` note is emitted
787    /// into the failing-test replay output.
788    ///
789    /// # Example
790    ///
791    /// ```no_run
792    /// use hegel::generators as gs;
793    ///
794    /// #[hegel::test]
795    /// fn my_test(tc: hegel::TestCase) {
796    ///     let mut total: i32 = 0;
797    ///     tc.repeat(|| {
798    ///         let n: i32 = tc.draw(gs::integers().min_value(0).max_value(10));
799    ///         total += n;
800    ///         assert!(total >= 0);
801    ///     });
802    /// }
803    /// ```
804    pub fn repeat<F: FnMut()>(&self, mut body: F) -> ! {
805        use crate::generators::{booleans, integers};
806
807        let max_safe_min_size = usize::try_from(1u64 << 40).unwrap_or(usize::MAX / 2);
808        let min_size = self.draw_silent(integers::<usize>().max_value(max_safe_min_size));
809
810        let mut collection = Collection::new(self, min_size, None);
811        let mut iteration: u64 = 0;
812
813        while collection.more() {
814            iteration += 1;
815            self.note(&format!("// Repetition #{}", iteration));
816
817            let prev_indent = self.local.borrow().indent;
818            self.local.borrow_mut().indent = prev_indent + 2;
819            let result = catch_unwind(AssertUnwindSafe(&mut body));
820            self.local.borrow_mut().indent = prev_indent;
821
822            match result {
823                Ok(()) => {}
824                Err(e) if e.downcast_ref::<AssumeFailed>().is_some() => {}
825                Err(e)
826                    if e.downcast_ref::<StopTest>().is_some()
827                        || e.downcast_ref::<InvalidArgument>().is_some()
828                        || e.downcast_ref::<InternalError>().is_some() =>
829                {
830                    resume_unwind(e);
831                }
832                Err(e) => {
833                    self.draw_silent(booleans());
834                    resume_unwind(e);
835                }
836            }
837        }
838
839        raise_control(LoopDone);
840    }
841
842    pub(crate) fn child(&self, extra_indent: usize) -> Self {
843        let local = self.local.borrow();
844        TestCase {
845            global: self.global.clone(),
846            local: RefCell::new(TestCaseLocalData {
847                span_depth: 0,
848                printing_depth: 0,
849                indent: local.indent + extra_indent,
850                on_draw: local.on_draw.clone(),
851            }),
852            handle: Arc::clone(&self.handle),
853            printer: RefCell::new(None),
854            pending_notes: RefCell::new(Vec::new()),
855            draw_state: Arc::new(Mutex::new(DrawState::default())),
856        }
857    }
858
859    /// Run `f` with this instance's printer onto its own region of the
860    /// family document, fetching the handle on first use. No lock is
861    /// involved: the instance owns its region, concurrent clones each own
862    /// theirs, and the engine assembles the regions by anchor position.
863    fn with_printer<R>(&self, f: impl FnOnce(&mut PrettyPrinter) -> R) -> R {
864        let mut printer = self.printer.borrow_mut();
865        let printer = printer.get_or_insert_with(|| {
866            PrettyPrinter::from_handle(self.with_ctc(|ctc| ctc.printer(PRINTER_MAX_WIDTH)))
867        });
868        f(printer)
869    }
870
871    /// The `[worker N +X.XXXms] ` attribution for output written from a
872    /// concurrent stateful worker thread, empty elsewhere. Computed when a
873    /// line is recorded — on the worker's own thread, against the case-wide
874    /// start time — so attribution and timing survive into the document
875    /// rendered after the case completes.
876    fn worker_line_prefix(&self) -> String {
877        match crate::stateful::current_worker_index() {
878            Some(worker) => {
879                let ms = self.global.case_start.elapsed().as_secs_f64() * 1000.0;
880                format!("[worker {worker} +{ms:.3}ms] ")
881            }
882            None => String::new(),
883        }
884    }
885
886    /// Emit any notes recorded while a draw was in progress on this
887    /// instance.
888    fn flush_pending_notes(&self) {
889        let notes = std::mem::take(&mut *self.pending_notes.borrow_mut());
890        if notes.is_empty() {
891            return;
892        }
893        self.with_printer(|printer| {
894            for (prefix, indent, message) in &notes {
895                emit_note_line(printer, prefix, *indent, message);
896            }
897        });
898    }
899
900    /// Render the document of drawn values and notes accumulated so far —
901    /// this instance's region and every region forked from it — and push it,
902    /// line by line, through the output sink. Called by the run lifecycle,
903    /// on the root instance, once the test body has finished (successfully
904    /// or not). A straggling clone still writing on an unjoined thread loses
905    /// its uncommitted draw and its region dies; its later writes are
906    /// harmless no-ops.
907    pub(crate) fn emit_rendered_output(&self) {
908        if !self.global.emit {
909            return;
910        }
911        let output = self.with_printer(|printer| printer.try_value());
912        let local = self.local.borrow();
913        match output {
914            Ok(output) => {
915                for line in output.lines() {
916                    (local.on_draw)(line);
917                }
918            }
919            Err(message) => (local.on_draw)(&format!(
920                "Failed to render this test case's drawn values ({message}). This \
921                 indicates a bug in printing code the test uses: check any \
922                 hand-written PrettyPrintable impl or print_with closure for \
923                 unbalanced begin_group/end_group calls."
924            )),
925        }
926    }
927
928    /// Validate and count a draw name, returning the allocated display name
929    /// when this draw should be recorded (`None` when not emitting).
930    fn allocate_display_name(&self, name: &str, repeatable: bool) -> Option<String> {
931        let emit = self.global.emit;
932
933        self.with_draw_state(|draw_state| {
934            match draw_state.named_draw_repeatable.get(name) {
935                Some(&prev) if prev != repeatable => {
936                    hegel_internal_error!(
937                        "__draw_named: name {:?} used with inconsistent repeatable flag \
938                         (was {}, now {})",
939                        name,
940                        prev,
941                        repeatable
942                    );
943                }
944                Some(_) => {}
945                None => {
946                    draw_state
947                        .named_draw_repeatable
948                        .insert(name.to_string(), repeatable);
949                }
950            }
951
952            let current_count = match draw_state.named_draw_counts.get_mut(name) {
953                Some(count) => {
954                    *count += 1;
955                    *count
956                }
957                None => {
958                    draw_state.named_draw_counts.insert(name.to_string(), 1);
959                    1
960                }
961            };
962
963            if !repeatable && current_count > 1 {
964                hegel_internal_error!(
965                    "__draw_named: name {:?} used more than once but repeatable is false",
966                    name
967                );
968            }
969
970            if !emit {
971                return None;
972            }
973
974            let display = if repeatable {
975                let mut candidate = current_count;
976                loop {
977                    let name = format!("{}_{}", name, candidate);
978                    if draw_state.allocated_display_names.insert(name.clone()) {
979                        break name;
980                    }
981                    candidate += 1;
982                }
983            } else {
984                let name = name.to_string();
985                draw_state.allocated_display_names.insert(name.clone());
986                name
987            };
988            Some(display)
989        })
990    }
991
992    /// Run `f` with this instance's own libhegel handle.
993    ///
994    /// Each `TestCase` instance owns its handle, so there is no shared lock to
995    /// take here: libhegel serialises a single handle against concurrent use
996    /// itself (returning `HEGEL_E_CONCURRENT_USE`), and clones each carry their
997    /// own handle and lock.
998    pub(crate) fn with_ctc<R>(&self, f: impl FnOnce(&CTestCase) -> R) -> R {
999        f(&self.handle)
1000    }
1001
1002    /// The number of currently-open spans on this instance. Lets a generator
1003    /// that catches an unwind mid-draw (e.g. `recursive()` abandoning an
1004    /// oversized attempt) discard exactly the spans the unwound draw left
1005    /// open.
1006    pub(crate) fn open_span_depth(&self) -> usize {
1007        self.local.borrow().span_depth
1008    }
1009
1010    /// Reset this instance's open-span count to `depth` after the engine has
1011    /// discarded the spans above it (e.g. `hegel_recursion_retry` closing an
1012    /// abandoned attempt's spans engine-side).
1013    pub(crate) fn reset_open_spans_to(&self, depth: usize) {
1014        let mut local = self.local.borrow_mut();
1015        hegel_internal_assert!(local.span_depth >= depth);
1016        local.span_depth = depth;
1017    }
1018
1019    #[doc(hidden)]
1020    pub fn start_span(&self, label: u64) {
1021        self.local.borrow_mut().span_depth += 1;
1022        if let Err(rc) = self.with_ctc(|ctc| ctc.start_span(label)) {
1023            let mut local = self.local.borrow_mut();
1024            hegel_internal_assert!(local.span_depth > 0);
1025            local.span_depth -= 1;
1026            drop(local);
1027            raise_for_rc(rc);
1028        }
1029    }
1030
1031    #[doc(hidden)]
1032    pub fn stop_span(&self, discard: bool) {
1033        {
1034            let mut local = self.local.borrow_mut();
1035            hegel_internal_assert!(local.span_depth > 0);
1036            local.span_depth -= 1;
1037        }
1038        if let Err(rc) = self.with_ctc(|ctc| ctc.stop_span(discard)) {
1039            raise_for_rc(rc);
1040        }
1041    }
1042}
1043
1044impl TestCase {
1045    /// Run a draw against this instance's libhegel handle, raising the
1046    /// appropriate control-flow payload on failure.
1047    fn draw_or_raise<T>(
1048        &self,
1049        f: impl FnOnce(&CTestCase) -> Result<T, hegel_c::hegel_result_t>,
1050    ) -> T {
1051        self.with_ctc(f).unwrap_or_else(|rc| raise_for_rc(rc))
1052    }
1053
1054    /// Draw an integer in `[min_value, max_value]` (both within `i64`).
1055    pub(crate) fn generate_integer_i64(&self, min_value: i64, max_value: i64) -> i64 {
1056        self.draw_or_raise(|ctc| ctc.generate_integer(min_value, max_value))
1057    }
1058
1059    /// Draw an integer with bounds given as two's-complement little-endian
1060    /// byte encodings, returning the value's encoding sign-extended to 17
1061    /// bytes.
1062    pub(crate) fn generate_integer_le17(&self, min_value: &[u8], max_value: &[u8]) -> [u8; 17] {
1063        self.draw_or_raise(|ctc| ctc.generate_integer_big(min_value, max_value))
1064    }
1065
1066    /// Draw a float according to the full libhegel spec.
1067    pub(crate) fn generate_float(
1068        &self,
1069        width: u32,
1070        min_value: f64,
1071        max_value: f64,
1072        allow_nan: bool,
1073        allow_infinity: bool,
1074        exclude_min: bool,
1075        exclude_max: bool,
1076        smallest_nonzero_magnitude: f64,
1077    ) -> f64 {
1078        self.draw_or_raise(|ctc| {
1079            ctc.generate_float(
1080                width,
1081                min_value,
1082                max_value,
1083                allow_nan,
1084                allow_infinity,
1085                exclude_min,
1086                exclude_max,
1087                smallest_nonzero_magnitude,
1088            )
1089        })
1090    }
1091
1092    /// Draw a boolean that is `true` with probability `p`.
1093    pub(crate) fn generate_boolean(&self, p: f64) -> bool {
1094        self.draw_or_raise(|ctc| ctc.generate_boolean(p))
1095    }
1096
1097    /// Draw a byte string with length in `[min_size, max_size]`.
1098    pub(crate) fn generate_bytes(&self, min_size: usize, max_size: usize) -> Vec<u8> {
1099        self.draw_or_raise(|ctc| ctc.generate_bytes(min_size as u64, max_size as u64))
1100    }
1101
1102    /// Draw a string described by a prebuilt libhegel string generator.
1103    pub(crate) fn generate_string(&self, generator: &crate::ffi::StringGenerator) -> String {
1104        self.draw_or_raise(|ctc| ctc.generate_string(generator))
1105    }
1106
1107    /// Draw a Gregorian calendar date in `[min, max]`.
1108    pub(crate) fn generate_date(
1109        &self,
1110        min: hegel_c::hegel_date_t,
1111        max: hegel_c::hegel_date_t,
1112    ) -> hegel_c::hegel_date_t {
1113        self.draw_or_raise(|ctc| ctc.generate_date(min, max))
1114    }
1115
1116    /// Draw a time of day in `[min, max]`.
1117    pub(crate) fn generate_time(
1118        &self,
1119        min: hegel_c::hegel_time_t,
1120        max: hegel_c::hegel_time_t,
1121    ) -> hegel_c::hegel_time_t {
1122        self.draw_or_raise(|ctc| ctc.generate_time(min, max))
1123    }
1124
1125    /// Draw a naive datetime in `[min, max]`.
1126    pub(crate) fn generate_datetime(
1127        &self,
1128        min: hegel_c::hegel_datetime_t,
1129        max: hegel_c::hegel_datetime_t,
1130    ) -> hegel_c::hegel_datetime_t {
1131        self.draw_or_raise(|ctc| ctc.generate_datetime(min, max))
1132    }
1133
1134    /// Draw a UUID's 16 big-endian bytes, optionally forcing the version.
1135    pub(crate) fn generate_uuid(&self, version: Option<u8>) -> [u8; 16] {
1136        self.draw_or_raise(|ctc| ctc.generate_uuid(version))
1137    }
1138
1139    /// Draw an IPv4 address.
1140    pub(crate) fn generate_ipv4(&self) -> std::net::Ipv4Addr {
1141        self.draw_or_raise(|ctc| ctc.generate_ipv4())
1142    }
1143
1144    /// Draw an IPv6 address.
1145    pub(crate) fn generate_ipv6(&self) -> std::net::Ipv6Addr {
1146        self.draw_or_raise(|ctc| ctc.generate_ipv6())
1147    }
1148}
1149
1150/// Uses the backend to determine collection sizing.
1151///
1152/// The backend-side collection object is created lazily on the first call to
1153/// [`more()`](Collection::more).
1154pub struct Collection<'a> {
1155    tc: &'a TestCase,
1156    min_size: usize,
1157    max_size: Option<usize>,
1158    handle: Option<crate::ffi::CollectionHandle>,
1159    finished: bool,
1160}
1161
1162impl<'a> Collection<'a> {
1163    /// Create a new backend-managed collection.
1164    pub fn new(tc: &'a TestCase, min_size: usize, max_size: Option<usize>) -> Self {
1165        Collection {
1166            tc,
1167            min_size,
1168            max_size,
1169            handle: None,
1170            finished: false,
1171        }
1172    }
1173
1174    fn ensure_initialized(&mut self) {
1175        if self.handle.is_none() {
1176            let result = self.tc.with_ctc(|ctc| {
1177                ctc.new_collection(self.min_size as u64, self.max_size.map(|m| m as u64))
1178            });
1179            let handle = match result {
1180                Ok(handle) => handle,
1181                Err(rc) => raise_for_rc(rc), // nocov
1182            };
1183            self.handle = Some(handle);
1184        }
1185    }
1186
1187    /// Ask the backend whether to produce another element.
1188    pub fn more(&mut self) -> bool {
1189        if self.finished {
1190            return false;
1191        }
1192        self.ensure_initialized();
1193        let handle = self.handle.as_ref().unwrap();
1194        let result = match self.tc.with_ctc(|ctc| ctc.collection_more(handle)) {
1195            Ok(b) => b,
1196            Err(rc) => {
1197                self.finished = true;
1198                raise_for_rc(rc);
1199            }
1200        };
1201        if !result {
1202            self.finished = true;
1203        }
1204        result
1205    }
1206
1207    /// Reject the last element (don't count it towards the size budget).
1208    pub fn reject(&mut self, why: Option<&str>) {
1209        if self.finished {
1210            return;
1211        }
1212        self.ensure_initialized();
1213        let handle = self.handle.as_ref().unwrap();
1214        let _ = self.tc.with_ctc(|ctc| ctc.collection_reject(handle, why));
1215    }
1216}
1217
1218#[doc(hidden)]
1219pub mod labels {
1220    use hegel_c::hegel_label_t;
1221
1222    pub const LIST: u64 = hegel_label_t::HEGEL_LABEL_LIST as u64;
1223    pub const LIST_ELEMENT: u64 = hegel_label_t::HEGEL_LABEL_LIST_ELEMENT as u64;
1224    pub const SET: u64 = hegel_label_t::HEGEL_LABEL_SET as u64;
1225    pub const SET_ELEMENT: u64 = hegel_label_t::HEGEL_LABEL_SET_ELEMENT as u64;
1226    pub const MAP: u64 = hegel_label_t::HEGEL_LABEL_MAP as u64;
1227    pub const MAP_ENTRY: u64 = hegel_label_t::HEGEL_LABEL_MAP_ENTRY as u64;
1228    pub const TUPLE: u64 = hegel_label_t::HEGEL_LABEL_TUPLE as u64;
1229    pub const ONE_OF: u64 = hegel_label_t::HEGEL_LABEL_ONE_OF as u64;
1230    pub const OPTIONAL: u64 = hegel_label_t::HEGEL_LABEL_OPTIONAL as u64;
1231    pub const FIXED_DICT: u64 = hegel_label_t::HEGEL_LABEL_FIXED_DICT as u64;
1232    pub const FLAT_MAP: u64 = hegel_label_t::HEGEL_LABEL_FLAT_MAP as u64;
1233    pub const FILTER: u64 = hegel_label_t::HEGEL_LABEL_FILTER as u64;
1234    pub const MAPPED: u64 = hegel_label_t::HEGEL_LABEL_MAPPED as u64;
1235    pub const SAMPLED_FROM: u64 = hegel_label_t::HEGEL_LABEL_SAMPLED_FROM as u64;
1236    pub const ENUM_VARIANT: u64 = hegel_label_t::HEGEL_LABEL_ENUM_VARIANT as u64;
1237    pub const FEATURE_FLAG: u64 = hegel_label_t::HEGEL_LABEL_FEATURE_FLAG as u64;
1238    pub const STATEFUL_RULE: u64 = hegel_label_t::HEGEL_LABEL_STATEFUL_RULE as u64;
1239    pub const RECURSIVE: u64 = hegel_label_t::HEGEL_LABEL_RECURSIVE as u64;
1240}
1241
1242#[cfg(test)]
1243#[path = "../tests/embedded/test_case_tests.rs"]
1244mod tests;
1245
1246/// The conventional full ranges for the structured draws: years 1..=9999
1247/// (what Hypothesis's `dates()` spans) and the whole nanosecond-resolution
1248/// day.
1249pub(crate) mod full_ranges {
1250    pub(crate) const MIN_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
1251        year: 1,
1252        month: 1,
1253        day: 1,
1254    };
1255    pub(crate) const MAX_DATE: hegel_c::hegel_date_t = hegel_c::hegel_date_t {
1256        year: 9999,
1257        month: 12,
1258        day: 31,
1259    };
1260    pub(crate) const MIDNIGHT: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
1261        hour: 0,
1262        minute: 0,
1263        second: 0,
1264        nanosecond: 0,
1265    };
1266    pub(crate) const LAST_NANOSECOND: hegel_c::hegel_time_t = hegel_c::hegel_time_t {
1267        hour: 23,
1268        minute: 59,
1269        second: 59,
1270        nanosecond: 999_999_999,
1271    };
1272    pub(crate) const MIN_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
1273        date: MIN_DATE,
1274        time: MIDNIGHT,
1275    };
1276    pub(crate) const MAX_DATETIME: hegel_c::hegel_datetime_t = hegel_c::hegel_datetime_t {
1277        date: MAX_DATE,
1278        time: LAST_NANOSECOND,
1279    };
1280}