1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//! A heckin small test case generator.
//!
//! # What is test case generation?
//!
//! A test case generator is a program which writes programs to test other programs.
//! The idea is that we can find more bugs if we test more paths in a program,
//! and the best way to do that is to provide a wide range of inputs to our
//! programs. And the best way to do _that_ is to write a program designed to
//! generate inputs. You may have heard this being referred to as "fuzzing" or
//! "property testing" as well.
//!
//! # Examples
//!
//! This is a basic roundtrip test for an RGB serializer and parser, which ensures
//! that the output matches the original input.
//!
//! ```
//! use heckcheck::prelude::*;
//!
//! /// A color value encoded as Red-Green-Blue
//! #[derive(Clone, Debug, Arbitrary, PartialEq)]
//! pub struct Rgb {
//! pub r: u8,
//! pub g: u8,
//! pub b: u8,
//! }
//!
//! impl Rgb {
//! /// Convert from RGB to Hexadecimal.
//! pub fn to_hex(&self) -> String {
//! format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
//! }
//!
//! /// Convert from Hexadecimal to RGB.
//! pub fn from_hex(s: String) -> Self {
//! let s = s.strip_prefix('#').unwrap();
//! Rgb {
//! r: u8::from_str_radix(&s[0..2], 16).unwrap(),
//! g: u8::from_str_radix(&s[2..4], 16).unwrap(),
//! b: u8::from_str_radix(&s[4..6], 16).unwrap(),
//! }
//! }
//! }
//!
//! // Validate values can be converted from RGB to Hex and back.
//! heckcheck::check(|rgb: Rgb| {
//! let hex = rgb.to_hex();
//! let res = Rgb::from_hex(hex);
//! assert_eq!(rgb, res);
//! Ok(())
//! });
//! ```
//!
//! # Philosophy
//!
//! We believe that test case generation is an *essential* tool in modern
//! programmer's toolboxes, and both fuzzing and property testing play a role in
//! this. Just like [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz),
//! `heckcheck` relies on the stable `Arbitrary` trait to modify structured
//! data. This means that all code in a crate can just implement `Arbitrary` in
//! order to be hooked up to a variety of different test case generation tools.
//!
//! Unlike some other tools, `heckcheck` is also *extensible*. The built-in
//! shrinker and permutation algorithms aren't necessarily the best in class.
//! But they can be modified in order to be improved. The end vision would be to
//! see something like `Arbitrary`, `cargo-fuzz`, and `heckcheck` provided out
//! of the box by the `::test` module.
//!
//! # Replaying tests
//!
//! When a test generated by `heckcheck` fails, we print a `base64` string of
//! the input data as part of the panic message. You can pass this string
//! together with the failing code to `heckcheck::replay` to create a
//! reproduction. This makes it quick to go from finding a failing test to
//! having a reproduction available.
//!
//! # Conditional derives
//!
//! If you only want to only derive `Default` when the code is being tested, you
//! can use `cfg_attr(test)` to only compile it when code is being tested.
//!
//! ```rust
//! #[cfg_attr(test, derive(Arbitrary))]
//! #[derive(Clone, Debug, PartialEq)]
//! pub struct Rgb {
//! pub r: u8,
//! pub g: u8,
//! pub b: u8,
//! }
//! ```
//!
//! # Acknowledgements
//!
//! This crate was built thanks to the work of the [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) team on the
//! [`arbitrary`](https://docs.rs/arbitrary/1.0.1/arbitrary/) crate. It has been
//! heavily inspired by burntsushi's work on
//! [`quickcheck`](https://docs.rs/quickcheck/1.0.3/quickcheck/). And we would like to thank
//! [Boxy](https://twitter.com/EllenNyan0214/status/1418730276440707079) who
//! solved a critical bug that almost made us give up.
//!
#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
#![deny(missing_debug_implementations, nonstandard_style)]
#![warn(missing_docs, unreachable_pub)]
pub use arbitrary;
use arbitrary::Arbitrary;
mod checker;
mod shrink;
mod shrinker;
pub use checker::HeckCheck;
pub use shrink::{Shrink, ShrinkReport};
pub use shrinker::Shrinker;
/// The `heckcheck` prelude
pub mod prelude {
pub use arbitrary::Arbitrary;
}
/// Check a target.
///
/// This is a shorthand for calling `HeckCheck::new` and `HeckCheck::check`.
pub fn check<A, F>(f: F)
where
A: for<'b> Arbitrary<'b>,
F: FnMut(A) -> arbitrary::Result<()>,
{
let mut checker = HeckCheck::new();
checker.check(f);
}
/// Replay a failing test from a base64 string.
///
/// When a call to `check` fails, a base64-encoded string is printed
/// representing the failing input data. You can pass this string together with
/// the failing test code to `heckcheck::replay` to create a permanent
/// reproduction of the error.
pub fn replay<A, F>(bytes: &str, mut f: F)
where
A: for<'b> Arbitrary<'b>,
F: FnMut(A) -> arbitrary::Result<()>,
{
let bytes = base64::decode(bytes).unwrap();
let mut u = arbitrary::Unstructured::new(&bytes);
let instance = A::arbitrary(&mut u).unwrap();
f(instance).unwrap();
}