use std::env;
use std::io;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::TestResult;
fn parse_seed(var: &str, raw: &str) -> TestResult<u64> {
let trimmed = raw.trim();
let (radix, digits) = match trimmed.strip_prefix("0x") {
Some(rest) => (16, rest),
None => (10, trimmed),
};
let cleaned: String = digits.chars().filter(|c| *c != '_').collect();
u64::from_str_radix(&cleaned, radix).map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"environment variable {var:?} has an invalid value {raw:?}: {err}; \
expected a decimal integer or a 0x-prefixed hex value (e.g. 1_000_000, 0xDEAD_BEEF)"
),
)
.into()
})
}
pub fn seed_from_env_or_time(var: &str) -> TestResult<u64> {
match env::var(var) {
Ok(raw) => parse_seed(var, &raw),
Err(env::VarError::NotPresent) => Ok(time_seed()),
Err(env::VarError::NotUnicode(_)) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("environment variable {var:?} is not valid UTF-8"),
)
.into()),
}
}
fn time_seed() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_seed_accepts_decimal() {
assert_eq!(parse_seed("SEED", "42").expect("decimal must parse"), 42);
}
#[test]
fn parse_seed_accepts_hex_prefix() {
assert_eq!(
parse_seed("SEED", "0xDEAD_BEEF").expect("hex must parse"),
0xDEAD_BEEF
);
}
#[test]
fn parse_seed_accepts_underscore_separators() {
assert_eq!(
parse_seed("SEED", "1_000_000").expect("underscores must parse"),
1_000_000
);
assert_eq!(
parse_seed("SEED", "0xDEAD_BEEF_CAFE").expect("hex with underscores must parse"),
0xDEAD_BEEF_CAFE
);
}
#[test]
fn parse_seed_reports_invalid_value_with_context() {
let err = parse_seed("SEED", "not-a-number").expect_err("garbage must not parse");
let msg = format!("{err}");
assert!(
msg.contains("SEED"),
"var name must be in the message: {msg}"
);
assert!(
msg.contains("not-a-number"),
"raw value must be in the message: {msg}"
);
assert!(
msg.contains("0x"),
"prefix example must be in the message: {msg}"
);
}
#[test]
fn time_seed_returns_recent_nanos() {
let a = time_seed();
let b = time_seed();
assert!(a != 0 || b != 0);
}
#[test]
fn seed_from_env_or_time_falls_back_when_variable_unset() {
let name = "NOPROP_SEED_TESTS_ABSOLUTELY_UNSET_9F3A_2E7B";
let a = seed_from_env_or_time(name).expect("unset var must fall back");
let b = seed_from_env_or_time(name).expect("unset var must fall back");
assert!(a != 0 || b != 0);
}
}