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

Ability to generate values with all optional members present #128

Closed
jayvdb opened this issue Mar 22, 2023 · 4 comments · Fixed by #135
Closed

Ability to generate values with all optional members present #128

jayvdb opened this issue Mar 22, 2023 · 4 comments · Fixed by #135

Comments

@jayvdb
Copy link
Contributor

jayvdb commented Mar 22, 2023

Often I would like a fake generated object with all of the optional data fields filled in, and any arrays to have at least one item. This is useful when the objective of the test requires some deeply nested member to exist, and I dont mind the perf hit of all the other optionals also being generated. Currently I need to manually call fake for each level of the nested structure to remove any None or empty arrays which may exist.

I tried using a constant RNG which only returns 1 using StepRng, but it still used None all the time, and arrays were always empty.

let mut constant_rng = rand::rngs::mock::StepRng::new(1, 0);
let foo: MyStruct = Faker.fake_with_rng(&mut constant_rng);

There are also times I would like a fake object with none of the optional data fields filled in, so that may also be useful to consider this at the same time, but I find that this isnt critical, and when it is, setting a part of the data structure to None is easy, and self-explanatory in the code.

While not the same #54 by @tujh2 seems to have similar objectives, just in my case I am looking for 100% or 0% as the ratios.

@jayvdb
Copy link
Contributor Author

jayvdb commented Mar 23, 2023

With a little fiddling, I found that I can get StepRng to produce always true, and thus always Some instead of None.

use fake::{Fake, Faker};
let MAGIC_NUMBER = 1u64 << 31;
let mut rng = rand::rngs::mock::StepRng::new(MAGIC_NUMBER, 0);
for _ in 0..1000 {
    let foo: Option<f64> = Faker.fake_with_rng(&mut rng);
    assert!(foo.is_some());
}

The above comes at the cost of randomness - in the above the f64 is always 2.1758023649454117e-7.
However there is a reasonable range where this holds true - see rust-random/rand#1303 . So a bit of randomness can be injected by limiting allowed values to that range.

@jayvdb
Copy link
Contributor Author

jayvdb commented Apr 4, 2023

A more complete implementation of an RNG which will always be true for booleans is:

use rand::{rngs::mock::StepRng, Error, RngCore};
use rand_core::impls;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)]
pub struct AlwaysTrueRng {
    inner: StepRng,
    increment: u64,
}

impl AlwaysTrueRng {
    /// Currently implemented using StepRng's `true` range.
    /// See https://github.com/rust-random/rand/pull/1304
    fn new(initial: u64, increment: u64) -> Self {
        AlwaysTrueRng {
            inner: StepRng::new(initial, increment),
            increment,
        }
    }
}

impl Default for AlwaysTrueRng {
    fn default() -> Self {
        AlwaysTrueRng::new(1 << 31, (1 << 31) + 1)
    }
}

impl RngCore for AlwaysTrueRng {
    #[inline]
    fn next_u32(&mut self) -> u32 {
        self.next_u64() as u32
    }

    #[inline]
    fn next_u64(&mut self) -> u64 {
        let mut rv = self.inner.next_u64();
        if rv & (1 << 31) == 0 {
            self.inner = StepRng::new(rv | 1 << 31, self.increment);
            rv = self.inner.next_u64();
        }
        rv
    }

    #[inline]
    fn fill_bytes(&mut self, dest: &mut [u8]) {
        impls::fill_bytes_via_next(self, dest);
    }

    #[inline]
    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
        self.fill_bytes(dest);
        Ok(())
    }
}

Now the only extra detail needed is to adjust array/Vec/etc fakers to use bool to fill the first member, so that they will never be empty when AlwaysTrueRng is being used.

@cksac
Copy link
Owner

cksac commented May 3, 2023

released 2.6.0, now you can use below

use fake::{Dummy, Fake, Faker, Opt, Optional};

#[derive(Debug, Dummy)]
pub struct Order {
    #[dummy(faker = "0..200")]
    pub a: Option<u64>,

    #[dummy(faker = "Opt(0..200, 100)", from = "Optional<u64>")]
    pub always_some: Option<u64>,

    #[dummy(expr = "Some((0..200).fake())")]
    pub always_some_v2: Option<u64>,

    #[dummy(faker = "Opt(0..200, 0)", from = "Optional<u64>")]
    pub always_none: Option<u64>,

    #[dummy(expr="None")]
    pub always_none_v2: Option<u64>,

    #[dummy(faker = "0..200")]
    pub c: Option<Option<u64>>,

    #[dummy(expr = "Opt(Opt(0..200, 50), 50).fake::<Optional<Optional<u64>>>().0.map(|v| v.0)")]
    pub d: Option<Option<u64>>,
}

fn main() {
    let opt: Optional<usize> = Opt(0..100, 100).fake();
    println!("{:?}", opt);

    let opt: Optional<Optional<usize>> = Opt(Opt(0..200, 50), 20).fake();
    println!("{:?}", opt.0.map(|v| v.0));

    let opt: Option<usize> = Opt(0..100, 100).fake::<Optional<usize>>().into();
    println!("{:?}", opt);

    let o: Order = Faker.fake();
    println!("{:?}", o);
}

@cksac cksac closed this as completed May 3, 2023
@jayvdb
Copy link
Contributor Author

jayvdb commented May 3, 2023

Using #[dummy(..)] does not solve this, as it only allows one fake configuration for each struct member. We want to be able to toggle between (1) normal completely random instances and (2, requested) the ability to ensure that all optional members are filled in so that we have instances with maximum depth, but again random. The solution in #135 does by supporting a class of rngs which return true when asked for a boolean, with the only cost being that the rng can only use half of the random distribution space.

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

Successfully merging a pull request may close this issue.

2 participants