use arbitrary::{Arbitrary, Unstructured};
use crate::error::*;
use arbitrary::unstructured::Int;
use std::ops::RangeInclusive;
#[must_use = "Be sure to use Generator::fail even if you're not generating new values, to provide an error message when running check()"]
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct Generator<'a> {
#[deref]
#[deref_mut]
arb: Unstructured<'a>,
check: bool,
}
impl<'a> From<Unstructured<'a>> for Generator<'a> {
fn from(arb: Unstructured<'a>) -> Self {
assert!(!arb.is_empty());
Self { arb, check: false }
}
}
impl<'a> From<&'a [u8]> for Generator<'a> {
fn from(bytes: &'a [u8]) -> Self {
arbitrary::Unstructured::new(bytes).into()
}
}
impl<'a> Generator<'a> {
pub(crate) fn checker() -> Self {
Self {
arb: arbitrary::Unstructured::new(&[]),
check: true,
}
}
pub fn fail(&self, err: impl ToString) -> Mutation<()> {
if self.check {
Err(MutationError::Check(err.to_string()))
} else {
Ok(())
}
}
pub fn set<T: PartialEq + Clone, S: ToString>(
&self,
source: &mut T,
target: &T,
err: impl FnOnce() -> S,
) -> Mutation<()> {
if source != target {
if self.check {
return Err(MutationError::Check(err().to_string()));
} else {
*source = target.clone();
}
}
Ok(())
}
pub fn arbitrary<T: Arbitrary<'a>, S: ToString>(
&mut self,
err: impl FnOnce() -> S,
) -> Mutation<T> {
self.with(err, |u| u.arbitrary())
}
pub fn choose<T: Arbitrary<'a>, S: ToString>(
&mut self,
choices: &'a [T],
err: impl FnOnce() -> S,
) -> Mutation<&T> {
if choices.is_empty() {
return Err(MutationError::User("Empty choices".to_string())).into();
}
if choices.len() == 1 {
return Ok(&choices[0]).into();
}
if !self.check && self.arb.is_empty() {
return Err(MutationError::User("Ran out of entropy".to_string())).into();
}
self.with(err, |u| u.choose(choices))
}
pub fn int_in_range<T, S>(
&mut self,
range: RangeInclusive<T>,
err: impl FnOnce() -> S,
) -> Mutation<T>
where
T: Arbitrary<'a> + PartialOrd + Copy + Int,
S: ToString,
{
if range.start() > range.end() {
return Err(MutationError::User("Invalid range".to_string())).into();
} else if range.start() == range.end() {
return Ok(*range.start()).into();
}
if !self.check && self.arb.is_empty() {
return Err(MutationError::User("Ran out of entropy".to_string())).into();
}
self.with(err, |u| u.int_in_range(range))
}
pub fn with<T, S: ToString>(
&mut self,
err: impl FnOnce() -> S,
f: impl FnOnce(&mut Unstructured<'a>) -> Result<T, arbitrary::Error>,
) -> Mutation<T> {
if self.check {
Err(MutationError::Check(err().to_string())).into()
} else {
f(&mut self.arb).map_err(Into::into)
}
}
}
#[cfg(test)]
pub mod test {
use crate::MutationError;
use rand::prelude::SliceRandom;
use rand::SeedableRng;
#[test]
pub fn test_generator_int_in_range_invalid_range() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
assert_eq!(
gen.int_in_range(5..=4, || "error"),
Err(MutationError::User("Invalid range".to_string()))
);
}
#[test]
pub fn test_generator_int_in_range_valid_range_single_option() {
let mut gen = crate::generator::Generator::from(&[0][..]);
for _ in 0..10 {
assert_eq!(gen.int_in_range(5..=5, || "error").unwrap(), 5);
}
assert_eq!(gen.len(), 1);
}
#[test]
pub fn test_generator_int_in_range_valid_range_multiple_options() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
for i in 0..6 {
assert_eq!(gen.int_in_range(0..=3, || "error").unwrap(), i % 4);
}
assert_eq!(gen.len(), 0);
assert_eq!(
gen.int_in_range(0..=3, || "error"),
Err(MutationError::User("Ran out of entropy".to_string()))
);
}
#[test]
pub fn test_generator_no_choices() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
let choices: [usize; 0] = [];
assert_eq!(
gen.choose(&choices, || "error"),
Err(MutationError::User("Empty choices".to_string()))
);
}
#[test]
pub fn test_generator_one_choices() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
let choices = [0];
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
}
#[test]
pub fn test_generator_choose_two_values() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
let choices = [0, 1];
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(
gen.choose(&choices, || "error"),
Err(MutationError::User("Ran out of entropy".to_string()))
);
}
#[test]
pub fn test_generator_choose_three_values() {
let mut gen = crate::generator::Generator::from(&[0, 1, 2, 3, 4, 5][..]);
let choices = [0, 1, 2];
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &2);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &2);
assert_eq!(
gen.choose(&choices, || "error"),
Err(MutationError::User("Ran out of entropy".to_string()))
);
}
#[test]
pub fn test_generator_choose_three_values_random() {
let mut u_data = [0, 1, 2, 3, 4, 5];
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
u_data.shuffle(&mut rng);
let mut gen = crate::generator::Generator::from(&u_data[..]);
let choices = [0, 1, 2];
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &2);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &2);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &1);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
assert_eq!(
gen.choose(&choices, || "error"),
Err(MutationError::User("Ran out of entropy".to_string()))
);
}
#[test]
pub fn test_generator_choose_single_without_entropy() {
let mut gen = crate::generator::Generator::from(&[0][..]);
let choices = [0];
for _ in 0..10 {
assert_eq!(gen.choose(&choices, || "error").unwrap(), &0);
}
assert_eq!(gen.len(), 1);
}
}