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
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 same backend connection — they are views onto one test
198//! case, not independent test cases. Individual backend calls are serialised
199//! by a shared mutex, so code like "spawn worker, worker draws, join, main
200//! thread draws" is deterministic.
201//!
202//! **Using threads is currently extremely fragile and should only be used with
203//! extreme caution right now.** You are liable to get flaky test failures when
204//! multiple threads draw concurrently. We intend to support this use case
205//! increasingly well over time, but right now it is a significant footgun —
206//! see [`TestCase`]'s documentation for the full contract and the patterns
207//! that are safe to 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 cbor_utils;
221pub(crate) mod cli;
222pub(crate) mod control;
223#[cfg(feature = "native")]
224pub mod embed;
225pub mod explicit_test_case;
226pub mod extras;
227pub mod generators;
228#[cfg(feature = "native")]
229pub(crate) mod native;
230#[doc(hidden)]
231pub mod run_lifecycle;
232pub(crate) mod runner;
233#[cfg(not(feature = "native"))]
234pub(crate) mod server;
235pub mod stateful;
236mod test_case;
237#[cfg(feature = "native")]
238pub(crate) mod unicodedata;
239#[doc(hidden)]
240pub use control::currently_in_test_context;
241pub use explicit_test_case::ExplicitTestCase;
242pub use generators::Generator;
243pub use test_case::TestCase;
244
245// re-export for macro use
246#[doc(hidden)]
247pub use ciborium;
248#[doc(hidden)]
249pub use paste;
250#[doc(hidden)]
251pub use test_case::{
252    __IsTestCase, __assert_is_test_case, generate_from_schema, generate_raw, with_output_override,
253};
254
255// re-export public api
256#[doc(hidden)]
257pub use antithesis::TestLocation;
258
259// Internal re-exports for benches/. Gated on the private `__bench` feature so
260// the items remain `pub(crate)` for normal builds; the bench harness opts in
261// with `--features __bench`. Functions are wrapped (rather than re-exported)
262// because the originals are `pub(crate)` in `native::core::state`.
263#[doc(hidden)]
264#[cfg(feature = "__bench")]
265pub mod __bench {
266    pub use crate::native::bignum::BigInt;
267    pub use crate::native::core::choices::{BytesChoice, FloatChoice, IntegerChoice, StringChoice};
268    pub use crate::native::intervalsets::IntervalSet;
269
270    pub fn biased_integer_sample(
271        ic: &IntegerChoice,
272        rng: &mut rand::rngs::SmallRng,
273    ) -> crate::native::bignum::BigInt {
274        crate::native::core::state::biased_integer_sample(ic, rng)
275    }
276
277    pub fn biased_string_sample(sc: &StringChoice, rng: &mut rand::rngs::SmallRng) -> Vec<u32> {
278        crate::native::core::state::biased_string_sample(sc, rng)
279    }
280
281    pub fn biased_bytes_sample(bc: &BytesChoice, rng: &mut rand::rngs::SmallRng) -> Vec<u8> {
282        crate::native::core::state::biased_bytes_sample(bc, rng)
283    }
284
285    pub fn biased_float_sample(fc: &FloatChoice, rng: &mut rand::rngs::SmallRng) -> f64 {
286        crate::native::core::state::biased_float_sample(fc, rng)
287    }
288}
289
290/// Derive a generator for a struct or enum.
291///
292/// This implements [`DefaultGenerator`](generators::DefaultGenerator) for the type,
293/// allowing it to be used with [`default`](generators::default) via `default::<T>()`.
294///
295/// For structs, the generated generator has:
296/// - `<field>(generator)` - builder method to customize each field's generator
297///
298/// For enums, the generated generator has:
299/// - `default_<VariantName>()` - methods returning default variant generators
300/// - `<VariantName>(generator)` - builder methods to customize variant generation
301///
302/// # Struct Example
303///
304/// ```ignore
305/// use hegel::DefaultGenerator;
306/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
307///
308/// #[derive(DefaultGenerator)]
309/// struct Person {
310///     name: String,
311///     age: u32,
312/// }
313///
314/// #[hegel::test]
315/// fn generates_people(tc: hegel::TestCase) {
316///     let generator = gs::default::<Person>()
317///         .age(gs::integers::<u32>().min_value(0).max_value(120));
318///     let person: Person = tc.draw(generator);
319/// }
320/// ```
321///
322/// # Enum Example
323///
324/// ```ignore
325/// use hegel::DefaultGenerator;
326/// use hegel::generators::{self as gs, DefaultGenerator as _, Generator as _};
327///
328/// #[derive(DefaultGenerator)]
329/// enum Status {
330///     Pending,
331///     Active { since: String },
332///     Error { code: i32, message: String },
333/// }
334///
335/// #[hegel::test]
336/// fn generates_statuses(tc: hegel::TestCase) {
337///     let generator = gs::default::<Status>()
338///         .active(|g| g.since(gs::text().max_size(20)));
339///     let status: Status = tc.draw(generator);
340/// }
341/// ```
342pub use hegel_macros::DefaultGenerator;
343
344/// Define a composite generator from a function.
345///
346/// The first parameter must be a [`TestCase`] and is passed automatically
347/// when the generator is drawn. Any additional parameters become parameters
348/// of the returned factory function. The function must have an explicit
349/// return type.
350///
351/// ```ignore
352/// use hegel::generators as gs;
353///
354/// #[hegel::composite]
355/// fn sorted_vec(tc: hegel::TestCase, min_len: usize) -> Vec<i32> {
356///     let mut v: Vec<i32> = tc.draw(gs::vecs(gs::integers()).min_size(min_len));
357///     v.sort();
358///     v
359/// }
360///
361/// #[hegel::test]
362/// fn test_sorted(tc: hegel::TestCase) {
363///     let v = tc.draw(sorted_vec(3));
364///     assert!(v.len() >= 3);
365///     assert!(v.windows(2).all(|w| w[0] <= w[1]));
366/// }
367/// ```
368pub use hegel_macros::composite;
369pub use hegel_macros::explicit_test_case;
370
371#[doc(hidden)]
372pub use hegel_macros::rewrite_draws;
373
374/// Derive a [`StateMachine`](crate::stateful::StateMachine) implementation from an `impl` block.
375///
376/// See the [`stateful`] module docs for more information.
377pub use hegel_macros::state_machine;
378
379/// The main entrypoint into Hegel.
380///
381/// The function must take exactly one parameter of type [`TestCase`]. The test case can be
382/// used to generate values via [`TestCase::draw`].
383///
384/// The `#[test]` attribute is added automatically and must not be present on the function.
385///
386/// ```ignore
387/// #[hegel::test]
388/// fn my_test(tc: TestCase) {
389///     let x: i32 = tc.draw(integers());
390///     assert!(x + 0 == x);
391/// }
392/// ```
393///
394/// You can set settings using attributes on [`test`], corresponding to methods on [`Settings`]:
395///
396/// ```ignore
397/// #[hegel::test(test_cases = 500)]
398/// fn test_runs_many_more_times(tc: TestCase) {
399///     let x: i32 = tc.draw(integers());
400///     assert!(x + 0 == x);
401/// }
402/// ```
403///
404/// You can use other test attribute macros, like `tokio::test`, by putting them *before* `hegel::test`:
405///
406/// ```ignore
407/// #[tokio::test]
408/// #[hegel::test]
409/// async fn my_async_test() {
410///     // ...
411/// }
412/// ```
413pub use hegel_macros::test;
414
415/// Turn a function into a standalone Hegel binary entry point.
416///
417/// The function must take exactly one parameter of type [`TestCase`]. Behaves
418/// like [`test`] — draws are rewritten to record variable names, and any
419/// `#[hegel::explicit_test_case]` attributes are run first — but instead of
420/// producing a `#[test]` it produces a plain function body that parses CLI
421/// arguments and runs a [`Hegel`] driver.
422///
423/// Supported CLI flags (with defaults taken from the attribute args):
424/// `--test-cases`, `--seed`, `--verbosity`, `--derandomize`, `--database`,
425/// `--suppress-health-check`, `-h` / `--help`.
426///
427/// ```ignore
428/// use hegel::TestCase;
429/// use hegel::generators as gs;
430///
431/// #[hegel::main(test_cases = 500)]
432/// fn main(tc: TestCase) {
433///     let n: i32 = tc.draw(gs::integers());
434///     assert_eq!(n + 0, n);
435/// }
436/// ```
437pub use hegel_macros::main;
438
439/// Rewrite a function taking a [`TestCase`] plus additional arguments into
440/// one that takes just those arguments and internally runs Hegel.
441///
442/// Behaves like [`test`] for name rewriting, explicit test cases, and
443/// settings parsing. The generated function has the original signature
444/// with the `TestCase` parameter removed, and its body is run as an
445/// [`FnMut`] closure inside [`Hegel::run`].
446///
447/// ```ignore
448/// use hegel::TestCase;
449/// use hegel::generators as gs;
450///
451/// #[hegel::standalone_function(test_cases = 10)]
452/// fn check_addition_commutative(tc: TestCase, increment: i32) {
453///     let n: i32 = tc.draw(gs::integers());
454///     assert_eq!(n + increment, increment + n);
455/// }
456///
457/// // callers invoke it as a normal function:
458/// # fn _example() {
459/// check_addition_commutative(5);
460/// # }
461/// ```
462pub use hegel_macros::standalone_function;
463
464#[doc(hidden)]
465pub use cli::CliOutcome;
466#[doc(hidden)]
467pub use cli::apply_cli_args as __apply_cli_args;
468#[doc(hidden)]
469pub use runner::hegel;
470pub use runner::{HealthCheck, Hegel, Mode, Phase, Settings, Verbosity};
471#[cfg(not(feature = "native"))]
472#[doc(hidden)]
473pub use server::process::__test_kill_server;
474#[cfg(not(feature = "native"))]
475#[doc(hidden)]
476pub use server::process::format_log_excerpt;