Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to combine quickcheck 1+ with fake? #320

Open
marktani opened this issue Sep 1, 2023 · 3 comments
Open

How to combine quickcheck 1+ with fake? #320

marktani opened this issue Sep 1, 2023 · 3 comments

Comments

@marktani
Copy link

marktani commented Sep 1, 2023

Following the zero2production book With quickcheck = "0.9.2", quickcheck_macros = "0.9.1" and fake = "~2.3", the following works, because the function Arbitrary::arbitrary expected a Gen trait, and Gen used to be a trait:

#[derive(Debug, Clone)]
struct ValidEmailFixture(pub String);

impl quickcheck::Arbitrary for ValidEmailFixture {
    fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
        let email = SafeEmail().fake_with_rng(g);
        Self(email)
    }
}

#[quickcheck_macros::quickcheck]
fn valid_emails_are_parsed_successfully(valid_email: ValidEmailFixture) -> bool {
    SubscriberEmail::parse(valid_email.0).is_ok()
}

Then we pass the mutable reference g into SafeEmail().fake_with_rng(g); and it works - fake generates random valid emails.

However, in quickcheck = "1", quickcheck::Gen is no trait anymore, only a struct. I haven't found out how to access the inner rng: rand::rngs::SmallRng of Gen to pass it into SafeEmail().fake_with_rng and get fake to work with quickcheck.

How is it still possible in 1+?

@joshuamegnauth54
Copy link

joshuamegnauth54 commented Dec 23, 2023

I've encountered the same issue. I solved it in a hacky way by reimplementing SafeEmail.

#[cfg(test)]
mod tests {
    use fake::locales::{self, Data};
    use quickcheck::{Arbitrary, Gen};

    #[derive(Debug, Clone)]
    struct ValidEmailFixture(pub String);

    impl Arbitrary for ValidEmailFixture {
        fn arbitrary(g: &mut Gen) -> Self {
            let username = g
                .choose(locales::EN::NAME_FIRST_NAME)
                .unwrap()
                .to_lowercase();
            let domain = g.choose(&["com", "net", "org"]).unwrap();
            let email = format!("{username}@example.{domain}");
            Self(email)
        }
    }

    #[quickcheck_macros::quickcheck]
    fn valid_emails_are_parsed_successfully(email: ValidEmailFixture) -> bool {
        SubscriberEmail::parse(email.0).is_ok()
    }
}

Not ideal but also not too bad.

Zero to Production's author, Luca Palmieri, posted an excerpt that covers OP's code above if anyone wants to follow along.

@npuichigo
Copy link

Same issue here @BurntSushi. How to use quickcheck together with other tools like fake which needs an RNG

@choco-green
Copy link

I've encountered the same issue. I solved it in a hacky way by reimplementing SafeEmail.

#[cfg(test)]
mod tests {
    use fake::locales::{self, Data};
    use quickcheck::{Arbitrary, Gen};

    #[derive(Debug, Clone)]
    struct ValidEmailFixture(pub String);

    impl Arbitrary for ValidEmailFixture {
        fn arbitrary(g: &mut Gen) -> Self {
            let username = g
                .choose(locales::EN::NAME_FIRST_NAME)
                .unwrap()
                .to_lowercase();
            let domain = g.choose(&["com", "net", "org"]).unwrap();
            let email = format!("{username}@example.{domain}");
            Self(email)
        }
    }

    #[quickcheck_macros::quickcheck]
    fn valid_emails_are_parsed_successfully(email: ValidEmailFixture) -> bool {
        SubscriberEmail::parse(email.0).is_ok()
    }
}

Not ideal but also not too bad.

Zero to Production's author, Luca Palmieri, posted an excerpt that covers OP's code above if anyone wants to follow along.

An extension for more context if you came here from the same book - This is exactly how fake is implmenting the safeEmail function.

impl<L: Data + Copy> Dummy<SafeEmail<L>> for String {
    fn dummy_with_rng<R: Rng + ?Sized>(c: &SafeEmail<L>, rng: &mut R) -> Self {
        let username: String = FirstName(c.0).fake_with_rng::<&str, _>(rng).to_lowercase();
        let domain = ["com", "net", "org"].choose(rng).unwrap();
        format!("{}@example.{}", username, domain)
    }
}

For more information, there is #265 to read for why rng is no longer a dependency.

Enough Digging, Enjoy coding :3

dead-claudia added a commit to dead-claudia/journald-exporter that referenced this issue Jul 6, 2024
...and switch the 32-bit integer parser to just exhaustive checking.
(More on that later.)

Why move away from QuickCheck?

1. The maintainer appears to have little interest in actually
   maintaining it. BurntSushi/quickcheck#315

2. Its API is incredibly inefficient, especially on failure, and it's
   far too rigid for my needs. For one, I need something looser than
   `Arbitrary: Clone` so things like `std::io::Error` can be generated
   more easily. Also, with larger structures, efficiency will directly
   correlate to faster test runs. Also, I've run into the limitations
   of not being able to access the underlying random number generator
   far too many times to count, as I frequently need to generate random
   values within ranges, among other things.
   - BurntSushi/quickcheck#279
   - BurntSushi/quickcheck#312
   - BurntSushi/quickcheck#320
   - BurntSushi/quickcheck#267

3. It correctly limits generated `Vec` and `String` length, but it
   doesn't similarly enforce limits on test length.

4. There's numerous open issues in it that I've addressed, in some
   cases by better core design. To name a few particularly bad ones:
   - Misuse of runtime bounds in `Duration` generation, `SystemTime`
     generation able to panic for unrelated reasons:
     BurntSushi/quickcheck#321
   - Incorrect generation of `SystemTime`:
     BurntSushi/quickcheck#321
   - Unbounded float shrinkers:
     BurntSushi/quickcheck#295
   - Avoiding pointless debug string building:
     BurntSushi/quickcheck#303
   - Signed shrinker shrinks to the most negative value, leading to
     occasional internal panics:
     BurntSushi/quickcheck#301

There's still some room for improvement, like switching away from a
recursive loop: BurntSushi/quickcheck#285.
But, this is good enough for my use cases right now. And this code
base is structured such that such a change is *much* easier to do.
(It's also considerably simpler.)

As for the integer parser change, I found a way to re-structure it so
I could perform true exhaustive testing on it. Every code path has
every combination of inputs tested, except for memory space as a whole.
This gives me enough confidence that I can ditch the randomized
property checking for it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants