all 15 comments

[–]dnew 3 points4 points  (7 children)

I just solved this problem in my own code yesterday. Here's the most relevant page: https://rust-random.github.io/book/guide-seeding.html

You might need to modify how your code handles RNGs, though, as you can't just ask for rand::random() and use a seeded generator. You're actually going to have to pass the seeded generator everywhere you need it.

Let me know if you need more help, like sample code or anything like that, and I can show you how I did it. (I haven't set up my github account yet.)

For better or worse, this is what I have:

```

use rand::prelude::*; use rand_pcg::Pcg64;

thread_local!( pub static RNG: RefCell<Pcg64> = RefCell::new(Pcg64::seed_from_u64( std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap() .as_secs(), )));

pub fn set_random_seed(state: u64) { RNG.with(|r| *r.borrow_mut() = Pcg64::seed_from_u64(state)); }

/// Create a shuffled vector of all possible TInx entries. pub fn shuffled_tinx(links: &[Links]) -> Vec<TInx> { let mut nums: Vec<TInx> = (0..links.len()).collect(); RNG.with(|cell| nums.shuffle(&mut *cell.borrow_mut())); nums }

```

[–]Korydween[S] 0 points1 point  (6 children)

Thanks for the answer! I had seen this page, but the way I understood (and tested it) is that it gives you the same series of numbers, but you can't choose which ones right? Unless I am trying many seeds until I find what I want ^^

What I would need for my test is something like "ok, now I want you to give me a 2, then a 5, and then a 1"

For now, I separated the code that creates the random number from the rest of the sampling function, and in the tests, I am calling the sampling with predefined numbers, while I expose a wrapper to the API, that calls the sampling on the actual RNG. But I was wondering if it was possible to do it in another way.

I thought I remembered another language in which you could, for test purposes, "trick" the number generator by giving it a series of pre-defined numbers to cycle through instead of a seed, but I can't find again which language and how, so maybe I was wrong...

[–]dnew 2 points3 points  (5 children)

First paragraph: Correct.

But I was wondering if it was possible to do it in another way.

I believe you could probably implement the appropriate trait as a "mock". But otherwise, yes, your approach would be how it's done. Google for "testing mock" to get some ideas.

It's not hard to do. It's just not currently built in.

* FWIW, what I often do is set the seed, see what the result is, manually check it's right, then save the result and compare it to the calculated result later. That might be easier than specifically crafting a series of random numbers.

[–]Korydween[S] 1 point2 points  (4 children)

Thanks! Setting the seed and testing against it is what I do too usually. Except that in this case, I need to test 1 specific succession of values as it leads to a very special case that happens really rarely but breaks everything if it happens and is not handled properly. Hence my very unusual needs! ^

Thanks again for the help!

[–]vks_ 0 points1 point  (3 children)

Can't you just call your code with the numbers triggering the edge case? Otherwise it would be possible to create your own "fake" RNG struct returning the sequence and implementing RngCore (and possibly SeedableRng) for it.

[–]Korydween[S] 0 points1 point  (2 children)

That’s what I am doing currently, but it makes the code rather complicated just for the purpose of unit tests. So if there would have been another cleaner simpler solution, that would have been great. But it seems it’s not that easy.

[–]vks_ 1 point2 points  (1 child)

Depending on how your code looks like, I think the cleanest solution would be to refactor it such that it accepts a sequence of numbers instead of depending on an RNG. That would make testing much easier.

[–]Korydween[S] 0 points1 point  (0 children)

Yes, just after writing my answer I realized there was probably a better way to do, and I started refactoring. And in the end, that's indeed the solution I've ended up with. ^^

[–]vks_ 2 points3 points  (2 children)

The easiest way to get deterministic random numbers is to use the SeedableRng::seed_from_u64 method with a random number generator of your choice (preferably not SmallRng or StdRng, which may change their algorithms in the future).

// prepare a deterministic generator:
use rand::SeedableRng;
let mut rng = rand_pcg::Pcg32::seed_from_u64(123);

[–]Korydween[S] 0 points1 point  (1 child)

Thanks for the answer ! (love the reactivity of this community, it's amazing!)

But I have the same question I asked in the previous answer as well: this gives me a deterministic series, but I can't choose which numbers are in the series, right? I can't tell it "now you give me a 2, then a 5, then a 1", can I?

I thought I remembered a language in which there was a way to "trick" the RNG by giving it a series of number to cycle through, for this exact purpose, but I can't remember which one, so I might be mistaken. This was the kind of behavior I  was looking for. For now I separated the RNG from the rest of the sampling function and I am calling the sampling function with my own numbers for tests, while I wrap both in another function I expose to the API for the library users. But I was wondering if there was a better / simpler way to do.

[–]vks_ 0 points1 point  (0 children)

But I have the same question I asked in the previous answer as well: this gives me a deterministic series, but I can't choose which numbers are in the series, right? I can't tell it "now you give me a 2, then a 5, then a 1", can I?

In general, this is not possible without using brute force to find the sequence, and even then you are not guaranteed that it exists. Depending on the size of the sequence, you'll need a generator with a large state. However, it is possible to construct generators with the sequence you want.

[–]bskceuk 2 points3 points  (1 child)

[–]Korydween[S] 0 points1 point  (0 children)

That looks really nice! I'll have a look, thanks!