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();
}