BurntSushi / quickcheck

Automated property based testing for Rust (with shrinking).
The Unlicense
2.4k stars 149 forks source link

quickcheck combinators #245

Closed FintanH closed 4 years ago

FintanH commented 4 years ago

Hey hey :wave:

I'm using your library coming fresh into the Rust world after spending time using Haskell. Something that's surprised me looking around is the lack of combinators for writing Arbitrary instances. I was able to manage easily for structs because I can just dish out to the Arbitrary instances for base types like String. But as soon as I got to enum I became lost. In Haskell we have things like oneof, frequency, and elements.

Is there idiomatic way to do these things with quickcheck? If so, can I help document them? Or would these combinators be welcome in the library?

Looking forward to your response and hoping I can help out here :)

hengchu commented 4 years ago

@FintanH The combinator that I miss the most is frequency, and here's an attempt in Rust using this crate. Hope it helps! Critiques are also welcome :)

use quickcheck::{Arbitrary, Gen};

pub type BoxedGenerator<G, T> = Box<dyn FnMut(&mut G) -> T>;

pub fn frequency<G, T>(g: &mut G, freqs: &mut [(i64, BoxedGenerator<G, T>)]) -> T
where
    G: Gen,
{
    if freqs.is_empty() {
        panic!("frequency: received empty list")
    }
    if freqs.iter().all(|(v, _)| *v == 0) {
        panic!("frequency: all weights were zero")
    }
    if freqs.iter().any(|(v, _)| *v < 0) {
        panic!("frequency: some weight is negative")
    }
    let sum_of_weights: i64 = freqs.iter().map(|v| v.0).sum();
    let mut weight = i64::arbitrary(g).abs() % sum_of_weights + 1;
    // println!("s = {}, w = {}", sum_of_weights, weight);
    for (v, f) in freqs {
        if weight - *v <= 0 {
            return f(g);
        } else {
            weight -= *v
        };
    }
    panic!("did not exhaust weight {}", weight)
}
FintanH commented 4 years ago

Thanks @hengchu :blush: I actually ended up experimenting with proptest which allowed me to limit the search space a bit easier.

Regarding your code sample, you could bolster it up a bit you could use [NonEmpty](https://docs.rs/nonempty/0.1.5/nonempty/struct.NonEmpty.html) instead of an array, that way you don't need to do the is_empty check :)

Maybe something like a quickcheck-combinators crate would be welcome :thinking:

BurntSushi commented 4 years ago

Since rand::RngCore is a super-trait of quickcheck::Gen, the intent is that you should be able to use any of the methods on RngCore and Rng. Specifically, the latter is probably where you'll find your combinators.

FintanH commented 4 years ago

Those pointers are super helpful @BurntSushi! I'll have to play with quickcheck again and get a better feel for it :) Being new to Rust means I'm still getting a feel for the ecosystem and how things are organised. I'll try make a PR with any helpful docs/tutorials if I come up with anything.

Thanks for the response :heart: