use std::fs::File;
use std::io::{Write, BufWriter};
use std::path::{Path, PathBuf};
use std::fmt::Display;
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,
env_vars: Vec<String>,
old_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,
old_cwd: std::env::current_dir().map_err(as_string)?,
env_vars: vec![],
};
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 set_env<K: AsRef<str>, V: Display>(&mut self, k: K, v: V) {
let key = k.as_ref();
self.env_vars.push(key.to_string());
std::env::set_var(key, v.to_string());
}
}
impl Drop for Jail {
fn drop(&mut self) {
self.env_vars.iter().for_each(std::env::remove_var);
let _ = std::env::set_current_dir(&self.old_cwd);
}
}