Skip to main content

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