Structured Testing
In addition to generating random byte slices, bolero
supports generating well-formed types, with the bolero-generator
crate.
Operation Example
Let's supposes we've implemented a MySet
data structure. It has 3 operations:
insert(value)
- inserts an value into the setremove(value)
- removes an value from the setclear()
- removes all values from the set
The operations can easily be modeled as an enum
:
#![allow(unused_variables)] fn main() { use bolero::generator::TypeGenerator; #[derive(Debug, TypeGenerator)] enum Operation { Insert(u64), Remove(u64), Clear, } }
Note that we've added TypeGenerator
to the list of derives. This enables bolero
to generate random values for Operation
. We can combine that with a Vec<Operation>
and get a list of operations to perform on our MySet
data structure.
use bolero::{check, generator::*}; use my_set::MySet; #[derive(Debug, TypeGenerator)] enum Operation { Insert(u64), Remove(u64), Clear, } fn main() { check!() .with_type::<Vec<Operation>>() .for_each(|operations| { let mut set = MySet::new(); for operation in operations.iter() { match operation { Operation::Insert(value) => { set.insert(value); } Operation::Remove(value) => { set.remove(value); } Operation::Clear => { set.clear(); } } } }) }
Controlling The Number Of Operations Generated
Using check!().with_type::<Vec<T>>()
will generate vectors with lengths in the range 0..=64
. If you need more control over the number of elements being generated you can instead use .with_generator
and provide a customized generator:
use bolero::{check, generator::*}; use bolero::gen; use my_set::MySet; #[derive(Debug, TypeGenerator)] enum Operation { Insert(u64), Remove(u64), Clear, } fn main() { check!() // Generate 0 to 200 operations .with_generator(gen::<Vec<Operation>>().with().len(0..=200)) .for_each(|operations| { let mut set = MySet::new(); for operation in operations.iter() { match operation { Operation::Insert(value) => { set.insert(value); } Operation::Remove(value) => { set.remove(value); } Operation::Clear => { set.clear(); } } } // assertions go here }) }
Using Test Oracles
The basic test we constructed above will make sure we don't panic on any of the list of operations. We can take it to the next step by using a test oracle to make sure the behavior of MySet
is actually correct. Here we'll use HashSet
from the std
library:
use bolero::{check, generator::*}; use my_set::MySet; use std::collections::HashSet; #[derive(Debug, TypeGenerator)] enum Operation { Insert(u64), Remove(u64), Clear, } fn main() { check!() .with_type::<Vec<Operation>>() .for_each(|operations| { let mut set = MySet::new(); let mut oracle = HashSet::new(); for operation in operations.iter() { match operation { Operation::Insert(value) => { set.insert(value); oracle.insert(value); } Operation::Remove(value) => { set.remove(value); oracle.remove(value); } Operation::Clear => { set.clear(); oracle.clear(); } } } assert!(set.iter().eq(oracle.iter())); }) }