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