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//! ## Learning more
181//!
182//! - Browse the [`generators`] module for the full list of available generators.
183//! - See [`Settings`] for more configuration settings to customise how your test runs.
184
185#![forbid(future_incompatible)]
186#![cfg_attr(docsrs, feature(doc_cfg))]
187
188pub(crate) mod antithesis;
189pub(crate) mod cbor_utils;
190pub(crate) mod control;
191pub mod generators;
192pub(crate) mod protocol;
193pub(crate) mod runner;
194pub mod stateful;
195mod test_case;
196
197#[doc(hidden)]
198pub use control::currently_in_test_context;
199pub use generators::Generator;
200pub use test_case::TestCase;
201
202// re-export for macro use
203#[doc(hidden)]
204pub use ciborium;
205#[doc(hidden)]
206pub use paste;
207#[doc(hidden)]
208pub use test_case::{__IsTestCase, __assert_is_test_case, generate_from_schema, generate_raw};
209
210// re-export public api
211#[doc(hidden)]
212pub use antithesis::TestLocation;
213
214/// Derive a generator for a struct or enum.
215///
216/// This implements [`DefaultGenerator`](generators::DefaultGenerator) for the type,
217/// allowing it to be used with [`default`](generators::default) via `default::<T>()`.
218///
219/// For structs, the generated generator has:
220/// - `<field>(generator)` - builder method to customize each field's generator
221///
222/// For enums, the generated generator has:
223/// - `default_<VariantName>()` - methods returning default variant generators
224/// - `<VariantName>(generator)` - builder methods to customize variant generation
225///
226/// # Struct Example
227///
228/// ```ignore
229/// use hegel::DefaultGenerator;
230/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
231///
232/// #[derive(DefaultGenerator)]
233/// struct Person {
234///     name: String,
235///     age: u32,
236/// }
237///
238/// #[hegel::test]
239/// fn generates_people(tc: hegel::TestCase) {
240///     let generator = gs::default::<Person>()
241///         .age(gs::integers::<u32>().min_value(0).max_value(120));
242///     let person: Person = tc.draw(generator);
243/// }
244/// ```
245///
246/// # Enum Example
247///
248/// ```ignore
249/// use hegel::DefaultGenerator;
250/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
251///
252/// #[derive(DefaultGenerator)]
253/// enum Status {
254///     Pending,
255///     Active { since: String },
256///     Error { code: i32, message: String },
257/// }
258///
259/// #[hegel::test]
260/// fn generates_statuses(tc: hegel::TestCase) {
261///     let generator = gs::default::<Status>()
262///         .Active(
263///             gs::default::<Status>()
264///                 .default_Active()
265///                 .since(gs::text().max_size(20))
266///         );
267///     let status: Status = tc.draw(generator);
268/// }
269/// ```
270pub use hegel_macros::DefaultGenerator;
271
272/// Define a composite generator from a function.
273///
274/// The first parameter must be a [`TestCase`] and is passed automatically
275/// when the generator is drawn. Any additional parameters become parameters
276/// of the returned factory function. The function must have an explicit
277/// return type.
278///
279/// ```ignore
280/// use hegel::generators as gs;
281///
282/// #[hegel::composite]
283/// fn sorted_vec(tc: hegel::TestCase, min_len: usize) -> Vec<i32> {
284///     let mut v: Vec<i32> = tc.draw(gs::vecs(gs::integers()).min_size(min_len));
285///     v.sort();
286///     v
287/// }
288///
289/// #[hegel::test]
290/// fn test_sorted(tc: hegel::TestCase) {
291///     let v = tc.draw(sorted_vec(3));
292///     assert!(v.len() >= 3);
293///     assert!(v.windows(2).all(|w| w[0] <= w[1]));
294/// }
295/// ```
296pub use hegel_macros::composite;
297
298/// Derive a [`StateMachine`](crate::stateful::StateMachine) implementation from an `impl` block.
299///
300/// See the [`stateful`] module docs for more information.
301pub use hegel_macros::state_machine;
302
303/// The main entrypoint into Hegel.
304///
305/// The function must take exactly one parameter of type [`TestCase`]. The test case can be
306/// used to generate values via [`TestCase::draw`].
307///
308/// The `#[test]` attribute is added automatically and must not be present on the function.
309///
310/// ```ignore
311/// #[hegel::test]
312/// fn my_test(tc: TestCase) {
313///     let x: i32 = tc.draw(integers());
314///     assert!(x + 0 == x);
315/// }
316/// ```
317///
318/// You can set settings using attributes on [`test`], corresponding to methods on [`Settings`]:
319///
320/// ```ignore
321/// #[hegel::test(test_cases = 500)]
322/// fn test_runs_many_more_times(tc: TestCase) {
323///     let x: i32 = tc.draw(integers());
324///     assert!(x + 0 == x);
325/// }
326/// ```
327pub use hegel_macros::test;
328
329#[doc(hidden)]
330pub use runner::hegel;
331pub use runner::{HealthCheck, Hegel, Settings, Verbosity};