use std::path::PathBuf;
use derive_builder::Builder;
use crate::config::DocumentConfig;
#[derive(Debug, PartialEq, Eq, Builder)]
pub struct Context {
pub work_directory: PathBuf,
pub temp_directory: PathBuf,
pub file: PathBuf,
#[builder(default)]
pub config: DocumentConfig,
}
#[cfg(test)]
impl Context {
pub fn new_for_test() -> Self {
Self::new_for_test_with_config(Default::default())
}
#[cfg(test)]
pub fn new_for_test_with_config(config: DocumentConfig) -> Self {
Self {
work_directory: test::create_testing_directory(),
temp_directory: test::create_testing_directory(),
file: PathBuf::from("test.md"),
config,
}
}
}
#[cfg(test)]
impl Drop for Context {
fn drop(&mut self) {
use std::time::Duration;
static MAX_DELETE_ATTEMPTS: i32 = 20;
static WAIT_AFTER_FAIL_TIME: Duration = Duration::from_millis(100);
static MAX_WAIT_AFTER_FAIL_TIME: Duration = Duration::from_secs(1);
for (name, directory) in [
("temp", &self.temp_directory),
("work", &self.work_directory),
] {
if !directory
.to_string_lossy()
.contains(test::TESTING_PATH_PREFIX)
{
continue;
}
let mut wait_time = WAIT_AFTER_FAIL_TIME;
for attempt in 1..(MAX_DELETE_ATTEMPTS + 1) {
match std::fs::remove_dir_all(directory) {
Err(err) => {
if attempt == MAX_DELETE_ATTEMPTS {
panic!(
"failed to clean up testing {name} directory recursively in \"{}\": {}",
directory.display(),
err
)
} else {
tracing::warn!(
attempt,
?err,
"failed to clean up {name} directory and will try again shortly"
)
}
std::thread::sleep(wait_time);
wait_time = std::cmp::min(wait_time * 2, MAX_WAIT_AFTER_FAIL_TIME);
}
Ok(_) => break,
}
}
}
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use tempfile::Builder;
use tempfile::tempdir;
use super::Context;
pub(super) const TESTING_PATH_PREFIX: &str = "scrut-selftest-temp-directory";
pub(super) fn create_testing_directory() -> PathBuf {
Builder::new()
.prefix(TESTING_PATH_PREFIX)
.tempdir()
.expect("create testing working directory for context")
.keep()
}
#[test]
fn test_testing_context_creates_directories() {
let context = Context::new_for_test();
assert!(context.temp_directory.exists(), "temp directory is created");
assert!(
context
.temp_directory
.to_string_lossy()
.contains(TESTING_PATH_PREFIX),
"temp directory has identifying prefix"
);
assert!(context.work_directory.exists(), "work directory is created");
assert!(
context
.work_directory
.to_string_lossy()
.contains(TESTING_PATH_PREFIX),
"work directory has identifying prefix"
);
}
#[test]
fn test_testing_context_drop_removes_directories_it_created() {
let context = Context::new_for_test();
let temp_directory = context.temp_directory.clone();
let work_directory = context.work_directory.clone();
assert!(temp_directory.exists(), "temp directory is created");
assert!(work_directory.exists(), "work directory is created");
drop(context);
assert!(!temp_directory.exists(), "temp directory is cleaned up");
assert!(!work_directory.exists(), "work directory is cleaned up");
}
#[test]
fn test_testing_context_drop_does_not_remove_directories_it_did_not_create() {
let temp_directory = tempdir().expect("crate temp directory");
let work_directory = tempdir().expect("crate work directory");
let context = Context {
temp_directory: temp_directory.path().to_path_buf(),
work_directory: work_directory.path().to_path_buf(),
file: PathBuf::from("test.md"),
config: Default::default(),
};
assert!(temp_directory.path().exists(), "temp directory is created");
assert!(work_directory.path().exists(), "work directory is created");
drop(context);
assert!(
temp_directory.path().exists(),
"temp directory is NOT cleaned up"
);
assert!(
work_directory.path().exists(),
"work directory is NOT cleaned up"
);
}
}