use std::fs::File;
use std::io::{Write, BufWriter};
use std::path::{Path, PathBuf};
use std::fmt::Display;
use std::ffi::{OsStr, OsString};
use std::collections::HashMap;
use tempfile::TempDir;
use parking_lot::Mutex;
use crate::error::Result;
#[cfg_attr(nightly, doc(cfg(feature = "test")))]
pub struct Jail {
_directory: TempDir,
canonical_dir: PathBuf,
saved_env_vars: HashMap<OsString, Option<OsString>>,
saved_cwd: PathBuf,
}
fn as_string<S: Display>(s: S) -> String { s.to_string() }
static LOCK: Mutex<()> = parking_lot::const_mutex(());
impl Jail {
#[track_caller]
pub fn expect_with<F: FnOnce(&mut Jail) -> Result<()>>(f: F) {
if let Err(e) = Jail::try_with(f) {
panic!("jail failed: {}", e)
}
}
#[track_caller]
pub fn try_with<F: FnOnce(&mut Jail) -> Result<()>>(f: F) -> Result<()> {
let _lock = LOCK.lock();
let directory = TempDir::new().map_err(as_string)?;
let mut jail = Jail {
canonical_dir: directory.path().canonicalize().map_err(as_string)?,
_directory: directory,
saved_cwd: std::env::current_dir().map_err(as_string)?,
saved_env_vars: HashMap::new(),
};
std::env::set_current_dir(jail.directory()).map_err(as_string)?;
f(&mut jail)
}
pub fn directory(&self) -> &Path {
&self.canonical_dir
}
pub fn create_file<P: AsRef<Path>>(&self, path: P, contents: &str) -> Result<File> {
let path = path.as_ref();
if !path.is_relative() {
return Err("Jail::create_file(): file path is absolute".to_string().into());
}
let file = File::create(self.directory().join(path)).map_err(as_string)?;
let mut writer = BufWriter::new(file);
writer.write_all(contents.as_bytes()).map_err(as_string)?;
Ok(writer.into_inner().map_err(as_string)?)
}
pub fn clear_env(&mut self) {
for (key, val) in std::env::vars_os() {
std::env::remove_var(&key);
if !self.saved_env_vars.contains_key(&key) {
self.saved_env_vars.insert(key, Some(val));
}
}
}
pub fn set_env<K: AsRef<str>, V: Display>(&mut self, k: K, v: V) {
let key = k.as_ref();
if !self.saved_env_vars.contains_key(OsStr::new(key)) {
self.saved_env_vars.insert(key.into(), std::env::var_os(key));
}
std::env::set_var(key, v.to_string());
}
}
impl Drop for Jail {
fn drop(&mut self) {
for (key, value) in self.saved_env_vars.iter() {
match value {
Some(val) => std::env::set_var(key, val),
None => std::env::remove_var(key)
}
}
let _ = std::env::set_current_dir(&self.saved_cwd);
}
}