Skip to content

Commit

Permalink
Refactor: Use gen::from_fn where applicable
Browse files Browse the repository at this point in the history
This reduces the boilerplate code needed and hopefully makes the code
more readable.
  • Loading branch information
jockbert committed Mar 11, 2024
1 parent 36a4196 commit 059fd5d
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 348 deletions.
13 changes: 3 additions & 10 deletions src/gen/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,16 @@ use rand_chacha::ChaCha8Rng;

/// Generator of boolean values where ratio can be scewed according to given
/// ratios.
pub fn with_ratio(ratio_false: u32, ratio_true: u32) -> BoxGen<bool>
where
{
pub fn with_ratio(ratio_false: u32, ratio_true: u32) -> BoxGen<bool> {
Box::new(BoolGen {
ratio_false,
ratio_true,
})
}

/// Uniformly distributed generator of `true` and `false`.
pub fn evenly() -> BoxGen<bool>
where
{
Box::new(BoolGen {
ratio_false: 1,
ratio_true: 1,
})
pub fn evenly() -> BoxGen<bool> {
with_ratio(1, 1)
}

#[derive(Clone)]
Expand Down
55 changes: 9 additions & 46 deletions src/gen/chain.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{BoxGen, BoxIter, BoxShrink, Gen};
use crate::BoxGen;

/// Concatenate together two generators.
///
Expand All @@ -24,58 +24,21 @@ pub fn chain<E>(first_gen: BoxGen<E>, second_gen: BoxGen<E>) -> BoxGen<E>
where
E: Clone + 'static,
{
Box::new(ChainGen::new(first_gen, second_gen))
}
let shrinker = first_gen.shrinker();

/// Generator wrapper that allows binding new shrinker to existing generator.
#[derive(Clone)]
struct ChainGen<E>
where
E: Clone,
{
generator1: BoxGen<E>,
generator2: BoxGen<E>,
}

impl<E> ChainGen<E>
where
E: Clone,
{
/// Create a new generator with (other) shrinker
pub fn new(g1: BoxGen<E>, g2: BoxGen<E>) -> ChainGen<E> {
ChainGen::<E> {
generator1: g1,
generator2: g2,
}
}
}

impl<E> Gen<E> for ChainGen<E>
where
E: Clone + 'static,
{
fn examples(&self, seed: u64) -> BoxIter<E> {
Box::new(
self.generator1
.examples(seed)
.chain(self.generator2.examples(seed)),
)
}

fn shrinker(&self) -> BoxShrink<E> {
self.generator1.shrinker().clone()
}
crate::gen::from_fn(move |seed| {
first_gen.examples(seed).chain(second_gen.examples(seed))
})
.with_shrinker(shrinker)
}

#[cfg(test)]
mod test {
use crate::{gen::fixed, Gen};

use super::ChainGen;
use crate::gen::fixed;

#[test]
fn empty_generators() {
let gen = ChainGen::new(
let gen = super::chain(
fixed::sequence::<u8>(&[]),
fixed::sequence::<u8>(&[]),
);
Expand All @@ -85,7 +48,7 @@ mod test {

#[test]
fn some_elements_in_each_generator() {
let gen = ChainGen::new(
let gen = super::chain(
fixed::sequence::<u8>(&[1, 2]),
fixed::sequence::<u8>(&[3, 4]),
);
Expand Down
58 changes: 10 additions & 48 deletions src/gen/fixed.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Generators mainly used for internal testing, where you want a
//! deterministicly generated values.
use crate::{BoxGen, BoxIter, BoxShrink, Gen};
use crate::BoxGen;

/// Generates a fixed sequence of examples and then ends, having no more values.
///
Expand All @@ -26,26 +26,13 @@ pub fn sequence<E>(examples: &[E]) -> BoxGen<E>
where
E: Clone + std::fmt::Debug + 'static,
{
Box::new(SequenceGen {
data: examples.to_vec(),
})
let example_vec = examples.to_vec();
crate::gen::from_fn(move |_seed| example_vec.clone().into_iter())
}

/// Generator from a given set of examples to return.
#[derive(Clone)]
struct SequenceGen<E> {
data: Vec<E>,
}

impl<E: Clone + 'static> Gen<E> for SequenceGen<E> {
fn examples(&self, _seed: u64) -> BoxIter<E> {
let x = self.data.clone();
Box::new(x.into_iter())
}

fn shrinker(&self) -> BoxShrink<E> {
crate::shrink::none()
}
/// Infinite generator always returning given constant
pub fn constant<E: Clone + 'static>(example: E) -> BoxGen<E> {
crate::gen::from_fn(move |_seed| std::iter::repeat(example.clone()))
}

/// Generates a fixed loop of examples. This generator is convenient when you,
Expand All @@ -66,28 +53,13 @@ pub fn in_loop<E>(examples: &[E]) -> BoxGen<E>
where
E: Clone + 'static,
{
Box::new(LoopGen {
data: examples.to_vec(),
let examples_vec = examples.to_vec().clone();
crate::gen::from_fn(move |_seed| {
let x = examples_vec.clone();
LoopIter { data: x, index: 0 }
})
}

/// Generator from a given set of examples to return in loop.
#[derive(Clone)]
struct LoopGen<E> {
data: Vec<E>,
}

impl<E: Clone + 'static> Gen<E> for LoopGen<E> {
fn examples(&self, _seed: u64) -> BoxIter<E> {
let x = self.data.clone();
Box::new(LoopIter { data: x, index: 0 })
}

fn shrinker(&self) -> BoxShrink<E> {
crate::shrink::none()
}
}

/// Generator from a given set of examples to return in loop.
#[derive(Clone)]
struct LoopIter<E> {
Expand All @@ -104,13 +76,3 @@ impl<E: Clone + 'static> Iterator for LoopIter<E> {
self.data.get(index_to_use).cloned()
}
}

/// Infinite generator always returning given constant
pub fn constant<E>(example: E) -> BoxGen<E>
where
E: Clone + 'static,
{
Box::new(LoopGen {
data: vec![example],
})
}
65 changes: 7 additions & 58 deletions src/gen/integers.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
//! Generic generators for integer type values.
use crate::BoxGen;
use crate::BoxShrink;
use crate::Gen;
use min_max_traits::{Max, Min};
use num_traits::Num;
use rand::distributions::uniform::SampleUniform;
use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use std::ops::Bound;
use std::ops::RangeBounds;

Expand Down Expand Up @@ -56,10 +53,14 @@ where
+ 'static,
B: RangeBounds<E>,
{
Box::new(UxGen {
min: start(&bounds),
max: end(&bounds),
let min = start(&bounds);
let max = end(&bounds);

crate::gen::from_fn(move |seed| {
let distr = rand::distributions::Uniform::new_inclusive(min, max);
rand_chacha::ChaCha8Rng::seed_from_u64(seed).sample_iter(distr)
})
.with_shrinker(crate::shrink::int())
}

fn start<E, B>(bounds: &B) -> E
Expand All @@ -86,58 +87,6 @@ where
}
}

/// Generator of random usize values.
#[derive(Clone)]
pub struct UxGen<E> {
min: E,
max: E,
}

impl<E> Gen<E> for UxGen<E>
where
E: Num
+ SampleUniform
+ Copy
+ Clone
+ std::cmp::PartialOrd
+ Max
+ 'static,
{
fn examples(&self, seed: u64) -> crate::BoxIter<E> {
Box::new(UxIter::<E> {
min: self.min,
max: self.max,
rng: rand_chacha::ChaCha8Rng::seed_from_u64(seed),
})
}

fn shrinker(&self) -> BoxShrink<E> {
crate::shrink::int()
}
}

/// Iterator of random integer values.
pub struct UxIter<E> {
min: E,
max: E,
rng: ChaCha8Rng,
}

impl<E> Iterator for UxIter<E>
where
E: Clone + SampleUniform,
{
type Item = E;

fn next(&mut self) -> Option<Self::Item> {
let distr = rand::distributions::Uniform::new_inclusive(
self.min.clone(),
self.max.clone(),
);
Some(self.rng.sample(distr))
}
}

#[cfg(test)]
mod tests {
use crate::testing::assert_generator_can_shrink;
Expand Down
37 changes: 3 additions & 34 deletions src/gen/map.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
use crate::BoxGen;
use crate::BoxIter;
use crate::BoxShrink;
use crate::Gen;

/// Generator wrapper that allows binding new shrinker to existing generator.
#[derive(Clone)]
pub struct MapGen<E0, E1>
where
E0: Clone,
E1: Clone,
{
gen0: BoxGen<E0>,
map_fn: fn(E0) -> E1,
unmap_fn: fn(E1) -> E0,
}

/// Convert a generator of type E0 to a generator of type E1.
///
Expand Down Expand Up @@ -54,26 +39,10 @@ where
E0: Clone + 'static,
E1: Clone + 'static,
{
Box::new(MapGen::<E0, E1> {
gen0,
map_fn,
unmap_fn,
})
}
let shrinker = gen0.shrinker();

impl<E0, E1> Gen<E1> for MapGen<E0, E1>
where
E0: Clone + 'static,
E1: Clone + 'static,
{
fn examples(&self, seed: u64) -> BoxIter<E1> {
let it = self.gen0.clone().examples(seed).map(self.map_fn);
Box::new(it)
}

fn shrinker(&self) -> BoxShrink<E1> {
crate::shrink::map(self.gen0.shrinker(), self.map_fn, self.unmap_fn)
}
crate::gen::from_fn(move |seed| gen0.examples(seed).map(map_fn))
.with_shrinker(crate::shrink::map(shrinker, map_fn, unmap_fn))
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 059fd5d

Please sign in to comment.