From 98c4f2f87aa7de5d8fe89e1aaf1f66675459ec29 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 16:31:19 +0200 Subject: [PATCH 1/9] Add changelog Signed-off-by: Denis Varlakov --- CHANGELOG.md | 20 ++++++++++++++++++++ src/lib.rs | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1712285 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +## v0.1.1 +* Add `DevRng::fork` method that derives a new randomness generator from existing one \ + May be useful when you have several threads/futures/places where you need access the randomness + generation, but you don't want to mess with ownership system. +* Changes format of seed printed to stdout \ + Old format: + ```text + Tests seed: {seed} + ``` + New format: + ```text + RUST_TESTS_SEED={seed} + ``` + New format makes it easier to copy-paste env var when you want to reproduce the tests. +* Add Github Actions + +See [#2](https://github.com/survived/rand_dev/pull/2) + +# v0.1.0 +First release of the library diff --git a/src/lib.rs b/src/lib.rs index 057c924..30068f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,9 @@ impl DevRng { /// Derives another randomness generator from this instance /// /// Uses `self` to generate a seed and constructs a new instance of `DevRng` from the seed. + /// + /// May be useful when you have several threads/futures/places where you need access the + /// randomness generation, but you don't want to mess with ownership system. pub fn fork(&mut self) -> Self { let mut seed = [0u8; 32]; self.fill_bytes(&mut seed); From 62dc788289f1dae197f8f969f644b67c08770ef7 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 16:32:58 +0200 Subject: [PATCH 2/9] Update rand/rand-core to latest versions Signed-off-by: Denis Varlakov --- Cargo.toml | 11 ++++++----- README.md | 4 ++-- examples/simple_usage/Cargo.toml | 4 ++-- examples/simple_usage/src/lib.rs | 2 +- src/lib.rs | 26 ++++++++++++++++---------- tests/tests.rs | 12 ++++++++++-- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 47f8437..395151d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rand_dev" -version = "0.1.1" +version = "0.2.0" license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" description = "Reproducible randomness source for tests" categories = ["development-tools", "development-tools::debugging", "development-tools::testing"] @@ -10,12 +10,13 @@ keywords = ["tests", "reproducibility", "rand"] repository = "https://github.com/survived/rand_dev" [dependencies] -rand_core = { version = "0.6", features = ["std"] } -rand_chacha = "0.3" +rand_core = { version = "0.9", features = ["os_rng"] } +rand_chacha = "0.9" +getrandom = { version = "0.3", default-features = false } hex = "0.4" [dev-dependencies] -rand = "0.8" +rand = "0.9" [features] default = [] diff --git a/README.md b/README.md index 6672905..40e8f97 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ platforms. ## Usage Reproducible source of randomness can be added in one line: -```rust +```rust,no_run use rand::Rng; use rand_dev::DevRng; #[test] fn it_works() { let mut rng = DevRng::new(); - assert!(rng.gen_range(0..=10) < 10); + assert!(rng.random_range(0..=10) < 10); } ``` diff --git a/examples/simple_usage/Cargo.toml b/examples/simple_usage/Cargo.toml index 1eb1c02..85e8af5 100644 --- a/examples/simple_usage/Cargo.toml +++ b/examples/simple_usage/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "simple_usage" version = "0.1.0" -edition = "2021" +edition = "2024" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "0.8" +rand = "0.9" rand_dev = { path = "../../" } diff --git a/examples/simple_usage/src/lib.rs b/examples/simple_usage/src/lib.rs index 15e87c2..5063327 100644 --- a/examples/simple_usage/src/lib.rs +++ b/examples/simple_usage/src/lib.rs @@ -6,6 +6,6 @@ mod tests { #[test] fn it_works() { let mut rng = DevRng::new(); - assert!(rng.gen_range(0..=10) < 10); + assert!(rng.random_range(0..=10) < 10); } } diff --git a/src/lib.rs b/src/lib.rs index 30068f3..0979208 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![doc = include_str!("../README.md")] use rand_chacha::ChaCha8Rng; -use rand_core::{CryptoRng, OsRng, RngCore, SeedableRng}; +use rand_core::{CryptoRng, OsRng, RngCore, SeedableRng, TryRngCore}; /// Reproducible random generator for tests #[derive(Debug, Clone)] @@ -26,7 +26,9 @@ impl DevRng { Err(std::env::VarError::NotUnicode(_)) => { panic!("provided seed is not a valid unicode") } - Err(std::env::VarError::NotPresent) => OsRng.fill_bytes(&mut seed), + Err(std::env::VarError::NotPresent) => OsRng + .try_fill_bytes(&mut seed) + .expect("system randomness unavailable"), } println!("RUST_TESTS_SEED={}", hex::encode(seed)); @@ -69,10 +71,6 @@ impl RngCore for DevRng { fn fill_bytes(&mut self, dest: &mut [u8]) { self.0.fill_bytes(dest) } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.0.try_fill_bytes(dest) - } } impl SeedableRng for DevRng { @@ -86,12 +84,20 @@ impl SeedableRng for DevRng { DevRng(ChaCha8Rng::seed_from_u64(state)) } - fn from_rng(rng: R) -> Result { - ChaCha8Rng::from_rng(rng).map(DevRng) + fn from_rng(rng: &mut impl RngCore) -> Self { + Self(ChaCha8Rng::from_rng(rng)) + } + + fn try_from_rng(rng: &mut R) -> Result { + ChaCha8Rng::try_from_rng(rng).map(Self) + } + + fn from_os_rng() -> Self { + Self(ChaCha8Rng::from_os_rng()) } - fn from_entropy() -> Self { - DevRng(ChaCha8Rng::from_entropy()) + fn try_from_os_rng() -> Result { + ChaCha8Rng::try_from_os_rng().map(Self) } } diff --git a/tests/tests.rs b/tests/tests.rs index 311082b..5ccd0c1 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,13 +5,21 @@ const VAR_NAME: &str = "RUST_TESTS_SEED"; #[test] fn reproducibility() { - std::env::remove_var(VAR_NAME); + // SAFETY: setting/removing env vars is safe in single-threaded programs. Since there's + // only one test, this is safe to do. + unsafe { + std::env::remove_var(VAR_NAME); + } let mut rng1 = DevRng::new(); let mut rng2 = DevRng::new(); assert_ne!(rng1.get_seed(), rng2.get_seed()); - std::env::set_var(VAR_NAME, hex::encode(rng1.get_seed())); + // SAFETY: setting/removing env vars is safe in single-threaded programs. Since there's + // only one test, this is safe to do. + unsafe { + std::env::set_var(VAR_NAME, hex::encode(rng1.get_seed())); + } let mut rng3 = DevRng::new(); assert_eq!(rng1.get_seed(), rng3.get_seed()); From 9dd94ef486c5448f6be84249c8dfcd4caf549dda Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 17:30:00 +0200 Subject: [PATCH 3/9] Update a seed in readme example Old seed doesn't seem to reproduce the same result anymore, most probably PRNG algorithm has changed Signed-off-by: Denis Varlakov --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40e8f97..3e40def 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ test tests::it_works ... FAILED failures: ---- tests::it_works stdout ---- -RUST_TESTS_SEED=cab4ab5c8471fa03691bb86d96c2febeb9b1099a78d164e8addbe7f83d107c78 +RUST_TESTS_SEED=fa48105a3c2ada139e0aa234f235a7af5c766cac4daefca97b57d73915c5b736 thread 'tests::it_works' panicked at 'assertion failed: rng.gen_range(0..=10) < 10', src/lib.rs:9:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace From 6298fdadc727ffb1dab3e77a4d113ac8d5a7be48 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 16:33:36 +0200 Subject: [PATCH 4/9] Update CI workflow Signed-off-by: Denis Varlakov --- .github/workflows/rust.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9bad2fa..1e44717 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,19 +9,23 @@ on: env: CARGO_TERM_COLOR: always CARGO_NET_GIT_FETCH_WITH_CLI: true + RUSTFLAGS: -D warnings jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: "true" - - name: Build - run: cargo build + - name: Check + run: cargo check - name: Run tests run: cargo test + - name: Check example + # note: if ran `cargo test`, it intentionally fails with 1/10 probability + run: (cd examples/simple_usage; cargo check --tests) - name: Check formatting run: cargo fmt --all -- --check - name: Run clippy From 764b37208732fb8076a21384f0dbba4b3ae5a45f Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 16:39:37 +0200 Subject: [PATCH 5/9] Replace `hex` dep with better maintained `const-hex` Signed-off-by: Denis Varlakov --- Cargo.toml | 2 +- src/lib.rs | 7 +++---- tests/tests.rs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 395151d..8b41623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/survived/rand_dev" rand_core = { version = "0.9", features = ["os_rng"] } rand_chacha = "0.9" getrandom = { version = "0.3", default-features = false } -hex = "0.4" +const-hex = { version = "1", default-features = false, features = ["alloc"] } [dev-dependencies] rand = "0.9" diff --git a/src/lib.rs b/src/lib.rs index 0979208..69212f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,9 +20,8 @@ impl DevRng { pub fn new() -> Self { let mut seed = [0u8; 32]; match std::env::var(Self::VAR_NAME) { - Ok(provided_seed) => { - hex::decode_to_slice(provided_seed, &mut seed).expect("provided seed is not valid") - } + Ok(provided_seed) => const_hex::decode_to_slice(provided_seed, &mut seed) + .expect("provided seed is not valid"), Err(std::env::VarError::NotUnicode(_)) => { panic!("provided seed is not a valid unicode") } @@ -30,7 +29,7 @@ impl DevRng { .try_fill_bytes(&mut seed) .expect("system randomness unavailable"), } - println!("RUST_TESTS_SEED={}", hex::encode(seed)); + println!("RUST_TESTS_SEED={}", const_hex::encode(seed)); DevRng(ChaCha8Rng::from_seed(seed)) } diff --git a/tests/tests.rs b/tests/tests.rs index 5ccd0c1..bc3b0f2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -18,7 +18,7 @@ fn reproducibility() { // SAFETY: setting/removing env vars is safe in single-threaded programs. Since there's // only one test, this is safe to do. unsafe { - std::env::set_var(VAR_NAME, hex::encode(rng1.get_seed())); + std::env::set_var(VAR_NAME, const_hex::encode(rng1.get_seed())); } let mut rng3 = DevRng::new(); assert_eq!(rng1.get_seed(), rng3.get_seed()); From 1bd48b08d86d079a7fad8f7acb3d29a6a138e4f7 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 17:28:07 +0200 Subject: [PATCH 6/9] Add an optional feature `rand-v09` that re-exports `rand` crate Signed-off-by: Denis Varlakov --- .github/workflows/rust.yml | 2 ++ Cargo.toml | 3 +++ README.md | 3 +++ examples/simple_usage/Cargo.toml | 3 +-- examples/simple_usage/src/lib.rs | 3 +-- src/lib.rs | 3 +++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1e44717..6d0bdcf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,6 +21,8 @@ jobs: cache-on-failure: "true" - name: Check run: cargo check + - name: Check with rand v0.9 + run: cargo check --features rand-v09 - name: Run tests run: cargo test - name: Check example diff --git a/Cargo.toml b/Cargo.toml index 8b41623..2d7cdff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,12 @@ rand_chacha = "0.9" getrandom = { version = "0.3", default-features = false } const-hex = { version = "1", default-features = false, features = ["alloc"] } +rand = { version = "0.9", optional = true } + [dev-dependencies] rand = "0.9" [features] default = [] +rand-v09 = ["dep:rand"] diff --git a/README.md b/README.md index 3e40def..bd60835 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ $ cargo test [`rand`]: https://docs.rs/rand +## Features +* `rand-v09` _(disabled by default)_ when enabled, library re-exports `rand v0.9` which will be accessible as `rand_dev::rand`. + ## License Licensed under either of diff --git a/examples/simple_usage/Cargo.toml b/examples/simple_usage/Cargo.toml index 85e8af5..e526397 100644 --- a/examples/simple_usage/Cargo.toml +++ b/examples/simple_usage/Cargo.toml @@ -7,5 +7,4 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "0.9" -rand_dev = { path = "../../" } +rand_dev = { path = "../../", features = ["rand-v09"] } diff --git a/examples/simple_usage/src/lib.rs b/examples/simple_usage/src/lib.rs index 5063327..5df7c27 100644 --- a/examples/simple_usage/src/lib.rs +++ b/examples/simple_usage/src/lib.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { - use rand::Rng; - use rand_dev::DevRng; + use rand_dev::{DevRng, rand::Rng}; #[test] fn it_works() { diff --git a/src/lib.rs b/src/lib.rs index 69212f6..ac7c461 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ #![doc = include_str!("../README.md")] +#[cfg(feature = "rand-v09")] +pub use rand; + use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRng, OsRng, RngCore, SeedableRng, TryRngCore}; From 9c8d6f1f955eec4ffff08574ef6badec1d40a10b Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 17:07:05 +0200 Subject: [PATCH 7/9] Parellize CI workflows Signed-off-by: Denis Varlakov --- .github/workflows/rust.yml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6d0bdcf..17eaee7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,7 +12,7 @@ env: RUSTFLAGS: -D warnings jobs: - build: + check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -21,14 +21,46 @@ jobs: cache-on-failure: "true" - name: Check run: cargo check + check-with-rand-v09: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" - name: Check with rand v0.9 run: cargo check --features rand-v09 + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" - name: Run tests run: cargo test + check-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" - name: Check example # note: if ran `cargo test`, it intentionally fails with 1/10 probability run: (cd examples/simple_usage; cargo check --tests) + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - name: Check formatting run: cargo fmt --all -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" - name: Run clippy run: cargo clippy -- -D clippy::all From d273be8400931840918914d2a0e03f4c5a5a783c Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 17:10:45 +0200 Subject: [PATCH 8/9] Print the seed to stderr instead of stdout Signed-off-by: Denis Varlakov --- README.md | 4 ++-- src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bd60835..742ddeb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Having reproducible tests helps debugging problems that have probabilistic nature. This library provides a random numbers generator `DevRng` compatible with [`rand`] crate (it implements `Rng`, `RngCore`, -`SeedableRng` traits). When generator is constructed, its seed is printed to stdout. You can override a +`SeedableRng` traits). When generator is constructed, its seed is printed to stderr. You can override a seed by setting `RUST_TESTS_SEED` env variable. Same seed leads to same randomness generated across all platforms. @@ -24,7 +24,7 @@ fn it_works() { } ``` -Then if test fails, you can observe seed of randomness generator in stdout: +Then if test fails, you can observe seed of randomness generator in captured stderr: ```text $ cargo test Finished test [unoptimized + debuginfo] target(s) in 0.00s diff --git a/src/lib.rs b/src/lib.rs index ac7c461..692348b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ impl DevRng { /// Constructs randomness generator /// /// Reads a seed from env variable `RUST_TESTS_SEED` or generates a random seed if env variable is not set. - /// Prints seed to stdout. + /// Prints seed to stderr. /// /// Panics if `RUST_TESTS_SEED` contains invalid value. #[track_caller] @@ -32,7 +32,7 @@ impl DevRng { .try_fill_bytes(&mut seed) .expect("system randomness unavailable"), } - println!("RUST_TESTS_SEED={}", const_hex::encode(seed)); + eprintln!("RUST_TESTS_SEED={}", const_hex::encode(seed)); DevRng(ChaCha8Rng::from_seed(seed)) } From 6665d5cf7012ece9b82bf00900d2d238b2a0111f Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 29 Aug 2025 17:20:12 +0200 Subject: [PATCH 9/9] Update changelog Signed-off-by: Denis Varlakov --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1712285..989f1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v0.2.0 +* BREAKING: update rand_core dep to latest `v0.9` +* POSSIBLY BREAKING: the seed is now printed to stderr instead of stdout +* Replace dependency on `hex` with better-maintained `const-hex v1` +* Add optional feature `rand-v09`: when it's enabled, library re-exports `rand v0.9` that will be + accessible as `rand_dev::rand` +* Improve CI workflow + +See [#4](https://github.com/survived/rand_dev/pull/4) + ## v0.1.1 * Add `DevRng::fork` method that derives a new randomness generator from existing one \ May be useful when you have several threads/futures/places where you need access the randomness