use std::collections::HashMap;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
pub fn set_var<K, V>(key: K, value: V) -> EnvGuard
where
K: Into<String>,
V: Into<String>,
{
let _lock = ENV_LOCK.lock().unwrap();
let key = key.into();
let prev = std::env::var(&key).ok();
unsafe { std::env::set_var(&key, value.into()); }
EnvGuard { key, prev }
}
pub fn remove_var<K>(key: K) -> EnvGuard
where
K: Into<String>,
{
let _lock = ENV_LOCK.lock().unwrap();
let key = key.into();
let prev = std::env::var(&key).ok();
unsafe { std::env::remove_var(&key); }
EnvGuard { key, prev }
}
pub fn set_vars<K, V, I>(pairs: I) -> MultiEnvGuard
where
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
let _lock = ENV_LOCK.lock().unwrap();
let mut prev = HashMap::new();
for (k, v) in pairs {
let key = k.into();
let old = std::env::var(&key).ok();
unsafe { std::env::set_var(&key, v.into()); }
prev.insert(key, old);
}
MultiEnvGuard { prev }
}
pub struct EnvGuard {
key: String,
prev: Option<String>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
let _lock = ENV_LOCK.lock().unwrap();
match &self.prev {
Some(val) => unsafe { std::env::set_var(&self.key, val); },
None => unsafe { std::env::remove_var(&self.key); },
}
}
}
pub struct MultiEnvGuard {
prev: HashMap<String, Option<String>>,
}
impl Drop for MultiEnvGuard {
fn drop(&mut self) {
let _lock = ENV_LOCK.lock().unwrap();
for (key, prev) in std::mem::take(&mut self.prev) {
match prev {
Some(val) => unsafe { std::env::set_var(&key, val); },
None => unsafe { std::env::remove_var(&key); },
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn remove_var_safe(key: &str) {
unsafe { std::env::remove_var(key); }
}
fn set_var_safe(key: &str, val: &str) {
unsafe { std::env::set_var(key, val); }
}
#[test]
fn set_var_sets_and_restores() {
let key = "RVTEST_ENV_SET_TEST";
remove_var_safe(key);
{
let _g = set_var(key, "hello");
assert_eq!(std::env::var(key).unwrap(), "hello");
}
assert!(std::env::var(key).is_err());
}
#[test]
fn set_var_restores_previous_value() {
let key = "RVTEST_ENV_RESTORE_TEST";
set_var_safe(key, "original");
{
let _g = set_var(key, "modified");
assert_eq!(std::env::var(key).unwrap(), "modified");
}
assert_eq!(std::env::var(key).unwrap(), "original");
remove_var_safe(key);
}
#[test]
fn remove_var_removes_and_restores() {
let key = "RVTEST_ENV_REMOVE_TEST";
set_var_safe(key, "value");
{
let _g = remove_var(key);
assert!(std::env::var(key).is_err());
}
assert_eq!(std::env::var(key).unwrap(), "value");
remove_var_safe(key);
}
#[test]
fn set_vars_sets_all() {
let keys = [("RVTEST_MV_1", "a"), ("RVTEST_MV_2", "b")];
remove_var_safe("RVTEST_MV_1");
remove_var_safe("RVTEST_MV_2");
{
let _g = set_vars(keys);
assert_eq!(std::env::var("RVTEST_MV_1").unwrap(), "a");
assert_eq!(std::env::var("RVTEST_MV_2").unwrap(), "b");
}
assert!(std::env::var("RVTEST_MV_1").is_err());
assert!(std::env::var("RVTEST_MV_2").is_err());
}
#[test]
fn guard_dropped_on_panic() {
let key = "RVTEST_ENV_PANIC_TEST";
remove_var_safe(key);
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _g = set_var(key, "panic_val");
assert_eq!(std::env::var(key).unwrap(), "panic_val");
panic!("intentional panic");
}));
assert!(r.is_err());
assert!(std::env::var(key).is_err(), "should be restored after panic");
}
}