From 374938f34ec4b96922b45fba967cebceea32286f Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Tue, 27 Aug 2024 16:32:58 +0100 Subject: [PATCH] test(vrd): :art: add SeedableRng unit tests --- src/random.rs | 43 +++++++++++++++++++- tests/test_random.rs | 96 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/random.rs b/src/random.rs index 8971811..671b14c 100644 --- a/src/random.rs +++ b/src/random.rs @@ -4,7 +4,7 @@ // See LICENSE-APACHE.md and LICENSE-MIT.md in the repository root for full license information. use crate::MersenneTwisterConfig; -use rand::RngCore; +use rand::{RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; @@ -868,3 +868,44 @@ impl RngCore for Random { Ok(()) } } + +impl SeedableRng for Random { + type Seed = [u8; 16]; // Adjust as necessary + + fn from_seed(seed: Self::Seed) -> Self { + let mut mt = [0u32; 624]; + + // Initialize the state with a non-zero value + mt[0] = u32::from_le_bytes(seed[0..4].try_into().unwrap()); + + for i in 1..624 { + mt[i] = 0x6C078965u32 + .wrapping_mul(mt[i - 1] ^ (mt[i - 1] >> 30)) + .wrapping_add(i as u32); + } + + // Further mix in the seed into the state array + let mut i = 1; + let mut j = 0; + for _ in 0..624.max(16) { + mt[i] = (mt[i] + ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) + .wrapping_mul(0x6C078965u32))) + .wrapping_add(u32::from_le_bytes( + seed[j..j + 4].try_into().unwrap(), + )) + .wrapping_add(j as u32); // Add seed and its index + i += 1; + j += 4; + if i >= 624 { + mt[0] = mt[623]; + i = 1; + } + if j >= 16 { + j = 0; + } + } + + Random { mt, mti: 624 } + } +} diff --git a/tests/test_random.rs b/tests/test_random.rs index 7a98348..5821841 100644 --- a/tests/test_random.rs +++ b/tests/test_random.rs @@ -5,7 +5,7 @@ #[cfg(test)] mod tests { - use rand::RngCore; + use rand::{RngCore, SeedableRng}; use vrd::random::Random; // Initialization tests @@ -618,4 +618,98 @@ mod tests { assert_eq!(rng1.rand(), rng2.rand()); } } + + #[test] + fn test_from_seed_generates_deterministic_rng() { + let seed = [0xABu8; 16]; + let mut rng1 = Random::from_seed(seed); + let mut rng2 = Random::from_seed(seed); + + assert_eq!(rng1.mt, rng2.mt); + assert_eq!(rng1.mti, rng2.mti); + + // Test that both RNGs produce the same sequence of numbers + for _ in 0..10 { + assert_eq!(rng1.next_u32(), rng2.next_u32()); + } + } + + #[test] + fn test_from_different_seeds_generates_different_rngs() { + let seed1 = [0xABu8; 16]; + let seed2 = [0xCDu8; 16]; + let mut rng1 = Random::from_seed(seed1); + let mut rng2 = Random::from_seed(seed2); + + // Print the initial state for debugging + println!("rng1.mt: {:?}", &rng1.mt[0..10]); // print the first 10 values for brevity + println!("rng2.mt: {:?}", &rng2.mt[0..10]); + + // Test that both RNGs produce different sequences of numbers + let mut different = false; + for _ in 0..10 { + let val1 = rng1.next_u32(); + let val2 = rng2.next_u32(); + println!( + "rng1.next_u32(): {}, rng2.next_u32(): {}", + val1, val2 + ); // Debug output + if val1 != val2 { + different = true; + break; + } + } + assert!(different, "RNGs with different seeds should produce different sequences"); + } + + #[test] + fn test_seed_size_incorrect() { + // This test will not compile because the SeedableRng trait ensures the correct seed size. + // It is included here to demonstrate the intention behind testing. + // let incorrect_seed = [0xABu8; 15]; // Should be of size 16 + // let rng = Random::from_seed(incorrect_seed); + // assert!(false, "This should not compile"); + } + + #[test] + fn test_reseeding_changes_rng_state() { + let seed1 = [0xABu8; 16]; + let seed2 = [0xCDu8; 16]; + let mut rng = Random::from_seed(seed1); + + let initial_state = rng.mt; + + // Reseed with a different seed + rng = Random::from_seed(seed2); + let reseeded_state = rng.mt; + + assert_ne!( + initial_state, reseeded_state, + "Reseeding should change the RNG state" + ); + } + + #[test] + fn test_from_seed_with_extreme_values() { + let min_seed = [0x00u8; 16]; + let max_seed = [0xFFu8; 16]; + + let mut rng_min = Random::from_seed(min_seed); + let mut rng_max = Random::from_seed(max_seed); + + // Test that RNGs produce sequences of numbers (no need to compare, just ensure no panics) + for _ in 0..10 { + let val_min = rng_min.next_u32(); + let val_max = rng_max.next_u32(); + + assert!( + val_min != 0xFFFFFFFF, + "RNG with all-zero seed should not produce all ones" + ); + assert!( + val_max != 0, + "RNG with all-ones seed should not produce all zeroes" + ); + } + } }