diskann_benchmark_runner/
checker.rs1use std::{
7 collections::HashSet,
8 path::{Path, PathBuf},
9};
10
11#[derive(Debug)]
13pub struct Checker {
14 search_directories: Vec<PathBuf>,
21
22 output_directory: Option<PathBuf>,
25
26 current_outputs: HashSet<PathBuf>,
30}
31
32impl Checker {
33 pub(crate) fn new(search_directories: Vec<PathBuf>, output_directory: Option<PathBuf>) -> Self {
35 Self {
36 search_directories,
37 output_directory,
38 current_outputs: HashSet::new(),
39 }
40 }
41
42 pub fn search_directories(&self) -> &[PathBuf] {
44 &self.search_directories
45 }
46
47 pub fn output_directory(&self) -> Option<&PathBuf> {
49 self.output_directory.as_ref()
50 }
51
52 pub fn register_output(&mut self, save_path: Option<&Path>) -> anyhow::Result<PathBuf> {
58 let resolved_dir = match save_path {
61 None => {
62 if let Some(output_dir) = self.output_directory() {
63 output_dir.clone()
64 } else {
65 return Err(anyhow::Error::msg(
66 "relative save path \"{}\" specified but no output directory was provided",
67 ));
68 }
69 }
70 Some(save_path) => {
71 if save_path.is_absolute() {
72 if !(save_path.is_dir()) {
73 return Err(anyhow::Error::msg(format!(
74 "absolute save path \"{}\" is not a valid directory",
75 save_path.display()
76 )));
77 }
78 save_path.to_path_buf()
79 } else {
80 if let Some(output_dir) = self.output_directory() {
82 let absolute = output_dir.join(save_path);
83 if !absolute.is_dir() {
84 return Err(anyhow::Error::msg(format!(
85 "relative save path \"{}\" is not a valid directory when combined with output directory \"{}\"",
86 save_path.display(),
87 output_dir.display()
88 )));
89 }
90 absolute
91 } else {
92 return Err(anyhow::Error::msg(format!(
93 "relative save path \"{}\" specified but no output directory was provided",
94 save_path.display()
95 )));
96 }
97 }
98 }
99 };
100
101 if !self.current_outputs.insert(resolved_dir.clone()) {
103 anyhow::bail!(
104 "output directory {} already being used by another job",
105 resolved_dir.display()
106 );
107 } else {
108 Ok(resolved_dir)
109 }
110 }
111
112 #[deprecated(since = "0.54.0", note = "please use `find_input_file` instead")]
120 pub fn check_path(&self, path: &Path) -> Result<PathBuf, anyhow::Error> {
121 self.find_input_file(path)
122 }
123
124 pub fn find_input_file(&self, path: &Path) -> Result<PathBuf, anyhow::Error> {
134 self.check_input_path(path, Kind::File)
135 }
136
137 pub fn find_input_dir(&self, path: &Path) -> Result<PathBuf, anyhow::Error> {
147 self.check_input_path(path, Kind::Dir)
148 }
149
150 fn check_input_path(&self, path: &Path, kind: Kind) -> Result<PathBuf, anyhow::Error> {
151 if path.is_absolute() {
156 if kind.check(path) {
157 return Ok(path.into());
158 } else {
159 let kind = kind.as_str();
160 return Err(anyhow::Error::msg(format!(
161 "input {} with absolute path \"{}\" either does not exist or is not a {}",
162 kind,
163 path.display(),
164 kind,
165 )));
166 }
167 };
168
169 for dir in self.search_directories() {
171 let absolute = dir.join(path);
172 if kind.check(&absolute) {
173 return Ok(absolute);
174 }
175 }
176
177 Err(anyhow::Error::msg(format!(
178 "could not find input {} \"{}\" in the search directories \"{:?}\"",
179 kind.as_str(),
180 path.display(),
181 self.search_directories(),
182 )))
183 }
184}
185
186#[derive(Debug, Clone, Copy)]
187enum Kind {
188 File,
189 Dir,
190}
191
192impl Kind {
193 fn check(self, path: &Path) -> bool {
194 match self {
195 Self::File => path.is_file(),
196 Self::Dir => path.is_dir(),
197 }
198 }
199
200 fn as_str(self) -> &'static str {
201 match self {
202 Self::File => "file",
203 Self::Dir => "directory",
204 }
205 }
206}
207
208#[cfg(test)]
213mod tests {
214 use super::*;
215
216 use std::fs::{create_dir, File};
217
218 #[test]
219 fn test_constructor() {
220 let checker = Checker::new(Vec::new(), None);
221 assert!(checker.search_directories().is_empty());
222 assert!(checker.output_directory().is_none());
223
224 let dir_a: PathBuf = "directory/a".into();
225 let dir_b: PathBuf = "directory/another/b".into();
226
227 let checker = Checker::new(vec![dir_a.clone()], Some(dir_b.clone()));
228 assert_eq!(checker.search_directories(), vec![dir_a.clone()]);
229 assert_eq!(checker.output_directory(), Some(&dir_b));
230
231 let checker = Checker::new(vec![dir_a.clone(), dir_b.clone()], None);
232 assert_eq!(
233 checker.search_directories(),
234 vec![dir_a.clone(), dir_b.clone()]
235 );
236 assert!(checker.output_directory().is_none());
237 }
238
239 fn create_test_directory(dir: &Path) {
251 File::create(dir.join("file_a.txt")).unwrap();
252
253 create_dir(dir.join("dir0")).unwrap();
254 create_dir(dir.join("dir0/dir2")).unwrap();
255 create_dir(dir.join("dir1")).unwrap();
256 create_dir(dir.join("dir1/dir0")).unwrap();
257 File::create(dir.join("dir0/file_b.txt")).unwrap();
258 File::create(dir.join("dir1/file_c.txt")).unwrap();
259 File::create(dir.join("dir1/dir0/file_c.txt")).unwrap();
260 }
261
262 #[test]
263 fn test_find_input_file() {
264 let dir = tempfile::tempdir().unwrap();
265 let path = dir.path();
266 create_test_directory(path);
267
268 let make_checker = |paths: &[PathBuf]| -> Checker { Checker::new(paths.to_vec(), None) };
269
270 {
272 let checker = make_checker(&[]);
273 let absolute = path.join("file_a.txt");
274 assert_eq!(
275 checker.find_input_file(&absolute).unwrap(),
276 absolute,
277 "absolute paths should be unmodified if they exist",
278 );
279
280 let absolute = path.join("dir0/file_b.txt");
281 assert_eq!(
282 checker.find_input_file(&absolute).unwrap(),
283 absolute,
284 "absolute paths should be unmodified if they exist",
285 );
286 }
287
288 {
290 let checker = make_checker(&[]);
291 let absolute = path.join("dir0/file_c.txt");
292 let err = checker.find_input_file(&absolute).unwrap_err();
293 let message = err.to_string();
294 assert!(message.contains("input file with absolute path"));
295 assert!(message.contains("either does not exist or is not a file"));
296 }
297
298 {
300 let checker =
301 make_checker(&[path.join("dir1/dir0"), path.join("dir1"), path.join("dir0")]);
302
303 let file = &Path::new("file_c.txt");
305 let resolved = checker.find_input_file(file).unwrap();
306 assert_eq!(resolved, path.join("dir1/dir0/file_c.txt"));
307
308 let file = &Path::new("file_b.txt");
309 let resolved = checker.find_input_file(file).unwrap();
310 assert_eq!(resolved, path.join("dir0/file_b.txt"));
311
312 let file = &Path::new("file_a.txt");
314 let err = checker.find_input_file(file).unwrap_err();
315 let message = err.to_string();
316 assert!(message.contains("could not find input file"));
317 assert!(message.contains("in the search directories"));
318
319 let file = path.join("file_c.txt");
321 let err = checker.find_input_file(&file).unwrap_err();
322 let message = err.to_string();
323 assert!(message.starts_with("input file with absolute path"));
324 }
325 }
326
327 #[test]
328 fn test_find_input_dir() {
329 let dir = tempfile::tempdir().unwrap();
330 let path = dir.path();
331 create_test_directory(path);
332
333 let make_checker = |paths: &[PathBuf]| -> Checker { Checker::new(paths.to_vec(), None) };
334
335 {
337 let checker = make_checker(&[]);
338 let absolute = path.join("dir0");
339 assert_eq!(
340 checker.find_input_dir(&absolute).unwrap(),
341 absolute,
342 "absolute paths should be unmodified if they exist",
343 );
344
345 let absolute = path.join("dir1/dir0");
346 assert_eq!(
347 checker.find_input_dir(&absolute).unwrap(),
348 absolute,
349 "absolute paths should be unmodified if they exist",
350 );
351 }
352
353 {
355 let checker = make_checker(&[]);
356 let absolute = path.join("dir1/dir1");
357 let err = checker.find_input_dir(&absolute).unwrap_err();
358 let message = err.to_string();
359 assert!(message.contains("input directory with absolute path"));
360 assert!(message.contains("either does not exist or is not a directory"));
361
362 let absolute = path.join("file_a.txt");
364 let err = checker.find_input_dir(&absolute).unwrap_err();
365 let message = err.to_string();
366 assert!(message.contains("input directory with absolute path"));
367 assert!(message.contains("either does not exist or is not a directory"));
368 }
369
370 {
372 let checker =
373 make_checker(&[path.join("dir1/dir0"), path.join("dir1"), path.join("dir0")]);
374
375 let dir = &Path::new("dir0");
377 let resolved = checker.find_input_dir(dir).unwrap();
378 assert_eq!(resolved, path.join("dir1/dir0"));
379
380 let dir = &Path::new("dir2");
381 let resolved = checker.find_input_dir(dir).unwrap();
382 assert_eq!(resolved, path.join("dir0/dir2"));
383
384 let dir = &Path::new("nope");
386 let err = checker.find_input_dir(dir).unwrap_err();
387 let message = err.to_string();
388 assert!(message.contains("could not find input directory"));
389 assert!(message.contains("in the search directories"));
390
391 let dir = path.join("dir2");
393 let err = checker.find_input_dir(&dir).unwrap_err();
394 let message = err.to_string();
395 assert!(message.starts_with("input directory with absolute path"));
396 }
397 }
398}