Skip to main content

hegel/
test_case.rs

1pub use crate::backend::{DataSource, DataSourceError};
2use crate::generators::Generator;
3use crate::runner::Mode;
4use ciborium::Value;
5use parking_lot::Mutex;
6use std::any::Any;
7use std::cell::RefCell;
8use std::collections::{HashMap, HashSet};
9use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
10use std::sync::Arc;
11
12use crate::generators::value;
13
14// We use the __IsTestCase trait internally to provide nice error messages for misuses of #[composite].
15// It should not be used by users.
16//
17// The idea is #[composite] calls __assert_is_test_case(<first param>), which errors with our on_unimplemented
18// message iff the first param does not have type TestCase.
19
20#[diagnostic::on_unimplemented(
21    // NOTE: worth checking if edits to this message should also be applied to the similar-but-different
22    // error message in #[composite] in hegel-macros.
23    message = "The first parameter in a #[composite] generator must have type TestCase.",
24    label = "This type does not match `TestCase`."
25)]
26pub trait __IsTestCase {}
27impl __IsTestCase for TestCase {}
28pub fn __assert_is_test_case<T: __IsTestCase>() {}
29
30pub(crate) const ASSUME_FAIL_STRING: &str = "__HEGEL_ASSUME_FAIL";
31
32/// The sentinel string used to identify overflow/StopTest panics.
33/// Distinct from ASSUME_FAIL_STRING so callers can tell user-initiated
34/// assumption failures apart from backend-initiated data exhaustion.
35pub(crate) const STOP_TEST_STRING: &str = "__HEGEL_STOP_TEST";
36
37/// The sentinel string used by `TestCase::repeat` to signal that its loop
38/// completed naturally (the collection said "stop" and no panic occurred
39/// inside the body). Because `repeat` returns `!`, it has no normal-return
40/// path; this panic is how it tells the runner "this test case finished
41/// successfully, record it as Valid".
42pub(crate) const LOOP_DONE_STRING: &str = "__HEGEL_LOOP_DONE";
43
44/// Panic with the appropriate sentinel for the given data source error.
45fn panic_on_data_source_error(e: DataSourceError) -> ! {
46    match e {
47        DataSourceError::StopTest => panic!("{}", STOP_TEST_STRING),
48        DataSourceError::Assume => panic!("{}", ASSUME_FAIL_STRING), // nocov
49        DataSourceError::ServerError(msg) => panic!("{}", msg),
50        // A semantically-invalid schema. In the main library this only fires
51        // if a generator builds a malformed schema (a bug), so we surface the
52        // diagnostic as a panic. libhegel never reaches here — it maps the
53        // error to `HEGEL_E_INVALID_ARG` instead.
54        DataSourceError::InvalidArgument(msg) => panic!("{}", msg),
55    }
56}
57
58pub(crate) struct TestCaseGlobalData {
59    mode: Mode,
60    /// Whether drawn-value records and notes are surfaced for this test case
61    /// (true on the final replay of a failure, or when verbose output is on).
62    /// When false `on_draw` is a no-op, so the draw-recording bookkeeping in
63    /// [`TestCase::record_named_draw`] (display-name allocation + `Debug`
64    /// rendering of the value) can be skipped entirely.
65    emit: bool,
66    /// Fine-grained lock over the state shared between clones of a
67    /// `TestCase`. Acquired briefly around each individual backend call
68    /// and around each mutation of the draw-tracking bookkeeping, not
69    /// around entire user-visible operations like a `draw`. The mutex is
70    /// non-reentrant; no method holds it while calling back into
71    /// `TestCase`.
72    shared: Mutex<SharedState>,
73}
74
75pub(crate) struct SharedState {
76    data_source: Box<dyn DataSource + Send + Sync>,
77    draw_state: DrawState,
78}
79
80pub(crate) struct DrawState {
81    named_draw_counts: HashMap<String, usize>,
82    named_draw_repeatable: HashMap<String, bool>,
83    allocated_display_names: HashSet<String>,
84}
85
86#[derive(Clone)]
87pub(crate) struct TestCaseLocalData {
88    span_depth: usize,
89    indent: usize,
90    on_draw: OutputSink,
91}
92
93/// A handle to the current test case.
94///
95/// This is passed to `#[hegel::test]` functions and provides methods
96/// for drawing values, making assumptions, and recording notes.
97///
98/// # Example
99///
100/// ```no_run
101/// use hegel::generators as gs;
102///
103/// #[hegel::test]
104/// fn my_test(tc: hegel::TestCase) {
105///     let x: i32 = tc.draw(gs::integers());
106///     tc.assume(x > 0);
107///     tc.note(&format!("x = {}", x));
108/// }
109/// ```
110///
111/// # Threading
112///
113/// `TestCase` is `Send` but not `Sync`. To drive generation from another
114/// thread, clone the test case and move the clone. Clones share the same
115/// underlying backend connection — they are views onto one test case, not
116/// independent test cases.
117///
118/// ```no_run
119/// use hegel::generators as gs;
120///
121/// #[hegel::test]
122/// fn my_test(tc: hegel::TestCase) {
123///     let tc_worker = tc.clone();
124///     let handle = std::thread::spawn(move || {
125///         tc_worker.draw(gs::integers::<i32>())
126///     });
127///     let n = handle.join().unwrap();
128///     let _b: bool = tc.draw(gs::booleans());
129///     let _ = n;
130/// }
131/// ```
132///
133/// ## What is guaranteed
134///
135/// Individual backend operations (a single `generate`, `start_span`,
136/// `stop_span`, or pool/collection call) are serialised by a shared
137/// mutex, so the bytes on the wire to the backend stay well-formed no
138/// matter how clones are used across threads.
139///
140/// This is enough for patterns where threads do not race on generation —
141/// for example:
142///
143/// - Spawn a worker, let it draw, `join` it, then continue on the main
144///   thread.
145/// - Repeatedly spawn-and-join one worker at a time.
146/// - Any pattern where exactly one thread is drawing at a time, with a
147///   happens-before relationship (join, channel receive, barrier) between
148///   each thread's work.
149///
150/// ## What is not guaranteed
151///
152/// Concurrent generation will get progressively better over time, but
153/// right now should be considered a borderline-internal feature. If
154/// you do not know exactly what you're doing it probably won't work.
155///
156/// Two or more threads drawing concurrently from clones of the same
157/// `TestCase` is allowed by the type system but is **not deterministic**:
158/// the order in which draws interleave depends on thread scheduling, and
159/// the backend has no way to reproduce that order on replay. Composite
160/// draws are also not atomic with respect to other threads — another
161/// thread's draws can land between this thread's `start_span` and
162/// `stop_span`, corrupting the shrink-friendly span structure. In
163/// practice this means such tests may:
164///
165/// - Produce different values on successive runs of the same seed.
166/// - Shrink poorly or not at all.
167/// - Surface backend errors (e.g. `StopTest`) in one thread caused by
168///   another thread's draws exhausting the budget.
169///
170/// ## Panics inside spawned threads
171///
172/// If a worker thread panics with an assumption failure or a backend
173/// `StopTest`, that panic stays inside the thread's `JoinHandle` until
174/// the main thread joins it. The main thread is responsible for
175/// propagating (or suppressing) the panic — typically by calling
176/// `handle.join().unwrap()`, which resumes the panic on the main thread
177/// so Hegel's runner can observe it.
178pub struct TestCase {
179    global: Arc<TestCaseGlobalData>,
180    // RefCell makes `TestCase: !Sync`. Local data is per-clone: each clone gets
181    // its own span depth, indent, and on_draw. Concurrent use across threads
182    // therefore requires cloning, which is enforced by the `!Sync` bound.
183    local: RefCell<TestCaseLocalData>,
184}
185
186impl Clone for TestCase {
187    fn clone(&self) -> Self {
188        TestCase {
189            global: self.global.clone(),
190            local: RefCell::new(self.local.borrow().clone()),
191        }
192    }
193}
194
195impl std::fmt::Debug for TestCase {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        f.debug_struct("TestCase").finish_non_exhaustive()
198    }
199}
200
201/// A callback invoked for each line of draw/note output during the final replay.
202pub(crate) type OutputSink = Arc<dyn Fn(&str) + Send + Sync>;
203
204thread_local! {
205    static OUTPUT_OVERRIDE: RefCell<Option<OutputSink>> = const { RefCell::new(None) };
206}
207
208/// Install a custom output sink for the duration of `f`, replacing the usual
209/// `eprintln!` behavior of draw and note output. Intended for tests that want
210/// to capture what a test case would print.
211///
212/// While active, notes and draws from the final replay go to `sink` instead of
213/// stderr. Non-final test cases still drop their draw/note output as usual.
214#[doc(hidden)]
215pub fn with_output_override<R>(sink: OutputSink, f: impl FnOnce() -> R) -> R {
216    let prev = OUTPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(sink));
217    let result = f();
218    OUTPUT_OVERRIDE.with(|cell| *cell.borrow_mut() = prev);
219    result
220}
221
222/// Return a clone of the currently-installed output sink, if any. Lets the
223/// run lifecycle's verbose output (stop-reason lines, per-test-case panic
224/// diagnostics) flow through `with_output_override` so tests can capture
225/// them in-process without having to spawn a subprocess.
226pub(crate) fn current_output_sink() -> Option<OutputSink> {
227    OUTPUT_OVERRIDE.with(|cell| cell.borrow().clone())
228}
229
230/// Emit a single line of verbose runner output, going through the
231/// installed output sink if there is one and otherwise to stderr.
232pub(crate) fn emit_verbose_line(msg: &str) {
233    if let Some(sink) = current_output_sink() {
234        sink(msg);
235    } else {
236        eprintln!("{}", msg);
237    }
238}
239
240fn panic_message(payload: &Box<dyn Any + Send>) -> String {
241    if let Some(s) = payload.downcast_ref::<&str>() {
242        s.to_string()
243    } else if let Some(s) = payload.downcast_ref::<String>() {
244        s.clone()
245    } else {
246        "Unknown panic".to_string() // nocov
247    }
248}
249
250impl TestCase {
251    pub(crate) fn new(
252        data_source: Box<dyn DataSource + Send + Sync>,
253        is_last_run: bool,
254        mode: Mode,
255        verbose: bool,
256    ) -> Self {
257        let override_sink = current_output_sink();
258        let should_emit = is_last_run || verbose;
259        let on_draw: OutputSink = match override_sink {
260            Some(sink) if should_emit => sink,
261            _ if should_emit => Arc::new(|msg| eprintln!("{}", msg)),
262            _ => Arc::new(|_| {}),
263        };
264        TestCase {
265            global: Arc::new(TestCaseGlobalData {
266                mode,
267                emit: should_emit,
268                shared: Mutex::new(SharedState {
269                    data_source,
270                    draw_state: DrawState {
271                        named_draw_counts: HashMap::new(),
272                        named_draw_repeatable: HashMap::new(),
273                        allocated_display_names: HashSet::new(),
274                    },
275                }),
276            }),
277            local: RefCell::new(TestCaseLocalData {
278                span_depth: 0,
279                indent: 0,
280                on_draw,
281            }),
282        }
283    }
284
285    pub(crate) fn mode(&self) -> Mode {
286        self.global.mode
287    }
288
289    /// Acquire the shared mutex for the duration of `f`.
290    ///
291    /// Held briefly around individual backend calls or draw-state updates,
292    /// never around whole user-visible operations. The mutex is
293    /// non-reentrant, so `f` must not call any other method that also
294    /// acquires the shared mutex.
295    fn with_shared<R>(&self, f: impl FnOnce(&mut SharedState) -> R) -> R {
296        let mut guard = self.global.shared.lock();
297        f(&mut guard)
298    }
299
300    /// Draw a value from a generator.
301    ///
302    /// # Example
303    ///
304    /// ```no_run
305    /// use hegel::generators as gs;
306    ///
307    /// #[hegel::test]
308    /// fn my_test(tc: hegel::TestCase) {
309    ///     let x: i32 = tc.draw(gs::integers());
310    ///     let s: String = tc.draw(gs::text());
311    /// }
312    /// ```
313    ///
314    /// Note: when run inside a `#[hegel::test]`, `draw()` will typically be
315    /// rewritten to `__draw_named()` with an appropriate variable name
316    /// in order to give better test output.
317    pub fn draw<T: std::fmt::Debug>(&self, generator: impl Generator<T>) -> T {
318        self.__draw_named(generator, "draw", true)
319    }
320
321    /// Draw a value from a generator with a specific name for output.
322    ///
323    /// When `repeatable` is true, a counter suffix is appended (e.g. `x_1`, `x_2`).
324    /// When `repeatable` is false, reusing the same name panics.
325    ///
326    /// Using the same name with different values of `repeatable` is an error.
327    ///
328    /// On the final replay of a failing test case, this prints:
329    /// - `let name = value;` (when not repeatable)
330    /// - `let name_N = value;` (when repeatable)
331    ///
332    /// Not intended for direct use. This is the target that `#[hegel::test]` rewrites `draw()`
333    /// calls to where appropriate.
334    pub fn __draw_named<T: std::fmt::Debug>(
335        &self,
336        generator: impl Generator<T>,
337        name: &str,
338        repeatable: bool,
339    ) -> T {
340        let value = generator.do_draw(self);
341        if self.local.borrow().span_depth == 0 {
342            self.record_named_draw(&value, name, repeatable);
343        }
344        value
345    }
346
347    /// Draw a value from a generator without recording it in the output.
348    ///
349    /// Unlike [`draw`](Self::draw), this does not require `T: Debug` and
350    /// will not print the value in the failing-test summary.
351    pub fn draw_silent<T>(&self, generator: impl Generator<T>) -> T {
352        generator.do_draw(self)
353    }
354
355    /// Assume a condition is true. If false, reject the current test input.
356    ///
357    /// # Example
358    ///
359    /// ```no_run
360    /// use hegel::generators as gs;
361    ///
362    /// #[hegel::test]
363    /// fn my_test(tc: hegel::TestCase) {
364    ///     let age: u32 = tc.draw(gs::integers());
365    ///     tc.assume(age >= 18);
366    /// }
367    /// ```
368    pub fn assume(&self, condition: bool) {
369        if !condition {
370            self.reject();
371        }
372    }
373
374    /// Reject the current test input unconditionally.
375    ///
376    /// Equivalent to `assume(false)`, but with a `!` return type so that code
377    /// following the call is statically known to be unreachable.
378    ///
379    /// # Example
380    ///
381    /// ```no_run
382    /// use hegel::generators as gs;
383    ///
384    /// #[hegel::test]
385    /// fn my_test(tc: hegel::TestCase) {
386    ///     let n: i32 = tc.draw(gs::integers());
387    ///     let positive: u32 = match u32::try_from(n) {
388    ///         Ok(v) => v,
389    ///         Err(_) => tc.reject(),
390    ///     };
391    ///     let _ = positive;
392    /// }
393    /// ```
394    pub fn reject(&self) -> ! {
395        panic!("{}", ASSUME_FAIL_STRING);
396    }
397
398    /// Note a message which will be displayed with the reported failing test case.
399    ///
400    /// At the default verbosity, only prints during the final replay of a
401    /// failing test case. At [`Verbose`](crate::Verbosity::Verbose) or
402    /// higher, prints on every test case.
403    ///
404    /// # Example
405    ///
406    /// ```no_run
407    /// use hegel::generators as gs;
408    ///
409    /// #[hegel::test]
410    /// fn my_test(tc: hegel::TestCase) {
411    ///     let x: i32 = tc.draw(gs::integers());
412    ///     tc.note(&format!("Generated x = {}", x));
413    /// }
414    /// ```
415    pub fn note(&self, message: &str) {
416        let local = self.local.borrow();
417        let indent = local.indent;
418        (local.on_draw)(&format!("{:indent$}{}", "", message, indent = indent));
419    }
420
421    /// Record a targeting observation to help the engine find extreme inputs.
422    ///
423    /// Call this inside a test body to guide generation toward inputs that
424    /// maximise `score`. Inside a `#[hegel::test]`, `#[hegel::main]`, or
425    /// `#[hegel::standalone_function]` body, `tc.target(expr)` is rewritten
426    /// to call [`target_labelled`](Self::target_labelled) with the source
427    /// text of `expr` as the label, so different targeting expressions are
428    /// tracked separately by default. Outside that rewrite, `tc.target(score)`
429    /// uses the empty label.
430    ///
431    /// Has no effect during replays or if the test case has been aborted.
432    ///
433    /// # Example
434    ///
435    /// ```no_run
436    /// use hegel::generators as gs;
437    ///
438    /// #[hegel::test]
439    /// fn my_test(tc: hegel::TestCase) {
440    ///     let n: u32 = tc.draw(gs::integers::<u32>());
441    ///     tc.target(n as f64);
442    /// }
443    /// ```
444    pub fn target(&self, score: f64) {
445        self.target_labelled(score, "");
446    }
447
448    /// Record a targeting observation under an explicit label.
449    ///
450    /// The label distinguishes multiple simultaneous targeting goals.
451    /// Use this directly when you want a specific label string;
452    /// [`target`](Self::target) is the usual entry point and will be
453    /// rewritten to call this with the source expression as the label
454    /// inside a `#[hegel::test]` body.
455    ///
456    /// Has no effect during replays or if the test case has been aborted.
457    pub fn target_labelled(&self, score: f64, label: impl Into<String>) {
458        let label = label.into();
459        self.with_data_source(|ds| ds.target_observation(score, &label));
460    }
461
462    /// Run `body` in a loop that should runs "logically infinitely" or until
463    /// error. Roughly equivalent to a `loop` but with better interaction with
464    /// the test runner: This loop will never exit until the test case completes.
465    ///
466    /// At the start of each iteration a `// Loop iteration N` note is emitted
467    /// into the failing-test replay output.
468    ///
469    /// # Example
470    ///
471    /// ```no_run
472    /// use hegel::generators as gs;
473    ///
474    /// #[hegel::test]
475    /// fn my_test(tc: hegel::TestCase) {
476    ///     let mut total: i32 = 0;
477    ///     tc.repeat(|| {
478    ///         let n: i32 = tc.draw(gs::integers().min_value(0).max_value(10));
479    ///         total += n;
480    ///         assert!(total >= 0);
481    ///     });
482    /// }
483    /// ```
484    pub fn repeat<F: FnMut()>(&self, mut body: F) -> ! {
485        if self.global.mode == Mode::SingleTestCase {
486            self.repeat_single_test_case(&mut body);
487        }
488        self.repeat_property_test(&mut body);
489    }
490
491    fn repeat_single_test_case(&self, body: &mut dyn FnMut()) -> ! {
492        let mut iteration: u64 = 0;
493        loop {
494            iteration += 1;
495            self.note(&format!("// Repetition #{}", iteration));
496
497            let prev_indent = self.local.borrow().indent;
498            self.local.borrow_mut().indent = prev_indent + 2;
499            body();
500            self.local.borrow_mut().indent = prev_indent;
501        }
502    }
503
504    fn repeat_property_test(&self, body: &mut dyn FnMut()) -> ! {
505        use crate::generators::{booleans, integers};
506
507        const MAX_SAFE_MIN_SIZE: usize = 1 << 40;
508        let min_size = self.draw_silent(integers::<usize>().max_value(MAX_SAFE_MIN_SIZE));
509
510        let mut collection = Collection::new(self, min_size, None);
511        let mut iteration: u64 = 0;
512
513        while collection.more() {
514            iteration += 1;
515            self.note(&format!("// Repetition #{}", iteration));
516
517            let prev_indent = self.local.borrow().indent;
518            self.local.borrow_mut().indent = prev_indent + 2;
519            let result = catch_unwind(AssertUnwindSafe(&mut *body));
520            self.local.borrow_mut().indent = prev_indent;
521
522            match result {
523                Ok(()) => {}
524                Err(e) => {
525                    let msg = panic_message(&e);
526                    if msg == ASSUME_FAIL_STRING {
527                    } else if msg == STOP_TEST_STRING {
528                        resume_unwind(e);
529                    } else {
530                        self.draw_silent(booleans());
531                        resume_unwind(e);
532                    }
533                }
534            }
535        }
536
537        panic!("{}", LOOP_DONE_STRING);
538    }
539
540    pub(crate) fn child(&self, extra_indent: usize) -> Self {
541        let local = self.local.borrow();
542        TestCase {
543            global: self.global.clone(),
544            local: RefCell::new(TestCaseLocalData {
545                span_depth: 0,
546                indent: local.indent + extra_indent,
547                on_draw: local.on_draw.clone(),
548            }),
549        }
550    }
551
552    fn record_named_draw<T: std::fmt::Debug>(&self, value: &T, name: &str, repeatable: bool) {
553        // The drawn-value record is only ever surfaced through `on_draw`, which
554        // is a no-op unless this is the final replay or verbose output is on.
555        // On ordinary generation/shrinking runs we therefore skip the
556        // display-name allocation and the (often expensive, e.g. Unicode) Debug
557        // render entirely. The usage-error checks below still run every test
558        // case so that misuse fails deterministically, not only on a failure's
559        // final replay.
560        let emit = self.global.emit;
561
562        let display_name = self.with_shared(|shared| {
563            let draw_state = &mut shared.draw_state;
564
565            match draw_state.named_draw_repeatable.get(name) {
566                Some(&prev) if prev != repeatable => {
567                    panic!(
568                        "__draw_named: name {:?} used with inconsistent repeatable flag (was {}, now {}). \
569                        If you have not called __draw_named deliberately yourself, this is likely a bug in \
570                        hegel. Please file a bug report at https://github.com/hegeldev/hegel-rust/issues",
571                        name, prev, repeatable
572                    );
573                }
574                Some(_) => {}
575                // Only the first occurrence of a name needs to allocate the key.
576                None => {
577                    draw_state
578                        .named_draw_repeatable
579                        .insert(name.to_string(), repeatable);
580                }
581            }
582
583            // Look the counter up by `&str` first so repeated draws of the same
584            // name (e.g. a `draw` inside a loop) don't allocate a fresh key on
585            // every call.
586            let current_count = match draw_state.named_draw_counts.get_mut(name) {
587                Some(count) => {
588                    *count += 1;
589                    *count
590                }
591                None => {
592                    draw_state.named_draw_counts.insert(name.to_string(), 1);
593                    1
594                }
595            };
596
597            if !repeatable && current_count > 1 {
598                panic!(
599                    "__draw_named: name {:?} used more than once but repeatable is false. \
600                    This is almost certainly a bug in hegel - please report it at https://github.com/hegeldev/hegel-rust/issues",
601                    name
602                );
603            }
604
605            // Display-name uniqueness bookkeeping is output-only; skip it (and
606            // its allocations) when nothing will be emitted.
607            if !emit {
608                return None;
609            }
610
611            let display = if repeatable {
612                let mut candidate = current_count;
613                loop {
614                    let name = format!("{}_{}", name, candidate);
615                    if draw_state.allocated_display_names.insert(name.clone()) {
616                        break name;
617                    }
618                    candidate += 1;
619                }
620            } else {
621                let name = name.to_string();
622                draw_state.allocated_display_names.insert(name.clone());
623                name
624            };
625            Some(display)
626        });
627
628        let Some(display_name) = display_name else {
629            return;
630        };
631
632        let local = self.local.borrow();
633        let indent = local.indent;
634
635        (local.on_draw)(&format!(
636            "{:indent$}let {} = {:?};",
637            "",
638            display_name,
639            value,
640            indent = indent
641        ));
642    }
643
644    /// Run `f` with access to this test case's data source.
645    ///
646    /// Acquires the shared mutex for the duration of the call so
647    /// concurrent threads don't scramble backend traffic. The closure
648    /// must not call back into any other `TestCase` method that would
649    /// re-acquire the shared mutex.
650    pub(crate) fn with_data_source<R>(&self, f: impl FnOnce(&dyn DataSource) -> R) -> R {
651        self.with_shared(|shared| f(shared.data_source.as_ref()))
652    }
653
654    /// Send `mark_complete` on this test case's data source.
655    ///
656    /// Both backends use this to communicate the outcome — the full
657    /// [`TestCaseResult`], including any captured [`Failure`] — to whatever
658    /// owns the per-test-case bookkeeping (Hypothesis on the server backend;
659    /// the native engine, via a per-test-case outcome handle, on the native
660    /// backend).
661    pub(crate) fn mark_complete(&self, result: &crate::backend::TestCaseResult) {
662        self.with_data_source(|ds| ds.mark_complete(result));
663    }
664
665    #[doc(hidden)]
666    pub fn start_span(&self, label: u64) {
667        self.local.borrow_mut().span_depth += 1;
668        if let Err(e) = self.with_data_source(|ds| ds.start_span(label)) {
669            // nocov start
670            let mut local = self.local.borrow_mut();
671            assert!(local.span_depth > 0);
672            local.span_depth -= 1;
673            drop(local);
674            panic_on_data_source_error(e);
675            // nocov end
676        }
677    }
678
679    #[doc(hidden)]
680    pub fn stop_span(&self, discard: bool) {
681        {
682            let mut local = self.local.borrow_mut();
683            assert!(local.span_depth > 0);
684            local.span_depth -= 1;
685        }
686        let _ = self.with_data_source(|ds| ds.stop_span(discard));
687    }
688}
689
690/// Send a schema to the backend and return the raw CBOR response.
691#[doc(hidden)]
692pub fn generate_raw(tc: &TestCase, schema: &Value) -> Value {
693    match tc.with_data_source(|ds| ds.generate(schema)) {
694        Ok(v) => v,
695        Err(e) => panic_on_data_source_error(e),
696    }
697}
698
699#[doc(hidden)]
700pub fn generate_from_schema<T: serde::de::DeserializeOwned>(tc: &TestCase, schema: &Value) -> T {
701    deserialize_value(generate_raw(tc, schema))
702}
703
704/// Deserialize a raw CBOR value into a Rust type.
705///
706/// This is a public helper for use by derived generators (proc macros)
707/// that need to deserialize individual field values from CBOR.
708pub fn deserialize_value<T: serde::de::DeserializeOwned>(raw: Value) -> T {
709    let hv = value::HegelValue::from(raw.clone());
710    value::from_hegel_value(hv).unwrap_or_else(|e| {
711        panic!("Failed to deserialize value: {}\nValue: {:?}", e, raw); // nocov
712    })
713}
714
715/// Uses the backend to determine collection sizing.
716///
717/// The backend-side collection object is created lazily on the first call to
718/// [`more()`](Collection::more).
719pub struct Collection<'a> {
720    tc: &'a TestCase,
721    min_size: usize,
722    max_size: Option<usize>,
723    handle: Option<i64>,
724    finished: bool,
725}
726
727impl<'a> Collection<'a> {
728    /// Create a new backend-managed collection.
729    pub fn new(tc: &'a TestCase, min_size: usize, max_size: Option<usize>) -> Self {
730        Collection {
731            tc,
732            min_size,
733            max_size,
734            handle: None,
735            finished: false,
736        }
737    }
738
739    fn ensure_initialized(&mut self) -> i64 {
740        if self.handle.is_none() {
741            let result = self.tc.with_data_source(|ds| {
742                ds.new_collection(self.min_size as u64, self.max_size.map(|m| m as u64))
743            });
744            let id = match result {
745                Ok(id) => id,
746                Err(e) => panic_on_data_source_error(e), // nocov
747            };
748            self.handle = Some(id);
749        }
750        self.handle.unwrap()
751    }
752
753    /// Ask the backend whether to produce another element.
754    pub fn more(&mut self) -> bool {
755        if self.finished {
756            return false; // nocov
757        }
758        let handle = self.ensure_initialized();
759        let result = match self.tc.with_data_source(|ds| ds.collection_more(handle)) {
760            Ok(b) => b,
761            Err(e) => {
762                self.finished = true;
763                panic_on_data_source_error(e);
764            }
765        };
766        if !result {
767            self.finished = true;
768        }
769        result
770    }
771
772    /// Reject the last element (don't count it towards the size budget).
773    pub fn reject(&mut self, why: Option<&str>) {
774        // nocov start
775        if self.finished {
776            return;
777        }
778        let handle = self.ensure_initialized();
779        let _ = self
780            .tc
781            .with_data_source(|ds| ds.collection_reject(handle, why));
782        // nocov end
783    }
784}
785
786#[doc(hidden)]
787pub mod labels {
788    pub const LIST: u64 = 1;
789    pub const LIST_ELEMENT: u64 = 2;
790    pub const SET: u64 = 3;
791    pub const SET_ELEMENT: u64 = 4;
792    pub const MAP: u64 = 5;
793    pub const MAP_ENTRY: u64 = 6;
794    pub const TUPLE: u64 = 7;
795    pub const ONE_OF: u64 = 8;
796    pub const OPTIONAL: u64 = 9;
797    pub const FIXED_DICT: u64 = 10;
798    pub const FLAT_MAP: u64 = 11;
799    pub const FILTER: u64 = 12;
800    pub const MAPPED: u64 = 13;
801    pub const SAMPLED_FROM: u64 = 14;
802    pub const ENUM_VARIANT: u64 = 15;
803}
804
805#[cfg(test)]
806#[path = "../tests/embedded/test_case_tests.rs"]
807mod tests;