mithril_common/test_utils/
temp_dir.rs1use sha2::{Digest, Sha256};
2use std::path::{Path, PathBuf};
3
4#[derive(Clone)]
6pub struct TempDir {
7 module_name: String,
8 name: String,
9 enable_short_path: bool,
10 short_path_max_len: usize,
11}
12
13const TEMP_DIR_ROOT_NAME: &str = "mithril_test";
14const DEFAULT_SHORT_PATH_MAX_LEN: usize = 90;
16
17impl TempDir {
18 pub fn new<M: Into<String>, N: Into<String>>(module: M, name: N) -> Self {
20 Self {
21 module_name: module.into(),
22 name: name.into(),
23 enable_short_path: false,
24 short_path_max_len: DEFAULT_SHORT_PATH_MAX_LEN,
25 }
26 }
27
28 pub fn generate_shorter_path(mut self) -> Self {
32 self.enable_short_path = true;
33 self
34 }
35
36 pub fn set_short_path_max_len(mut self, max_len: usize) -> Self {
38 self.short_path_max_len = max_len;
39 self
40 }
41
42 pub fn build_path(&self) -> PathBuf {
44 const SHA_LENGTH: usize = 10;
45 let base_dir = std::env::temp_dir().join(TEMP_DIR_ROOT_NAME);
46
47 if self.enable_short_path {
53 let mut hasher = Sha256::new();
56 hasher.update(&self.module_name);
57 hasher.update(&self.name);
58 let sha = hex::encode(hasher.finalize());
59 let short_sha = &sha[0..SHA_LENGTH];
60
61 let max_path_len =
63 self.short_path_max_len - SHA_LENGTH - 2 - base_dir.to_string_lossy().len();
64
65 let max_len = self.name.len().min(max_path_len);
66
67 base_dir.join([&self.name[0..max_len], "_", short_sha].concat())
68 } else {
69 base_dir.join(&self.module_name).join(&self.name)
70 }
71 }
72
73 pub fn build(&self) -> PathBuf {
75 let path = self.build_path();
76 self.create_dir(&path);
77
78 path
79 }
80
81 pub fn create<M: Into<String>, N: Into<String>>(module: M, name: N) -> PathBuf {
89 Self::new(module, name).build()
90 }
91
92 pub fn create_with_short_path<M: Into<String>, N: Into<String>>(module: M, name: N) -> PathBuf {
101 Self::new(module, name).generate_shorter_path().build()
102 }
103
104 fn create_dir(&self, path: &Path) {
105 if path.exists() {
106 std::fs::remove_dir_all(path)
107 .unwrap_or_else(|e| panic!("Could not remove dir {path:?}: {e}"));
108 }
109
110 std::fs::create_dir_all(path)
111 .unwrap_or_else(|e| panic!("Could not create dir {path:?}: {e}"));
112 }
113}
114
115#[macro_export]
117macro_rules! temp_dir {
118 () => {{
119 fn f() {}
120 let current_function_path = $crate::test_utils::format_current_function_path(f);
121 let current_function_path = current_function_path.replace("/tests/", "/");
122
123 $crate::test_utils::TempDir::new(current_function_path, "").build_path()
124 }};
125}
126pub use temp_dir;
127
128#[macro_export]
130macro_rules! temp_dir_create {
131 () => {{
132 fn f() {}
133 let current_function_path = $crate::test_utils::format_current_function_path(f);
134 let current_function_path = current_function_path.replace("/tests/", "/");
135
136 $crate::test_utils::TempDir::new(current_function_path, "").build()
137 }};
138}
139pub use temp_dir_create;
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::{fs, io::Write, ops::Not};
145
146 #[test]
147 fn non_short_path_are_in_a_mithril_test_slash_module_folder_structure() {
148 let path = TempDir::new("temp_dir", "basic").build();
149
150 assert_eq!(
151 Some(
152 std::env::temp_dir()
153 .join(TEMP_DIR_ROOT_NAME)
154 .join("temp_dir")
155 .as_path()
156 ),
157 path.parent()
158 );
159 assert!(path.exists());
160 }
161
162 #[test]
163 fn short_path_are_in_a_mithril_test_folder_structure() {
164 let path = TempDir::new("temp_dir", "basic_short_path")
165 .generate_shorter_path()
166 .build();
167
168 assert_eq!(
169 Some(std::env::temp_dir().join(TEMP_DIR_ROOT_NAME).as_path()),
170 path.parent()
171 );
172 assert!(path.exists());
173 }
174
175 #[test]
176 fn shorter_path_have_a_length_lower_than_90_chars_even_when_given_module_longer_than_that() {
177 let path = TempDir::new(
178 "module_longer_than_a_string_of_90_characters_so_this_test_can_fail_if_the_builder_is_a_bad_builder_that_do_nothing",
179 "name",
180 )
181 .generate_shorter_path()
182 .set_short_path_max_len(90)
183 .build_path();
184 let path_len = path.to_string_lossy().len();
185
186 assert!(
187 path_len <= 90,
188 "path with `short` option enabled was longer than 90 characters:\n\
189 path_len: `{path_len}`\n\
190 path: `{}`",
191 path.display()
192 );
193 }
194
195 #[test]
196 fn shorter_path_have_a_length_lower_than_90_chars_even_when_given_name_longer_than_that() {
197 let path = TempDir::new(
198 "mod",
199 "name_longer_than_a_string_of_90_characters_so_this_test_can_fail_if_the_builder_is_a_bad_builder_that_do_nothing",
200 )
201 .generate_shorter_path()
202 .set_short_path_max_len(90)
203 .build_path();
204 let path_len = path.to_string_lossy().len();
205
206 assert!(
207 path_len <= 90,
208 "path with `short` option enabled was longer than 90 characters:\n\
209 path_len: `{path_len}`\n\
210 path: `{}`",
211 path.display()
212 );
213 }
214
215 #[test]
216 fn same_name_but_two_different_module_generate_different_path() {
217 let path1 = TempDir::new("module_a", "test").build_path();
218 let path2 = TempDir::new("module_b", "test").build_path();
219
220 assert_ne!(path1, path2);
221 }
222
223 #[test]
224 fn same_name_but_two_different_module_generate_different_path_even_with_short_path_enabled() {
225 let path1 = TempDir::new("module_a", "test")
226 .generate_shorter_path()
227 .build_path();
228 let path2 = TempDir::new("module_b", "test")
229 .generate_shorter_path()
230 .build_path();
231
232 assert_ne!(path1, path2);
233 }
234
235 #[test]
236 fn creating_temp_dir_remove_existing_content() {
237 let builder = TempDir::new("temp_dir", "creating_temp_dir_remove_existing_content");
238 let (existing_dir, existing_file) = {
239 let path = builder.build_path();
240 (path.join("existing_subdir"), path.join("existing_file.md"))
241 };
242
243 fs::create_dir_all(&existing_dir).unwrap();
244 let mut file = fs::File::create(&existing_file).unwrap();
245 file.write_all(b"file content").unwrap();
246
247 builder.build();
248
249 assert!(
250 existing_file.exists().not(),
251 "should have cleaned up existing files"
252 );
253 assert!(
254 existing_dir.exists().not(),
255 "should have cleaned up existing subdirectory"
256 );
257 }
258
259 #[test]
260 fn creating_temp_dir_base_on_current_function() {
261 assert_eq!(
262 std::env::temp_dir()
263 .join(TEMP_DIR_ROOT_NAME)
264 .join("mithril_common")
265 .join("test_utils")
266 .join("temp_dir")
267 .join("creating_temp_dir_base_on_current_function"),
268 temp_dir!(),
269 );
270 }
271
272 #[tokio::test]
273 async fn creating_temp_dir_base_on_current_async_function() {
274 assert_eq!(
275 std::env::temp_dir()
276 .join(TEMP_DIR_ROOT_NAME)
277 .join("mithril_common")
278 .join("test_utils")
279 .join("temp_dir")
280 .join("creating_temp_dir_base_on_current_async_function"),
281 temp_dir!(),
282 );
283 }
284}