use crate::{EnvError, EnvProvider, Result};
use std::collections::BTreeMap;
use std::sync::{Arc, RwLock};
pub struct SimulatorEnv {
vars: Arc<RwLock<BTreeMap<String, String>>>,
}
impl SimulatorEnv {
#[must_use]
pub fn new() -> Self {
let mut vars = BTreeMap::new();
for (key, value) in std::env::vars() {
vars.insert(key, value);
}
Self::set_simulator_defaults(&mut vars);
Self {
vars: Arc::new(RwLock::new(vars)),
}
}
pub fn set_var(&self, name: &str, value: &str) {
let mut vars = self.vars.write().unwrap();
vars.insert(name.to_string(), value.to_string());
}
pub fn remove_var(&self, name: &str) {
let mut vars = self.vars.write().unwrap();
vars.remove(name);
}
pub fn clear(&self) {
let mut vars = self.vars.write().unwrap();
vars.clear();
}
pub fn reset(&self) {
let mut vars = self.vars.write().unwrap();
vars.clear();
for (key, value) in std::env::vars() {
vars.insert(key, value);
}
drop(vars);
let mut vars = self.vars.write().unwrap();
Self::set_simulator_defaults(&mut vars);
}
fn set_simulator_defaults(vars: &mut BTreeMap<String, String>) {
vars.entry("SIMULATOR_SEED".to_string())
.or_insert_with(|| "12345".to_string());
vars.entry("SIMULATOR_UUID_SEED".to_string())
.or_insert_with(|| "54321".to_string());
vars.entry("SIMULATOR_EPOCH_OFFSET".to_string())
.or_insert_with(|| "0".to_string());
vars.entry("SIMULATOR_STEP_MULTIPLIER".to_string())
.or_insert_with(|| "1".to_string());
vars.entry("SIMULATOR_RUNS".to_string())
.or_insert_with(|| "1".to_string());
vars.entry("SIMULATOR_MAX_PARALLEL".to_string())
.or_insert_with(|| "1".to_string());
vars.entry("SIMULATOR_DURATION".to_string())
.or_insert_with(|| "60".to_string());
vars.entry("DATABASE_URL".to_string())
.or_insert_with(|| "sqlite::memory:".to_string());
vars.entry("DB_HOST".to_string())
.or_insert_with(|| "localhost".to_string());
vars.entry("DB_NAME".to_string())
.or_insert_with(|| "test_db".to_string());
vars.entry("DB_USER".to_string())
.or_insert_with(|| "test_user".to_string());
vars.entry("DB_PASSWORD".to_string())
.or_insert_with(|| "test_password".to_string());
vars.entry("PORT".to_string())
.or_insert_with(|| "8080".to_string());
vars.entry("SSL_PORT".to_string())
.or_insert_with(|| "8443".to_string());
vars.entry("DEBUG_RENDERER".to_string())
.or_insert_with(|| "0".to_string());
vars.entry("TOKIO_CONSOLE".to_string())
.or_insert_with(|| "0".to_string());
log::debug!(
"Set simulator environment defaults: {} variables",
vars.len()
);
}
}
impl Default for SimulatorEnv {
fn default() -> Self {
Self::new()
}
}
impl EnvProvider for SimulatorEnv {
fn var(&self, name: &str) -> Result<String> {
let vars = self.vars.read().unwrap();
vars.get(name)
.cloned()
.ok_or_else(|| EnvError::NotFound(name.to_string()))
}
fn vars(&self) -> BTreeMap<String, String> {
let vars = self.vars.read().unwrap();
vars.clone()
}
}
static PROVIDER: std::sync::LazyLock<SimulatorEnv> = std::sync::LazyLock::new(SimulatorEnv::new);
pub fn var(name: &str) -> Result<String> {
PROVIDER.var(name)
}
#[must_use]
pub fn var_or(name: &str, default: &str) -> String {
PROVIDER.var_or(name, default)
}
pub fn var_parse<T>(name: &str) -> Result<T>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
PROVIDER.var_parse(name)
}
#[must_use]
pub fn var_parse_or<T>(name: &str, default: T) -> T
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
PROVIDER.var_parse_or(name, default)
}
pub fn var_parse_opt<T>(name: &str) -> Result<Option<T>>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
PROVIDER.var_parse_opt(name)
}
#[must_use]
pub fn var_exists(name: &str) -> bool {
PROVIDER.var_exists(name)
}
#[must_use]
pub fn vars() -> BTreeMap<String, String> {
PROVIDER.vars()
}
pub fn set_var(name: &str, value: &str) {
PROVIDER.set_var(name, value);
}
pub fn remove_var(name: &str) {
PROVIDER.remove_var(name);
}
pub fn clear() {
PROVIDER.clear();
}
pub fn reset() {
PROVIDER.reset();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EnvProvider;
use serial_test::serial;
#[test_log::test]
#[serial]
fn test_simulator_env_new_has_defaults() {
let env = SimulatorEnv::new();
assert_eq!(env.var("SIMULATOR_SEED").unwrap(), "12345");
assert_eq!(env.var("SIMULATOR_UUID_SEED").unwrap(), "54321");
assert_eq!(env.var("PORT").unwrap(), "8080");
assert_eq!(env.var("DATABASE_URL").unwrap(), "sqlite::memory:");
}
#[test_log::test]
#[serial]
fn test_simulator_env_set_and_get_var() {
let env = SimulatorEnv::new();
env.set_var("TEST_VAR", "test_value");
assert_eq!(env.var("TEST_VAR").unwrap(), "test_value");
}
#[test_log::test]
#[serial]
fn test_simulator_env_set_var_overwrites() {
let env = SimulatorEnv::new();
env.set_var("TEST_VAR", "first");
env.set_var("TEST_VAR", "second");
assert_eq!(env.var("TEST_VAR").unwrap(), "second");
}
#[test_log::test]
#[serial]
fn test_simulator_env_remove_var() {
let env = SimulatorEnv::new();
env.set_var("TEST_VAR", "test_value");
assert_eq!(env.var("TEST_VAR").unwrap(), "test_value");
env.remove_var("TEST_VAR");
assert!(matches!(
env.var("TEST_VAR"),
Err(EnvError::NotFound(ref name)) if name == "TEST_VAR"
));
}
#[test_log::test]
#[serial]
fn test_simulator_env_clear() {
let env = SimulatorEnv::new();
env.set_var("TEST_VAR", "test_value");
env.clear();
assert!(matches!(
env.var("SIMULATOR_SEED"),
Err(EnvError::NotFound(_))
));
assert!(matches!(env.var("TEST_VAR"), Err(EnvError::NotFound(_))));
}
#[test_log::test]
#[serial]
fn test_simulator_env_reset() {
let env = SimulatorEnv::new();
env.set_var("CUSTOM_VAR", "custom_value");
env.set_var("PORT", "9999");
env.reset();
assert!(matches!(env.var("CUSTOM_VAR"), Err(EnvError::NotFound(_))));
assert_eq!(env.var("PORT").unwrap(), "8080");
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_or_with_existing() {
let env = SimulatorEnv::new();
env.set_var("TEST_VAR", "actual_value");
assert_eq!(env.var_or("TEST_VAR", "default"), "actual_value");
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_or_with_missing() {
let env = SimulatorEnv::new();
assert_eq!(env.var_or("MISSING_VAR", "default_value"), "default_value");
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_success() {
let env = SimulatorEnv::new();
env.set_var("NUMBER", "42");
let result: i32 = env.var_parse("NUMBER").unwrap();
assert_eq!(result, 42);
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_error() {
let env = SimulatorEnv::new();
env.set_var("NOT_A_NUMBER", "abc");
let result: Result<i32> = env.var_parse("NOT_A_NUMBER");
assert!(matches!(result, Err(EnvError::ParseError(_, _))));
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_not_found() {
let env = SimulatorEnv::new();
let result: Result<i32> = env.var_parse("MISSING");
assert!(matches!(result, Err(EnvError::NotFound(_))));
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_or_with_valid() {
let env = SimulatorEnv::new();
env.set_var("NUMBER", "100");
let result: i32 = env.var_parse_or("NUMBER", 42);
assert_eq!(result, 100);
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_or_with_invalid() {
let env = SimulatorEnv::new();
env.set_var("NOT_A_NUMBER", "xyz");
let result: i32 = env.var_parse_or("NOT_A_NUMBER", 42);
assert_eq!(result, 42);
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_or_with_missing() {
let env = SimulatorEnv::new();
let result: i32 = env.var_parse_or("MISSING", 42);
assert_eq!(result, 42);
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_opt_some() {
let env = SimulatorEnv::new();
env.set_var("NUMBER", "123");
let result: Option<i32> = env.var_parse_opt("NUMBER").unwrap();
assert_eq!(result, Some(123));
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_opt_none() {
let env = SimulatorEnv::new();
let result: Option<i32> = env.var_parse_opt("MISSING").unwrap();
assert_eq!(result, None);
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_parse_opt_parse_error() {
let env = SimulatorEnv::new();
env.set_var("NOT_A_NUMBER", "not_an_int");
let result: Result<Option<i32>> = env.var_parse_opt("NOT_A_NUMBER");
assert!(matches!(result, Err(EnvError::ParseError(_, _))));
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_exists_true() {
let env = SimulatorEnv::new();
env.set_var("EXISTS", "yes");
assert!(env.var_exists("EXISTS"));
}
#[test_log::test]
#[serial]
fn test_simulator_env_var_exists_false() {
let env = SimulatorEnv::new();
assert!(!env.var_exists("DOES_NOT_EXIST"));
}
#[test_log::test]
#[serial]
fn test_simulator_env_vars() {
let env = SimulatorEnv::new();
env.clear();
env.set_var("VAR1", "value1");
env.set_var("VAR2", "value2");
let vars = env.vars();
assert_eq!(vars.get("VAR1").map(String::as_str), Some("value1"));
assert_eq!(vars.get("VAR2").map(String::as_str), Some("value2"));
assert_eq!(vars.len(), 2);
}
#[test_log::test]
#[serial]
fn test_simulator_env_default_trait() {
let env1 = SimulatorEnv::default();
let env2 = SimulatorEnv::new();
assert_eq!(env1.var("PORT").unwrap(), env2.var("PORT").unwrap());
}
#[test_log::test]
#[serial]
fn test_global_var() {
remove_var("GLOBAL_TEST");
set_var("GLOBAL_TEST", "global_value");
assert_eq!(var("GLOBAL_TEST").unwrap(), "global_value");
remove_var("GLOBAL_TEST");
}
#[test_log::test]
#[serial]
fn test_global_var_or() {
remove_var("MISSING_GLOBAL");
assert_eq!(var_or("MISSING_GLOBAL", "fallback"), "fallback");
}
#[test_log::test]
#[serial]
fn test_global_var_parse() {
set_var("GLOBAL_NUMBER", "777");
let result: i32 = var_parse("GLOBAL_NUMBER").unwrap();
assert_eq!(result, 777);
remove_var("GLOBAL_NUMBER");
}
#[test_log::test]
#[serial]
fn test_global_var_parse_or() {
remove_var("MISSING_NUMBER");
let result: i32 = var_parse_or("MISSING_NUMBER", 999);
assert_eq!(result, 999);
}
#[test_log::test]
#[serial]
fn test_global_var_parse_opt() {
set_var("OPTIONAL_NUMBER", "555");
let result: Option<i32> = var_parse_opt("OPTIONAL_NUMBER").unwrap();
assert_eq!(result, Some(555));
remove_var("OPTIONAL_NUMBER");
}
#[test_log::test]
#[serial]
fn test_global_var_exists() {
remove_var("EXISTS_GLOBAL");
assert!(!var_exists("EXISTS_GLOBAL"));
set_var("EXISTS_GLOBAL", "yes");
assert!(var_exists("EXISTS_GLOBAL"));
remove_var("EXISTS_GLOBAL");
assert!(!var_exists("EXISTS_GLOBAL"));
}
#[test_log::test]
#[serial]
fn test_global_vars() {
clear();
set_var("VARS_TEST1", "val1");
set_var("VARS_TEST2", "val2");
let all_vars = vars();
assert!(all_vars.contains_key("VARS_TEST1"));
assert!(all_vars.contains_key("VARS_TEST2"));
reset();
}
#[test_log::test]
#[serial]
fn test_global_clear() {
set_var("TO_BE_CLEARED", "value");
clear();
assert!(!var_exists("TO_BE_CLEARED"));
reset(); }
#[test_log::test]
#[serial]
fn test_global_reset() {
set_var("TO_BE_RESET", "custom");
reset();
assert!(!var_exists("TO_BE_RESET"));
assert!(var_exists("PORT"));
}
#[test_log::test]
#[serial]
fn test_simulator_defaults_completeness() {
let env = SimulatorEnv::new();
assert!(env.var_exists("SIMULATOR_SEED"));
assert!(env.var_exists("SIMULATOR_UUID_SEED"));
assert!(env.var_exists("SIMULATOR_EPOCH_OFFSET"));
assert!(env.var_exists("SIMULATOR_STEP_MULTIPLIER"));
assert!(env.var_exists("SIMULATOR_RUNS"));
assert!(env.var_exists("SIMULATOR_MAX_PARALLEL"));
assert!(env.var_exists("SIMULATOR_DURATION"));
assert!(env.var_exists("DATABASE_URL"));
assert!(env.var_exists("DB_HOST"));
assert!(env.var_exists("DB_NAME"));
assert!(env.var_exists("DB_USER"));
assert!(env.var_exists("DB_PASSWORD"));
assert!(env.var_exists("PORT"));
assert!(env.var_exists("SSL_PORT"));
assert!(env.var_exists("DEBUG_RENDERER"));
assert!(env.var_exists("TOKIO_CONSOLE"));
}
#[test_log::test]
#[serial]
fn test_parse_various_types() {
let env = SimulatorEnv::new();
env.set_var("BOOL_TRUE", "true");
env.set_var("BOOL_FALSE", "false");
assert!(env.var_parse::<bool>("BOOL_TRUE").unwrap());
assert!(!env.var_parse::<bool>("BOOL_FALSE").unwrap());
env.set_var("FLOAT", "2.5");
assert!((env.var_parse::<f64>("FLOAT").unwrap() - 2.5).abs() < 0.001);
env.set_var("UNSIGNED", "42");
assert_eq!(env.var_parse::<u32>("UNSIGNED").unwrap(), 42);
}
#[test_log::test]
#[serial]
fn test_simulator_env_arc_sharing() {
let env1 = SimulatorEnv::new();
let vars_clone = env1.vars.clone();
let env2 = SimulatorEnv { vars: vars_clone };
env1.set_var("SHARED_VAR", "from_env1");
assert_eq!(env2.var("SHARED_VAR").unwrap(), "from_env1");
env2.set_var("SHARED_VAR", "from_env2");
assert_eq!(env1.var("SHARED_VAR").unwrap(), "from_env2");
}
#[test_log::test]
#[serial]
fn test_remove_nonexistent_var() {
let env = SimulatorEnv::new();
env.remove_var("DEFINITELY_DOES_NOT_EXIST_123456789");
assert_eq!(env.var("PORT").unwrap(), "8080");
}
#[test_log::test]
#[serial]
fn test_empty_string_value() {
let env = SimulatorEnv::new();
env.set_var("EMPTY_VAR", "");
assert_eq!(env.var("EMPTY_VAR").unwrap(), "");
assert!(env.var_exists("EMPTY_VAR"));
let s: String = env.var_parse("EMPTY_VAR").unwrap();
assert_eq!(s, "");
let result: Result<i32> = env.var_parse("EMPTY_VAR");
assert!(matches!(result, Err(EnvError::ParseError(_, _))));
}
#[test_log::test]
#[serial]
fn test_concurrent_read_write() {
use std::sync::Arc;
use std::thread;
let env = SimulatorEnv::new();
let vars = Arc::clone(&env.vars);
let mut handles = vec![];
for i in 0..4 {
let vars_clone = vars.clone();
let handle = thread::spawn(move || {
let env = SimulatorEnv { vars: vars_clone };
for _ in 0..100 {
let _ = env.var("PORT");
let _ = env.vars();
let _ = env.var_exists("SIMULATOR_SEED");
}
i
});
handles.push(handle);
}
let vars_clone = vars.clone();
let writer = thread::spawn(move || {
let env = SimulatorEnv { vars: vars_clone };
for j in 0..100 {
env.set_var("CONCURRENT_VAR", &format!("value_{j}"));
}
});
for handle in handles {
handle.join().expect("Reader thread panicked");
}
writer.join().expect("Writer thread panicked");
let env = SimulatorEnv { vars };
assert!(env.var("CONCURRENT_VAR").is_ok());
}
#[test_log::test]
#[serial]
fn test_real_env_vars_preserved_on_new() {
unsafe {
std::env::set_var("REAL_TEST_VAR_FOR_SIMULATOR", "real_value");
}
let env = SimulatorEnv::new();
assert_eq!(
env.var("REAL_TEST_VAR_FOR_SIMULATOR").unwrap(),
"real_value"
);
unsafe {
std::env::remove_var("REAL_TEST_VAR_FOR_SIMULATOR");
}
}
#[test_log::test]
#[serial]
fn test_reset_reloads_real_env_vars() {
unsafe {
std::env::set_var("REAL_VAR_FOR_RESET_TEST", "original");
}
let env = SimulatorEnv::new();
assert_eq!(env.var("REAL_VAR_FOR_RESET_TEST").unwrap(), "original");
env.clear();
assert!(env.var("REAL_VAR_FOR_RESET_TEST").is_err());
unsafe {
std::env::set_var("REAL_VAR_FOR_RESET_TEST", "changed");
}
env.reset();
assert_eq!(env.var("REAL_VAR_FOR_RESET_TEST").unwrap(), "changed");
unsafe {
std::env::remove_var("REAL_VAR_FOR_RESET_TEST");
}
}
}