hegel/lib.rs
1//! Hegel is a property-based testing library for Rust. Hegel is based on [Hypothesis](https://github.com/hypothesisworks/hypothesis), using the [Hegel](https://hegel.dev/) protocol.
2//!
3//! # Getting started
4//!
5//! This guide walks you through the basics of installing Hegel and writing your first tests.
6//!
7//! ## Install Hegel
8//!
9//! Add `hegel-rust` to your `Cargo.toml` as a dev dependency using cargo:
10//!
11//! ```bash
12//! cargo add --dev hegeltest
13//! ```
14//!
15//! ## Write your first test
16//!
17//! You're now ready to write your first test. We'll use Cargo as a test runner for the
18//! purposes of this guide. Create a new test in the project's `tests/` directory:
19//!
20//! ```no_run
21//! use hegel::TestCase;
22//! use hegel::generators as gs;
23//!
24//! #[hegel::test]
25//! fn test_integer_self_equality(tc: TestCase) {
26//! let n = tc.draw(gs::integers::<i32>());
27//! assert_eq!(n, n); // integers should always be equal to themselves
28//! }
29//! ```
30//!
31//! Now run the test using `cargo test --test <filename>`. You should see that this test passes.
32//!
33//! Let's look at what's happening in more detail. The `#[hegel::test]` attribute runs your test
34//! many times (100, by default). The test function (in this case `test_integer_self_equality`)
35//! takes a [`TestCase`] parameter, which provides a [`draw`](TestCase::draw) method for drawing
36//! different values. This test draws a random integer and checks that it should be equal to itself.
37//!
38//! Next, try a test that fails:
39//!
40//! ```no_run
41//! # use hegel::TestCase;
42//! # use hegel::generators as gs;
43//! #[hegel::test]
44//! fn test_integers_always_below_50(tc: TestCase) {
45//! let n = tc.draw(gs::integers::<i32>());
46//! assert!(n < 50); // this will fail!
47//! }
48//! ```
49//!
50//! This test asserts that any integer is less than 50, which is obviously incorrect. Hegel will
51//! find a test case that makes this assertion fail, and then shrink it to find the smallest
52//! counterexample — in this case, `n = 50`.
53//!
54//! To fix this test, you can constrain the integers you generate with the `min_value` and
55//! `max_value` functions:
56//!
57//! ```no_run
58//! # use hegel::TestCase;
59//! # use hegel::generators as gs;
60//! #[hegel::test]
61//! fn test_bounded_integers_always_below_50(tc: TestCase) {
62//! let n = tc.draw(gs::integers::<i32>()
63//! .min_value(0)
64//! .max_value(49));
65//! assert!(n < 50);
66//! }
67//! ```
68//!
69//! Run the test again. It should now pass.
70//!
71//! ## Use generators
72//!
73//! Hegel provides a rich library of generators that you can use out of the box. There are
74//! primitive generators, such as [`integers`](generators::integers),
75//! [`floats`](generators::floats), and [`text`](generators::text), and combinators that allow
76//! you to make generators out of other generators, such as [`vecs`](generators::vecs) and
77//! [`tuples`].
78//!
79//! For example, you can use [`vecs`](generators::vecs) to generate a vector of integers:
80//!
81//! ```no_run
82//! # use hegel::TestCase;
83//! use hegel::generators as gs;
84//!
85//! #[hegel::test]
86//! fn test_append_increases_length(tc: TestCase) {
87//! let mut vector = tc.draw(gs::vecs(gs::integers::<i32>()));
88//! let initial_length = vector.len();
89//! vector.push(tc.draw(gs::integers::<i32>()));
90//! assert!(vector.len() > initial_length);
91//! }
92//! ```
93//!
94//! This test checks that appending an element to a random vector of integers should always
95//! increase its length.
96//!
97//! You can also define custom generators. For example, say you have a `Person` struct that
98//! we want to generate:
99//!
100//! ```no_run
101//! # use hegel::TestCase;
102//! # use hegel::generators as gs;
103//! #[derive(Debug)]
104//! struct Person {
105//! age: i32,
106//! name: String,
107//! }
108//!
109//! #[hegel::composite]
110//! fn generate_person(tc: TestCase) -> Person {
111//! let age = tc.draw(gs::integers::<i32>());
112//! let name = tc.draw(gs::text());
113//! Person { age, name }
114//! }
115//! ```
116//!
117//! Note that you can feed the results of a `draw` to subsequent calls. For example, say that
118//! you extend the `Person` struct to include a `driving_license` boolean field:
119//!
120//! ```no_run
121//! # use hegel::TestCase;
122//! # use hegel::generators as gs;
123//! #[derive(Debug)]
124//! struct Person {
125//! age: i32,
126//! name: String,
127//! driving_license: bool,
128//! }
129//!
130//! #[hegel::composite]
131//! fn generate_person(tc: TestCase) -> Person {
132//! let age = tc.draw(gs::integers::<i32>());
133//! let name = tc.draw(gs::text());
134//! let driving_license = if age >= 18 {
135//! tc.draw(gs::booleans())
136//! } else {
137//! false
138//! };
139//! Person { age, name, driving_license }
140//! }
141//! ```
142//!
143//! ## Debug your failing test cases
144//!
145//! Use the [`note`](TestCase::note) method to attach debug information:
146//!
147//! ```no_run
148//! # use hegel::TestCase;
149//! # use hegel::generators as gs;
150//! #[hegel::test]
151//! fn test_with_notes(tc: TestCase) {
152//! let x = tc.draw(gs::integers::<i32>());
153//! let y = tc.draw(gs::integers::<i32>());
154//! tc.note(&format!("x + y = {}, y + x = {}", x + y, y + x));
155//! assert_eq!(x + y, y + x);
156//! }
157//! ```
158//!
159//! Notes only appear when Hegel replays the minimal failing example.
160//!
161//! ## Change the number of test cases
162//!
163//! By default Hegel runs 100 test cases. To override this, pass the `test_cases` argument
164//! to the `test` attribute:
165//!
166//! ```no_run
167//! # use hegel::TestCase;
168//! # use hegel::generators as gs;
169//! #[hegel::test(test_cases = 500)]
170//! fn test_integers_many(tc: TestCase) {
171//! let n = tc.draw(gs::integers::<i32>());
172//! assert_eq!(n, n);
173//! }
174//! ```
175//!
176//! ## Threading
177//!
178//! [`TestCase`] is `Send` but not `Sync`: you can clone it and move the clone
179//! to another thread to drive generation from there.
180//!
181//! ```no_run
182//! use hegel::TestCase;
183//! use hegel::generators as gs;
184//!
185//! #[hegel::test]
186//! fn test_with_worker_thread(tc: TestCase) {
187//! let tc_worker = tc.clone();
188//! let handle = std::thread::spawn(move || {
189//! tc_worker.draw(gs::vecs(gs::integers::<i32>()).max_size(10))
190//! });
191//! let xs = handle.join().unwrap();
192//! let more: bool = tc.draw(gs::booleans());
193//! let _ = (xs, more);
194//! }
195//! ```
196//!
197//! Clones share the test case's *outcome* — the whole family passes, fails,
198//! or is rejected as one test case — but each clone draws from its own
199//! independent, deterministic stream of choices, so several threads can
200//! generate concurrently without perturbing each other's values and the
201//! same seed replays the same values on every stream.
202//!
203//! Determinism extends only as far as your own code's determinism: if your
204//! threads race on shared state, Hegel replays each stream faithfully but
205//! the test may still behave differently run to run — see [`TestCase`]'s
206//! documentation for the full contract and the patterns that are safe to
207//! rely on.
208//!
209//! ## Learning more
210//!
211//! - Browse the [`generators`] module for the full list of available generators.
212//! - See [`Settings`] for more configuration settings to customise how your test runs.
213
214#![forbid(future_incompatible)]
215#![cfg_attr(docsrs, feature(doc_cfg))]
216
217pub(crate) mod antithesis;
218#[doc(hidden)]
219pub mod backend;
220pub(crate) mod cli;
221pub(crate) mod control;
222#[doc(hidden)]
223pub mod explicit_test_case;
224pub mod extras;
225pub(crate) mod ffi;
226pub mod generators;
227#[doc(hidden)]
228pub mod run_lifecycle;
229pub(crate) mod runner;
230pub mod stateful;
231mod test_case;
232#[doc(hidden)]
233pub use control::currently_in_test_context;
234pub use explicit_test_case::ExplicitTestCase;
235pub use generators::Generator;
236pub use test_case::TestCase;
237
238#[doc(hidden)]
239pub use paste;
240#[doc(hidden)]
241pub use test_case::{__IsTestCase, __assert_is_test_case, with_output_override};
242
243#[doc(hidden)]
244pub use antithesis::TestLocation;
245
246#[doc(hidden)]
247#[cfg(feature = "__bench")]
248pub use hegel_c::__bench;
249
250/// Derive a generator for a struct or enum.
251///
252/// This implements [`DefaultGenerator`](generators::DefaultGenerator) for the type,
253/// allowing it to be used with [`default`](generators::default) via `default::<T>()`.
254///
255/// For structs, the generated generator has:
256/// - `<field>(generator)` - builder method to customize each field's generator
257///
258/// For enums, the generated generator has:
259/// - `default_<VariantName>()` - methods returning default variant generators
260/// - `<VariantName>(generator)` - builder methods to customize variant generation
261///
262/// # Struct Example
263///
264/// ```ignore
265/// use hegel::DefaultGenerator;
266/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
267///
268/// #[derive(DefaultGenerator)]
269/// struct Person {
270/// name: String,
271/// age: u32,
272/// }
273///
274/// #[hegel::test]
275/// fn generates_people(tc: hegel::TestCase) {
276/// let generator = gs::default::<Person>()
277/// .age(gs::integers::<u32>().min_value(0).max_value(120));
278/// let person: Person = tc.draw(generator);
279/// }
280/// ```
281///
282/// # Enum Example
283///
284/// ```ignore
285/// use hegel::DefaultGenerator;
286/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
287///
288/// #[derive(DefaultGenerator)]
289/// enum Status {
290/// Pending,
291/// Active { since: String },
292/// Error { code: i32, message: String },
293/// }
294///
295/// #[hegel::test]
296/// fn generates_statuses(tc: hegel::TestCase) {
297/// let generator = gs::default::<Status>()
298/// .active(|g| g.since(gs::text().max_size(20)));
299/// let status: Status = tc.draw(generator);
300/// }
301/// ```
302pub use hegel_macros::DefaultGenerator;
303
304/// Define a composite generator from a function.
305///
306/// The first parameter must be a [`TestCase`] and is passed automatically
307/// when the generator is drawn. Any additional parameters become parameters
308/// of the returned factory function. The function must have an explicit
309/// return type.
310///
311/// ```ignore
312/// use hegel::generators as gs;
313///
314/// #[hegel::composite]
315/// fn sorted_vec(tc: hegel::TestCase, min_len: usize) -> Vec<i32> {
316/// let mut v: Vec<i32> = tc.draw(gs::vecs(gs::integers()).min_size(min_len));
317/// v.sort();
318/// v
319/// }
320///
321/// #[hegel::test]
322/// fn test_sorted(tc: hegel::TestCase) {
323/// let v = tc.draw(sorted_vec(3));
324/// assert!(v.len() >= 3);
325/// assert!(v.windows(2).all(|w| w[0] <= w[1]));
326/// }
327/// ```
328pub use hegel_macros::composite;
329pub use hegel_macros::explicit_test_case;
330
331/// Replay a single failing example from a base64 *failure blob*.
332///
333/// When a test fails on the native backend and the
334/// [`print_blob`](Settings::print_blob) setting is enabled, Hegel prints a
335/// reproducer line of the form:
336///
337/// ```text
338/// To reproduce this failure, add the attribute below #[hegel::test]:
339/// #[hegel::reproduce_failure("AAEC…")]
340/// ```
341///
342/// Paste that attribute **below** `#[hegel::test]` and the next run will
343/// decode the blob's choice sequence and run *only* that example.
344///
345/// ```ignore
346/// #[hegel::test]
347/// #[hegel::reproduce_failure("AAEC…")]
348/// fn my_test(tc: hegel::TestCase) {
349/// let x: i32 = tc.draw(hegel::generators::integers());
350/// assert!(x < 100);
351/// }
352/// ```
353///
354/// The argument is any expression that resolves to a base64 blob — a string
355/// literal, or a `const`/`static`/variable holding one:
356///
357/// ```ignore
358/// const REGRESSION: &str = "AAEC…";
359///
360/// #[hegel::test]
361/// #[hegel::reproduce_failure(REGRESSION)]
362/// fn my_test(tc: hegel::TestCase) { /* ... */ }
363/// ```
364///
365/// The attribute may be stacked to keep track of several failures, but only
366/// the **first** one replays — the rest are bookkeeping. Delete them one by
367/// one as the failures are fixed:
368///
369/// ```ignore
370/// #[hegel::test]
371/// #[hegel::reproduce_failure("AAEC…")] // replayed
372/// #[hegel::reproduce_failure("AAED…")] // kept for later
373/// fn my_test(tc: hegel::TestCase) { /* ... */ }
374/// ```
375///
376/// The blob encodes Hegel's internal choice sequence, so it is only
377/// guaranteed to reproduce a failure within a specific version of Hegel.
378/// A blob that can't be decoded (corrupt or from an incompatible version),
379/// or that no longer reproduces a failure, panics with an explanatory
380/// message.
381pub use hegel_macros::reproduce_failure;
382
383#[doc(hidden)]
384pub use hegel_macros::rewrite_draws;
385
386/// Derive a [`StateMachine`](crate::stateful::StateMachine) implementation from an `impl` block.
387///
388/// See the [`stateful`] module docs for more information.
389pub use hegel_macros::state_machine;
390
391/// The main entrypoint into Hegel.
392///
393/// The function must take exactly one parameter of type [`TestCase`]. The test case can be
394/// used to generate values via [`TestCase::draw`].
395///
396/// The `#[test]` attribute is added automatically and must not be present on the function.
397///
398/// ```ignore
399/// #[hegel::test]
400/// fn my_test(tc: TestCase) {
401/// let x: i32 = tc.draw(integers());
402/// assert!(x + 0 == x);
403/// }
404/// ```
405///
406/// You can set settings using attributes on [`test`], corresponding to methods on [`Settings`]:
407///
408/// ```ignore
409/// #[hegel::test(test_cases = 500)]
410/// fn test_runs_many_more_times(tc: TestCase) {
411/// let x: i32 = tc.draw(integers());
412/// assert!(x + 0 == x);
413/// }
414/// ```
415///
416/// You can use other test attribute macros, like `tokio::test`, by putting them *before* `hegel::test`:
417///
418/// ```ignore
419/// #[tokio::test]
420/// #[hegel::test]
421/// async fn my_async_test() {
422/// // ...
423/// }
424/// ```
425pub use hegel_macros::test;
426
427/// Turn a function into a standalone Hegel binary entry point.
428///
429/// The function must take exactly one parameter of type [`TestCase`]. Behaves
430/// like [`test`] — draws are rewritten to record variable names, and any
431/// `#[hegel::explicit_test_case]` attributes are run first — but instead of
432/// producing a `#[test]` it produces a plain function body that parses CLI
433/// arguments and runs a [`Hegel`] driver.
434///
435/// Supported CLI flags (with defaults taken from the attribute args):
436/// `--test-cases`, `--seed`, `--verbosity`, `--derandomize`, `--database`,
437/// `--suppress-health-check`, `-h` / `--help`.
438///
439/// ```ignore
440/// use hegel::TestCase;
441/// use hegel::generators as gs;
442///
443/// #[hegel::main(test_cases = 500)]
444/// fn main(tc: TestCase) {
445/// let n: i32 = tc.draw(gs::integers());
446/// assert_eq!(n + 0, n);
447/// }
448/// ```
449pub use hegel_macros::main;
450
451/// Rewrite a function taking a [`TestCase`] plus additional arguments into
452/// one that takes just those arguments and internally runs Hegel.
453///
454/// Behaves like [`test`] for name rewriting, explicit test cases, and
455/// settings parsing. The generated function has the original signature
456/// with the `TestCase` parameter removed, and its body is run as an
457/// [`FnMut`] closure inside [`Hegel::run`].
458///
459/// ```ignore
460/// use hegel::TestCase;
461/// use hegel::generators as gs;
462///
463/// #[hegel::standalone_function(test_cases = 10)]
464/// fn check_addition_commutative(tc: TestCase, increment: i32) {
465/// let n: i32 = tc.draw(gs::integers());
466/// assert_eq!(n + increment, increment + n);
467/// }
468///
469/// // callers invoke it as a normal function:
470/// # fn _example() {
471/// check_addition_commutative(5);
472/// # }
473/// ```
474pub use hegel_macros::standalone_function;
475
476#[doc(hidden)]
477pub use cli::CliOutcome;
478#[doc(hidden)]
479pub use cli::apply_cli_args as __apply_cli_args;
480#[doc(hidden)]
481pub use runner::hegel;
482pub use runner::{Backend, HealthCheck, Hegel, Mode, Phase, Settings, Verbosity};