use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct TempDir {
module_name: String,
name: String,
enable_short_path: bool,
short_path_max_len: usize,
}
const TEMP_DIR_ROOT_NAME: &str = "mithril_test";
const DEFAULT_SHORT_PATH_MAX_LEN: usize = 90;
impl TempDir {
pub fn new<T: Into<String>>(module: T, name: T) -> Self {
Self {
module_name: module.into(),
name: name.into(),
enable_short_path: false,
short_path_max_len: DEFAULT_SHORT_PATH_MAX_LEN,
}
}
pub fn generate_shorter_path(mut self) -> Self {
self.enable_short_path = true;
self
}
pub fn set_short_path_max_len(mut self, max_len: usize) -> Self {
self.short_path_max_len = max_len;
self
}
pub fn build_path(&self) -> PathBuf {
const SHA_LENGTH: usize = 10;
let base_dir = std::env::temp_dir().join(TEMP_DIR_ROOT_NAME);
if self.enable_short_path {
let mut hasher = Sha256::new();
hasher.update(&self.module_name);
hasher.update(&self.name);
let sha = hex::encode(hasher.finalize());
let short_sha = &sha[0..SHA_LENGTH];
let max_path_len =
self.short_path_max_len - SHA_LENGTH - 2 - base_dir.to_string_lossy().len();
let max_len = self.name.len().min(max_path_len);
base_dir.join([&self.name[0..max_len], "_", short_sha].concat())
} else {
base_dir.join(&self.module_name).join(&self.name)
}
}
pub fn build(&self) -> PathBuf {
let path = self.build_path();
self.create_dir(&path);
path
}
pub fn create<T: Into<String>>(module: T, name: T) -> PathBuf {
Self::new(module, name).build()
}
pub fn create_with_short_path<T: Into<String>>(module: T, name: T) -> PathBuf {
Self::new(module, name).generate_shorter_path().build()
}
fn create_dir(&self, path: &Path) {
if path.exists() {
std::fs::remove_dir_all(path)
.unwrap_or_else(|e| panic!("Could not remove dir {path:?}: {e}"));
}
std::fs::create_dir_all(path)
.unwrap_or_else(|e| panic!("Could not create dir {path:?}: {e}"));
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{fs, io::Write, ops::Not};
#[test]
fn non_short_path_are_in_a_mithril_test_slash_module_folder_structure() {
let path = TempDir::new("temp_dir", "basic").build();
assert_eq!(
Some(
std::env::temp_dir()
.join(TEMP_DIR_ROOT_NAME)
.join("temp_dir")
.as_path()
),
path.parent()
);
assert!(path.exists());
}
#[test]
fn short_path_are_in_a_mithril_test_folder_structure() {
let path = TempDir::new("temp_dir", "basic_short_path")
.generate_shorter_path()
.build();
assert_eq!(
Some(std::env::temp_dir().join(TEMP_DIR_ROOT_NAME).as_path()),
path.parent()
);
assert!(path.exists());
}
#[test]
fn shorter_path_have_a_length_lower_than_90_chars_even_when_given_module_longer_than_that() {
let path = TempDir::new(
"module_longer_than_a_string_of_90_characters_so_this_test_can_fail_if_the_builder_is_a_bad_builder_that_do_nothing",
"name",
)
.generate_shorter_path()
.set_short_path_max_len(90)
.build_path();
let path_len = path.to_string_lossy().len();
assert!(
path_len <= 90,
"path with `short` option enabled was longer than 90 characters:\n\
path_len: `{path_len}`\n\
path: `{}`",
path.display()
);
}
#[test]
fn shorter_path_have_a_length_lower_than_90_chars_even_when_given_name_longer_than_that() {
let path = TempDir::new(
"mod",
"name_longer_than_a_string_of_90_characters_so_this_test_can_fail_if_the_builder_is_a_bad_builder_that_do_nothing",
)
.generate_shorter_path()
.set_short_path_max_len(90)
.build_path();
let path_len = path.to_string_lossy().len();
assert!(
path_len <= 90,
"path with `short` option enabled was longer than 90 characters:\n\
path_len: `{path_len}`\n\
path: `{}`",
path.display()
);
}
#[test]
fn same_name_but_two_different_module_generate_different_path() {
let path1 = TempDir::new("module_a", "test").build_path();
let path2 = TempDir::new("module_b", "test").build_path();
assert_ne!(path1, path2);
}
#[test]
fn same_name_but_two_different_module_generate_different_path_even_with_short_path_enabled() {
let path1 = TempDir::new("module_a", "test")
.generate_shorter_path()
.build_path();
let path2 = TempDir::new("module_b", "test")
.generate_shorter_path()
.build_path();
assert_ne!(path1, path2);
}
#[test]
fn creating_temp_dir_remove_existing_content() {
let builder = TempDir::new("temp_dir", "creating_temp_dir_remove_existing_content");
let (existing_dir, existing_file) = {
let path = builder.build_path();
(path.join("existing_subdir"), path.join("existing_file.md"))
};
fs::create_dir_all(&existing_dir).unwrap();
let mut file = fs::File::create(&existing_file).unwrap();
file.write_all(b"file content").unwrap();
builder.build();
assert!(
existing_file.exists().not(),
"should have cleaned up existing files"
);
assert!(
existing_dir.exists().not(),
"should have cleaned up existing subdirectory"
);
}
}