#[cfg(test)]
pub(crate) mod test_helpers;
use std::{
collections::HashMap,
env::{self, VarError},
ffi::{OsStr, OsString},
};
pub trait Environment {
fn set_var(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>);
fn var(&self, key: impl AsRef<OsStr>) -> Result<String, VarError>;
fn var_os(&self, key: impl AsRef<OsStr>) -> Option<OsString>;
fn remove_var(&mut self, key: impl AsRef<OsStr>);
}
pub struct RealEnvironment;
impl Environment for RealEnvironment {
fn set_var(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) {
env::set_var(key, value)
}
fn var(&self, key: impl AsRef<OsStr>) -> Result<String, VarError> {
env::var(key)
}
fn var_os(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
env::var_os(key)
}
fn remove_var(&mut self, key: impl AsRef<OsStr>) {
env::remove_var(key)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FakeEnvironment {
env_vars: HashMap<OsString, OsString>,
}
impl FakeEnvironment {
pub fn new() -> Self {
FakeEnvironment {
env_vars: HashMap::new(),
}
}
}
impl Environment for FakeEnvironment {
fn set_var(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) {
self.env_vars
.insert(key.as_ref().into(), value.as_ref().into());
}
fn var(&self, key: impl AsRef<OsStr>) -> Result<String, VarError> {
match self.env_vars.get(key.as_ref()) {
Some(val) => match val.to_str() {
Some(valid_utf8) => Ok(valid_utf8.into()),
None => Err(VarError::NotUnicode(val.into())),
},
None => Err(VarError::NotPresent),
}
}
fn var_os(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
self.env_vars.get(key.as_ref()).cloned()
}
fn remove_var(&mut self, key: impl AsRef<OsStr>) {
self.env_vars.remove(key.as_ref());
}
}
#[cfg(test)]
mod tests {
use std::{
env::VarError,
ffi::{OsStr, OsString},
os::unix::ffi::OsStrExt,
};
use crate::{test_helpers::random_upper, Environment, FakeEnvironment, RealEnvironment};
const INVALID_UTF8: [u8; 4] = [0x66, 0x6f, 0x80, 0x6f];
#[test]
fn when_adding_an_environment_variable_then_it_can_be_read() {
fn test(mut env: impl Environment) {
let key = random_upper();
let value = random_upper();
env.set_var(&key, &value);
let result = env.var(&key);
assert_eq!(result.unwrap(), value);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn given_a_nonexistent_env_var_when_getting_the_env_var_with_var_then_it_is_a_not_present_error(
) {
fn test(env: impl Environment) {
let nonexistent_key = random_upper();
let result = env.var(nonexistent_key);
assert_eq!(result.unwrap_err(), VarError::NotPresent);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn when_setting_env_vars_then_multiple_data_types_can_be_used_on_the_same_environment_instance()
{
fn test(mut env: impl Environment) {
env.set_var(&random_upper(), &random_upper());
env.set_var(random_upper(), random_upper());
env.set_var(OsStr::new(&random_upper()), OsStr::new(&random_upper()));
env.set_var(
OsString::from(random_upper()),
OsString::from(random_upper()),
);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn when_using_var_getter_with_an_invalid_utf8_value_then_it_is_a_not_unicode_error() {
fn test(mut env: impl Environment) {
let key = random_upper();
env.set_var(&key, OsStr::from_bytes(&INVALID_UTF8));
let result = env.var(&key);
assert!(matches!(result, Err(VarError::NotUnicode(_))));
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn given_a_nonexistent_env_var_when_getting_the_env_var_with_var_os_then_it_is_none() {
fn test(env: impl Environment) {
let key = random_upper();
let result = env.var_os(key);
assert!(result.is_none());
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn given_an_env_var_with_invalid_utf8_when_getting_the_env_var_with_var_os_then_it_is_some() {
fn test(mut env: impl Environment) {
let key = random_upper();
env.set_var(&key, OsStr::from_bytes(&INVALID_UTF8));
let result = env.var_os(&key);
assert!(result.is_some());
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn given_an_existing_environment_variable_when_setting_the_same_environment_variable_then_the_value_is_overwritten(
) {
fn test(mut env: impl Environment) {
let key = random_upper();
let val_1 = random_upper();
let val_2 = random_upper();
env.set_var(&key, &val_1);
env.set_var(&key, &val_2);
assert_eq!(env.var(&key).unwrap(), val_2);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn given_an_existing_environment_variable_when_removing_the_same_environment_variable_then_the_variable_no_longer_exists(
) {
fn test(mut env: impl Environment) {
let key = random_upper();
let value = random_upper();
env.set_var(&key, &value);
env.remove_var(&key);
assert_eq!(env.var(&key).unwrap_err(), VarError::NotPresent);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
#[test]
fn when_removing_a_nonexistent_environment_variable_then_do_not_panic() {
fn test(mut env: impl Environment) {
let key = random_upper();
env.remove_var(&key);
}
test(RealEnvironment);
test(FakeEnvironment::new());
}
}