dicetest/lib.rs
1//! Framework for writing tests with randomly generated test data.
2//!
3//! # Status of this crate
4//!
5//! The author does not consider this crate as stable yet. Changes will be documented in the
6//! [changelog](https://github.com/jakoschiko/dicetest/blob/master/CHANGELOG.md).
7//!
8//! # Example
9//!
10//! Here's an example of an incorrect sort function tested with dicetest:
11//!
12//! ```
13//! fn bubble_sort<T: Ord>(slice: &mut [T]) {
14//! let len = slice.len();
15//!
16//! for _ in 0..len {
17//! for j in 1..len - 1 {
18//! let jpp = j + 1;
19//! if slice[j] > slice[jpp] {
20//! slice.swap(j, jpp);
21//! }
22//! }
23//! }
24//! }
25//!
26//! #[cfg(test)]
27//! mod tests {
28//! use super::*;
29//! use dicetest::prelude::*;
30//!
31//! #[test]
32//! fn result_of_bubble_sort_is_sorted() {
33//! Dicetest::repeatedly().run(|mut fate| {
34//! let mut v = fate.roll(dice::vec(dice::u8(..), ..));
35//! hint!("unsorted: {:?}", v);
36//!
37//! bubble_sort(&mut v);
38//! hint!(" sorted: {:?}", v);
39//!
40//! let is_sorted = v.windows(2).all(|w| w[0] <= w[1]);
41//! assert!(is_sorted);
42//! })
43//! }
44//! }
45//! ```
46//!
47//! Running `cargo test` produces the following output:
48//!
49//! ```text
50//! The test failed after 31 passes.
51//!
52//! # Config
53//! - seed: 3713861809241954222
54//! - start limit: 0
55//! - end limit: 100
56//! - passes: 200
57//!
58//! # Counterexample
59//! - run code: "/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA=="
60//! - limit: 3
61//! - hints:
62//! - unsorted: [201, 209, 2]
63//! - sorted: [201, 2, 209]
64//! - error: assertion failed: is_sorted
65//! ```
66//!
67//! You can rerun the counterexample by setting an environment variable:
68//!
69//! ```text
70//! DICETEST_DEBUG=/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA== cargo test
71//! ```
72//!
73//! Or you can modify the test:
74//!
75//! ```
76//! # use dicetest::Dicetest;
77//! Dicetest::debug("/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA==").run(|mut fate| {
78//! // ...
79//! })
80//! ```
81//!
82//! # Features
83//!
84//! These features are **available**:
85//!
86//! - Generators for many libstd types (`u8`, `String`, `Vec`, etc.).
87//! - Generators for functions (`FnMut`, `FnOnce`, `Fn`).
88//! - Generator combinators (`map`, `flat_map`, `zip`, etc.).
89//! - Integration of `rand::distributions::Distribution`.
90//! - Integration of `quickcheck::Arbitrary` (without shrinking).
91//! - Configurable test runner.
92//! - Utilities for debugging tests (`hints` and `stats`).
93//!
94//! These features are **missing**:
95//!
96//! - Shrinking of counterexamples.
97//! - Custom pseudorandom number generators.
98//! - Own type class for arbitrary types.
99//!
100//! # Alternatives
101//!
102//! * Write down your test data and use a loop.
103//! * Use the crate [quickcheck].
104//! * Use the crate [proptest].
105//!
106//! [quickcheck]: https://crates.io/crates/quickcheck
107//! [proptest]: https://crates.io/crates/proptest
108//!
109//! # Guide
110//!
111//! This section will guide you through the most important concepts and features of dicetest.
112//!
113//! ## Pseudorandomness
114//!
115//! The type `Seed` allows to determine the [pseudorandomness]. You can either use a fixed
116//! `Seed` or a random `Seed`:
117//!
118//! ```
119//! use dicetest::Seed;
120//!
121//! println!("{:?}", Seed(42));
122//! // Output: Seed(42)
123//!
124//! println!("{:?}", Seed::random());
125//! // Output: Seed(8019292413750407764)
126//! ```
127//!
128//! The `Seed` can be used to initialize the [pseudorandom number generator] `Prng`. For each
129//! `Seed` the `Prng` provides a different infinite pseudorandom sequence of `u64`s
130//!
131//! ```
132//! use dicetest::{Prng, Seed};
133//!
134//! fn print_random_values(mut prng: Prng) {
135//! for _ in 0..3 {
136//! print!("{:?}, ", prng.next_number());
137//! }
138//! println!("...");
139//! }
140//!
141//! print_random_values(Prng::from_seed(Seed(42)));
142//! // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ...
143//! print_random_values(Prng::from_seed(Seed(42)));
144//! // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ...
145//! print_random_values(Prng::from_seed(Seed::random()));
146//! // Output: 4221507577048064061, 15374206214556255352, 4977687432463843847, ...
147//! print_random_values(Prng::from_seed(Seed::random()));
148//! // Output: 11086225885938422405, 9312304973013875005, 1036200222843160301, ...
149//! ```
150//!
151//! [pseudorandomness]: https://en.wikipedia.org/wiki/Pseudorandomness
152//! [pseudorandom number generator]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator
153//!
154//! ## Dice
155//!
156//! Although `Prng` can only generate pseudorandom `u64`s, the `u64`s can be used for constructing
157//! more complex values. The traits `DieOnce` and `Die` represents `Prng`-based generators for
158//! values of any type.
159//!
160//! An implementor of `DieOnce` is a generator that can be used a single time
161//! (similar to [`FnOnce`]).
162//! ```
163//! use dicetest::prelude::*;
164//!
165//! let xx = "xx".to_string();
166//! let yy = "yy".to_string();
167//!
168//! // This generator implements `DieOnce`.
169//! // It chooses one of the `String`s without cloning them.
170//! let xx_or_yy_die = dice::one_of_once().two(xx, yy);
171//! ```
172//!
173//! An implementor of `Die` is a generator that can be used infinite times (similar to [`Fn`]).
174//! ```
175//! use dicetest::prelude::*;
176//!
177//! let xx = "xx".to_string();
178//! let yy = "yy".to_string();
179//!
180//! // This generator implements `Die`.
181//! // It chooses one of the `String`s by cloning them.
182//! let xx_or_yy_die = dice::one_of().two(xx, yy);
183//!
184//! // This generator uses `xx_or_yy_die` to generate three `String`s at once.
185//! let three_xx_or_yy_die = dice::array::<_, _, 3>(xx_or_yy_die);
186//! ```
187//!
188//! Generators can be easily implemented and composed:
189//! ```
190//! use dicetest::prelude::*;
191//!
192//! // A classic die that generates a number between 1 and 6 with uniform distribution.
193//! let classic_die = dice::one_of().six::<u8>(1, 2, 3, 4, 5, 6);
194//!
195//! // A loaded die that generates the number 6 more frequently.
196//! let loaded_die =
197//! dice::weighted_one_of().six::<u8>((1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 6));
198//!
199//! // This die generates the result of the function.
200//! let die_from_fn = dice::from_fn(|_| 42);
201//!
202//! // This die generates always the same `String` by cloning the original one.
203//! let foo_die = dice::just("foo".to_string());
204//!
205//! // This die generates an arbitrary byte.
206//! let byte_die = dice::u8(..);
207//!
208//! // This die generates a non-zero byte.
209//! let non_zero_byte_die = dice::u8(1..);
210//!
211//! // This die generates a `Vec` that contains an arbitrary number of arbitrary bytes.
212//! let bytes_die = dice::vec(dice::u8(..), ..);
213//!
214//! // This die generates a `Vec` that contains up to 10 arbitrary bytes.
215//! let up_to_ten_bytes_die = dice::vec(dice::u8(..), ..=10);
216//!
217//! // This die generates an arbitrary wrapped byte.
218//! struct WrappedByte(u8);
219//! let wrapped_byte_die = dice::u8(..).map(WrappedByte);
220//!
221//! // This die generates a permutation of `(0..=n)` for an arbitrary `n`.
222//! let permutation_die = dice::length(0..).flat_map(|n| {
223//! let vec = (0..=n).collect::<Vec<_>>();
224//! dice::shuffled_vec(vec)
225//! });
226//! ```
227//!
228//! The struct `Fate` is necessary for using `DieOnce` or `Die`. It contains two parameters:
229//!
230//! * `Prng`: Provides the pseudorandom `u64`s that the implementor of `DieOnce` or `Die` can use
231//! for constructing more complex values. The implementor should only use this as its source of
232//! randomness.
233//! * `Limit`: The upper limit for the length of dynamic data structures generated by the
234//! implementor of `DieOnce` or `Die`. The implementor is allowed to freely interpret or even
235//! ignore this value.
236//!
237//! ```
238//! use dicetest::prelude::*;
239//! use dicetest::{Limit, Prng};
240//!
241//! // Provides the randomness for the generator and will be mutated when used.
242//! let mut prng = Prng::from_seed(0x5EED.into());
243//! // Limits the length of dynamic data structures. The generator has only read access.
244//! let limit = Limit(5);
245//!
246//! // Contains all parameters necessary for using `DieOnce` or `Die`.
247//! let mut fate = Fate::new(&mut prng, limit);
248//!
249//! // Generator for a `Vec` with an arbitrary length.
250//! let vec_die = dice::vec(dice::u8(..), ..);
251//!
252//! // Generates a `Vec`. Although `vec_die` can generate a `Vec` with an arbitrary length,
253//! // the length of the actual `Vec` is limited by `limit`.
254//! let vec = fate.roll(vec_die);
255//! assert!(vec.len() <= 5);
256//!
257//! println!("{:?}", vec);
258//! // Output: [252, 231, 153, 0]
259//! ```
260//!
261//! [`FnOnce`]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html
262//! [`Fn`]: https://doc.rust-lang.org/std/ops/trait.Fn.html
263//!
264//! ## Tests
265//!
266//! If you want to write a test with randomly generated test data you can use the test
267//! builder`Dicetest`:
268//! * It can be configured via source code or environment variables.
269//! * It runs your test repeatedly with different seeds.
270//! * It logs useful information that helps you to debug your test.
271//!
272//! ```
273//! use dicetest::prelude::*;
274//!
275//! #[test]
276//! fn test_foo() {
277//! // Runs your test with default configuration.
278//! Dicetest::repeatedly().run(|fate| {
279//! // Write your test here.
280//! });
281//! }
282//!
283//! #[test]
284//! fn test_bar() {
285//! // Runs your test with custom configuration.
286//! Dicetest::repeatedly().passes(10000).run(|fate| {
287//! // Write your test here.
288//! });
289//! }
290//! ```
291//!
292//! The closure contains your test. With the passed `fate` you can generate test data and make
293//! assertions. If the closure panics, `Dicetest` catches the panic, logs the test result to
294//! stdout and resumes the panic.
295//!
296//! ## Hints
297//!
298//! Hints can be used to analyze a single test run. In most cases you want to analyze the
299//! counterexample. Use it to reveal what test data were generated or which branches were taken:
300//!
301//! ```
302//! use dicetest::prelude::*;
303//!
304//! #[test]
305//! fn test_foo() {
306//! Dicetest::repeatedly().run(|mut fate| {
307//! let x = fate.roll(dice::u8(1..=5));
308//! hint_debug!(x);
309//!
310//! let y = fate.roll(dice::u8(1..=3));
311//! if y != x {
312//! hint!("took branch if with y = {}", y);
313//!
314//! assert_eq!(3, y);
315//! } else {
316//! hint!("took branch else");
317//! }
318//! })
319//! }
320//! ```
321//!
322//! Running the test produces the following output:
323//!
324//! ```text
325//! The test failed after 0 passes.
326//!
327//! # Config
328//! - seed: 10929669535587280453
329//! - start limit: 0
330//! - end limit: 100
331//! - passes: 200
332//!
333//! # Counterexample
334//! - run code: "JfXG0LRXjKUMu+YmdrF38/GstRdeLAeMRTKskCQcgNoAAAAAAAAAAA=="
335//! - limit: 0
336//! - hints:
337//! - x = 5
338//! - took branch if with y = 1
339//! - error: assertion failed: `(left == right)`
340//! left: `3`,
341//! right: `1`
342//! ```
343//!
344//! ## Stats
345//!
346//! Stats can be used to analyze multiple test runs. Use it to reveal the distribution of
347//! generated test data or the probability of branches:
348//!
349//! ```
350//! use dicetest::prelude::*;
351//!
352//! #[test]
353//! fn test_foo() {
354//! Dicetest::repeatedly().run(|mut fate| {
355//! let x = fate.roll(dice::u8(1..=5));
356//! stat_debug!(x);
357//!
358//! let y = fate.roll(dice::u8(1..=3));
359//! if y != x {
360//! stat!("branch", "if with y = {}", y)
361//! } else {
362//! stat!("branch", "else");
363//! }
364//! })
365//! }
366//! ```
367//!
368//! Running the test with the environment variable `DICETEST_STATS_ENABLED=true` produces
369//! the following output:
370//!
371//! ```text
372//! The test withstood 200 passes.
373//!
374//! # Config
375//! - seed: 5043079553183914912
376//! - start limit: 0
377//! - end limit: 100
378//! - passes: 200
379//!
380//! # Stats
381//! - branch:
382//! - 29.50% (59): if with y = 1
383//! - 27.50% (55): if with y = 3
384//! - 22.50% (45): if with y = 2
385//! - 20.50% (41): else
386//! - x:
387//! - 31.50% (63): 1
388//! - 22.00% (44): 5
389//! - 17.00% (34): 2
390//! - 15.50% (31): 4
391//! - 14.00% (28): 3
392//! ```
393//!
394//! ## Environment variables
395//!
396//! You can use environment variables to configure your tests without changing the source code.
397//! See the documentation of `Dicetest` for a full list of supported environment variables.
398//! Here are some examples:
399//!
400//! * You want to debug the counterexample of `mytest` with its run code (copied from the test result):
401//! ```text
402//! DICETEST_DEBUG=ABIDje/+CYVkmmCVTwKJ2go6VrzZWMjO2Bqc9m3b3h0DAAAAAAAAAA== cargo test mytest
403//! ```
404//! * You want to reproduce the result of `mytest` with its seed (copied from the test result):
405//! ```text
406//! DICETEST_SEED=795359663177100823 cargo test mytest
407//! ```
408//! * You want to see the stats of `mytest`:
409//! ```text
410//! DICETEST_STATS_ENABLED=true cargo test -- --show-output mytest
411//! ```
412//! * You want to run `mytest` with more passes and bigger test data:
413//! ```text
414//! DICETEST_PASSES_MULTIPLIER=10 DICETEST_LIMIT_MULTIPLIER=2 cargo test mytest
415//! ```
416//! * You want to run `mytest` with a single test run and see the test result:
417//! ```text
418//! DICETEST_MODE=once cargo test -- --show-output mytest
419//! ```
420//!
421//! ## Feature flags
422//!
423//! There are several feature flags for disabling runtime overhead or enabling additional
424//! features at compile time.
425//!
426//! ### `hints` (enabled by default)
427//! Enables or disables the hints feature at compile time. If disabled,
428//! all hints operations are no-ops.
429//!
430//! ### `stats` (enabled by default)
431//! Enables or disables the stats feature at compile time. If disabled,
432//! all stats operations are no-ops.
433//!
434//! ### `rand_core` (disabled by default)
435//! If enabled, `dicetest::Prng` and `dicetest::Fate` implements the `rand_core::RngCore`
436//! trait.
437//!
438//! ### `rand_full` (disabled by default, alias for `rand_core,rand`)
439//! If enabled, `Fate::roll_distribution` and `dice::from_distribution` are available.
440//! This allows to generate values and create `Die`s from implementations
441//! of `rand::distributions::Distribution`.
442//!
443//! ```
444//! # #[cfg(feature = "rand_full")] {
445//! use dicetest::prelude::*;
446//! use dicetest::{Limit, Prng};
447//!
448//! let mut prng = Prng::from_seed(0x5EED.into());
449//! let limit = Limit(5);
450//! let mut fate = Fate::new(&mut prng, limit);
451//!
452//! // Generate a value from a `rand::distributions::Distribution`
453//! let byte: u8 = fate.roll_distribution(rand::distributions::Standard);
454//! println!("{:?}", byte);
455//! // Output: 28
456//!
457//! // Create a `Die` from a `rand::distributions::Distribution`
458//! let byte_die = dice::from_distribution(rand::distributions::Standard);
459//! let bytes_die = dice::vec(byte_die, 1..);
460//! let bytes: Vec<u8> = fate.roll(bytes_die);
461//! println!("{:?}", bytes);
462//! // Output: [236, 205, 151, 229]
463//! # }
464//! ```
465//!
466//! ### `quickcheck_full` (disabled by default, alias for `rand_core,quickcheck`)
467//! If enabled, `Fate` implements the `quickcheck::Gen` trait and `Fate::roll_arbitrary` and
468//! `dice::arbitrary` are available. This allows to generate values and create `Die`s for types
469//! that implements `quickcheck::Arbitrary`.
470//!
471//! ```
472//! # #[cfg(feature = "quickcheck_full")] {
473//! use dicetest::prelude::*;
474//! use dicetest::{Limit, Prng};
475//!
476//! let mut prng = Prng::from_seed(0x5EED.into());
477//! let limit = Limit(5);
478//! let mut fate = Fate::new(&mut prng, limit);
479//!
480//! // Generate a value of a type that implements `quickcheck::Arbitrary`
481//! let byte: u8 = fate.roll_arbitrary();
482//! println!("{:?}", byte);
483//! // Output: 0
484//!
485//! // Create a `Die` for a type that implements `quickcheck::Arbitrary`
486//! let byte_die = dice::arbitrary();
487//! let bytes_die = dice::vec(byte_die, 1..);
488//! let bytes: Vec<u8> = fate.roll(bytes_die);
489//! println!("{:?}", bytes);
490//! // Output: [1, 4, 4, 2]
491//! # }
492//! ```
493
494// This crate makes assumptions regarding the pointer width. The following conditional error
495// prevents the compilation for unsupported pointer widths.
496//
497// See https://github.com/rust-lang/rfcs/issues/1748
498#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
499compile_error!("Only targets with pointer width 32 and 64 are currently supported");
500
501mod macros;
502
503mod util;
504
505mod seed;
506pub use seed::Seed;
507
508mod prng;
509pub use prng::Prng;
510
511mod codie;
512pub use codie::Codie;
513
514mod limit;
515pub use limit::Limit;
516
517mod fate;
518pub use fate::Fate;
519
520mod die_once;
521pub use die_once::DieOnce;
522
523mod die;
524pub use die::Die;
525
526pub mod adapters;
527
528pub mod codice;
529
530pub mod dice;
531
532pub mod hints;
533
534pub mod stats;
535
536pub mod runner;
537
538mod frontend;
539pub use frontend::Dicetest;
540
541pub mod prelude;
542
543#[cfg(test)]
544mod asserts;