Skip to main content

diskann_benchmark_runner/
files.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::path::{Path, PathBuf};
7
8use serde::{Deserialize, Serialize};
9
10use super::Checker;
11
12/// A file that is used as an input to for a benchmark.
13///
14/// When used during input deserialization, with a [`Checker`], the following actions will be
15/// taken:
16///
17/// 1. If the path is absolute or points to an existing file relative to the current working
18///    directory, no additional measure will be taken.
19///
20/// 2. If the path is not absolute, then every directory in [`Checker::search_directories()`]
21///    will be explored in-order. The first directory where `path` exists will be selected.
22///
23/// 3. If all these steps fail, then post-deserialization checking will fail with an error.
24#[derive(Debug, Serialize, Deserialize, Clone)]
25#[serde(transparent)]
26pub struct InputFile {
27    path: PathBuf,
28}
29
30impl InputFile {
31    /// Create a new new input file from the path-like `path``
32    pub fn new<P>(path: P) -> Self
33    where
34        PathBuf: From<P>,
35    {
36        Self {
37            path: PathBuf::from(path),
38        }
39    }
40
41    /// Try to resolve `self` to an existing file.
42    ///
43    /// If `self` is absolute, this will verify that `self` exists. Otherwise, the search
44    /// directories in `checker` will be prepended to `self` and the first existing file
45    /// will be returned.
46    pub fn resolve(&mut self, checker: &mut Checker) -> anyhow::Result<()> {
47        let checked_path = checker.find_input_file(self);
48        match checked_path {
49            Ok(p) => {
50                self.path = p;
51                Ok(())
52            }
53            Err(e) => Err(e),
54        }
55    }
56}
57
58impl std::ops::Deref for InputFile {
59    type Target = Path;
60    fn deref(&self) -> &Self::Target {
61        &self.path
62    }
63}
64
65///////////
66// Tests //
67///////////
68
69#[cfg(test)]
70mod tests {
71    use std::fs::{create_dir, File};
72
73    use super::*;
74
75    #[test]
76    fn test_input_file() {
77        let file = InputFile::new("hello/world");
78        let file_deref: &Path = &file;
79        assert_eq!(file_deref.to_str().unwrap(), "hello/world");
80    }
81
82    #[test]
83    fn test_serialization() {
84        let st: &str = "\"path/to/directory\"";
85        let file: InputFile = serde_json::from_str(st).unwrap();
86        assert_eq!(file.to_str().unwrap(), st.trim_matches('\"'));
87
88        assert_eq!(&*serde_json::to_string(&file).unwrap(), st);
89    }
90
91    #[test]
92    fn test_resolve() {
93        // We create a directory that looks like this:
94        //
95        // dir/
96        //     file_a.txt
97        //     dir0/
98        //        file_b.txt
99        //     dir1/
100        //        file_c.txt
101        //        dir0/
102        //           file_c.txt
103        let dir = tempfile::tempdir().unwrap();
104        let path = dir.path();
105
106        File::create(path.join("file_a.txt")).unwrap();
107        create_dir(path.join("dir0")).unwrap();
108        create_dir(path.join("dir1")).unwrap();
109        create_dir(path.join("dir1/dir0")).unwrap();
110        File::create(path.join("dir0/file_b.txt")).unwrap();
111        File::create(path.join("dir1/file_c.txt")).unwrap();
112        File::create(path.join("dir1/dir0/file_c.txt")).unwrap();
113
114        // Test absolute path success.
115        {
116            let absolute = path.join("file_a.txt");
117            let mut file = InputFile::new(absolute.clone());
118            let mut checker = Checker::new(Vec::new(), None);
119            file.resolve(&mut checker).unwrap();
120            assert_eq!(file.path, absolute);
121
122            let absolute = path.join("dir0/file_b.txt");
123            let mut file = InputFile::new(absolute.clone());
124            let mut checker = Checker::new(Vec::new(), None);
125            file.resolve(&mut checker).unwrap();
126            assert_eq!(file.path, absolute);
127        }
128
129        // Absolute path fail.
130        {
131            let absolute = path.join("dir0/file_c.txt");
132            let mut file = InputFile::new(absolute.clone());
133            let mut checker = Checker::new(Vec::new(), None);
134            let err = file.resolve(&mut checker).unwrap_err();
135            let message = err.to_string();
136            assert!(message.contains("input file with absolute path"));
137            assert!(message.contains("either does not exist or is not a file"));
138        }
139
140        // Directory search
141        {
142            let mut checker = Checker::new(
143                vec![path.join("dir1/dir0"), path.join("dir1"), path.join("dir0")],
144                None,
145            );
146
147            // Directories are searched in order.
148            let mut file = InputFile::new("file_c.txt");
149            file.resolve(&mut checker).unwrap();
150            assert_eq!(file.path, path.join("dir1/dir0/file_c.txt"));
151
152            let mut file = InputFile::new("file_b.txt");
153            file.resolve(&mut checker).unwrap();
154            assert_eq!(file.path, path.join("dir0/file_b.txt"));
155
156            // Directory search can fail.
157            let mut file = InputFile::new("file_a.txt");
158            let err = file.resolve(&mut checker).unwrap_err();
159            let message = err.to_string();
160            assert!(message.contains("could not find input file"));
161            assert!(message.contains("in the search directories"));
162
163            // If we give an absolute path, no directory search is performed.
164            let mut file = InputFile::new(path.join("file_c.txt"));
165            let err = file.resolve(&mut checker).unwrap_err();
166            let message = err.to_string();
167            assert!(message.starts_with("input file with absolute path"));
168        }
169    }
170}