BurntSushi / quickcheck

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

Cannot use Rng methods on `Gen` when implementing `Arbitrary` #279

Open nbraud opened 3 years ago

nbraud commented 3 years ago

I noticed when trying to update a project to quickcheck 1.x, that Gen is now an opaque struct and users implementing Arbitrary can only use Gen::choose to get randomness. This isn't sufficient in some usecases, which were previously served by using Gen's Rng methods: for instance, in uutils' factor I need to draw integers from large ranges known at runtime, to generate integers of known factorization:

impl quickcheck::Arbitrary for Factors {
    fn arbitrary(gen: &mut quickcheck::Gen) -> Self {
        use rand::Rng;
        let mut f = Factors::one();
        let mut g = 1u64;
        let mut n = u64::MAX;

        // Adam Kalai's algorithm for generating uniformly-distributed
        // integers and their factorization.
        //
        // See Generating Random Factored Numbers, Easily, J. Cryptology (2003)
        'attempt: loop {
            while n > 1 {
                n = gen.gen_range(1, n);
                if miller_rabin::is_prime(n) {
                    if let Some(h) = g.checked_mul(n) {
                        f.push(n);
                        g = h;
                    } else {
                        // We are overflowing u64, retry
                        continue 'attempt;
                    }
                }
            }

            return f;
        }
    }
}

(Technically, I could repeatedly use gen.choose(&[0, 1]) to draw individual, random bits, but this would be beyond silly)

nbraud commented 3 years ago

Seems related to #267

BurntSushi commented 3 years ago

Yes. The plan is to make a seed available, and then you should be able to materialize any RNG from that.

audunska commented 3 years ago

This really hurts the ability to write good Arbitrary impls for complicated data. I'm sure there are lots of examples, but my use case can be reduced to just a vector of vectors, Vec<Vec<u32>>. To make sure I sample the data properly I want to generate both tall skinny arrays and short wide ones. This means that the size parameter must be divided randomly into two factors in the arbitrary method, which requires a bounded random float.

If the issue with gen_range is that it exposes rand types, could I propose simply adding a public method Gen::gen_uniform(&mut self) -> f32 which generates a uniformly distributed float in the unit interval? That should satisfy most needs.

divagant-martian commented 2 years ago

Is there a known plan to make this work that would be followed by a first time contributor?

bergmark commented 2 years ago

Perhaps a bit hacky, but the workaround I ended up using was:

pub fn range(gen: &mut Gen, range: Range<u64>) -> u64 {
    u64::arbitrary(gen) % (range.end - range.start) + range.start
}