1use std::collections::BTreeSet;
2use std::path::Path;
3
4pub(crate) mod pathbytes;
5pub(crate) mod wordsplit;
6
7pub mod compress;
8
9pub(crate) fn fname_from_path(path: &Path) -> Option<String> {
13 if path.to_bytes().ends_with(b"/") {
14 return None;
15 }
16 let path = path.file_name()?.to_string_lossy();
17 Some(path.into_owned())
18}
19
20use pathbytes::AsUnixPathBytes;
21#[cfg(test)]
22pub(crate) use tests::is_path_file;
23
24#[cfg(not(test))]
25pub(crate) fn is_path_file(path: &Path) -> bool {
26 path.is_file()
27}
28
29#[cfg(test)]
30pub(crate) use tests::read_file_to_string;
31
32#[cfg(not(test))]
33pub(crate) fn read_file_to_string(path: &Path) -> std::io::Result<String> {
34 std::fs::read_to_string(path)
35}
36
37#[cfg(test)]
38pub(crate) use tests::read_file_to_bytes;
39
40#[cfg(not(test))]
41pub(crate) fn read_file_to_bytes(path: &Path) -> std::io::Result<Vec<u8>> {
42 std::fs::read(path)
43}
44
45macro_rules! map(
72 { $($key:expr => $value:expr),+ } => {
73 {
74 let mut m = ::std::collections::HashMap::new();
75 $(
76 m.insert($key, $value);
77 )+
78 m
79 }
80 };
81);
82
83pub(crate) trait MyJoin {
86 fn join(&self, sep: &str) -> String;
87}
88
89impl MyJoin for BTreeSet<String> {
100 fn join(&self, sep: &str) -> String {
101 self.iter().map(|item| item.as_str()).collect::<Vec<&str>>().join(sep)
102 }
103}
104
105#[cfg(test)]
106pub(crate) mod tests {
107 use lazy_static::lazy_static;
108 use std::collections::HashMap;
109
110 lazy_static! {
111 static ref ERROR_REGEX: regex::Regex = regex::Regex::new(r"^error:(?P<error_name>.+)$").unwrap();
112 }
113
114 use std::sync::Mutex;
143
144 pub(crate) struct TestPath {
145 _filename: &'static str,
146 contents: String,
147 read_count: u16,
148 }
149
150 impl TestPath {
151 fn new(filename: &'static str, contents: String) -> Self {
152 Self {
153 _filename: filename,
154 contents,
155 read_count: 0,
156 }
157 }
158
159 fn read(&mut self) -> String {
160 self.read_count += 1;
161 self.contents.clone()
162 }
163
164 fn count(&self) -> u16 {
165 self.read_count
166 }
167 }
168
169 thread_local!(
170 static MOCK_FS: Mutex<HashMap<&'static str, TestPath>> = Mutex::new(HashMap::new());
171 );
172
173 pub(crate) struct ResetFsGuard;
174
175 impl Drop for ResetFsGuard {
176 fn drop(&mut self) {
177 MOCK_FS.with(|fs| {
178 fs.lock().unwrap().clear();
179 });
180 }
181 }
182
183 #[must_use]
184 pub(crate) fn add_test_fs_paths(paths: &[&'static str]) -> ResetFsGuard {
185 MOCK_FS.with(|fs| {
186 let mut fs_map = fs.lock().unwrap();
187 for path in paths {
188 fs_map.insert(path, TestPath::new(path, String::new()));
189 }
190 });
191 ResetFsGuard
192 }
193
194 pub(crate) fn set_test_fs_path_content(path: &'static str, contents: String) {
195 MOCK_FS.with(|fs| {
196 let mut fs_map = fs.lock().unwrap();
197 fs_map.insert(path, TestPath::new(path, contents));
198 });
199 }
200
201 fn with_test_fs<F, R>(callback: F) -> R
202 where F: Fn(&mut HashMap<&'static str, TestPath>) -> R {
203 MOCK_FS.with(|fs| callback(&mut fs.lock().unwrap()))
204 }
205
206 pub(crate) fn is_path_file(path: &Path) -> bool {
207 with_test_fs(|fs| fs.contains_key(&path.to_str().unwrap()))
208 }
209
210 pub(crate) fn get_read_count(path: &str) -> u16 {
211 with_test_fs(|fs| fs.get(path).unwrap().count())
212 }
213
214 pub(crate) fn read_file_to_string(path: &Path) -> std::io::Result<String> {
215 fn str_to_err(str: &str) -> std::io::Result<String> {
216 Err(std::io::Error::from(match str {
217 "InvalidInput" => std::io::ErrorKind::InvalidInput,
218 "Interrupted" => std::io::ErrorKind::Interrupted,
219 "PermissionDenied" => std::io::ErrorKind::PermissionDenied,
220 "NotFound" => std::io::ErrorKind::NotFound,
221 "Other" => std::io::ErrorKind::Other,
222 _ => panic!("Unknown I/O ErrorKind '{str}'")
223 }))
224 }
225
226 with_test_fs(|fs| match fs.get_mut(path.to_str().unwrap()) {
227 None => Err(std::io::Error::new(
228 std::io::ErrorKind::NotFound,
229 format!("Test filesystem path {path:?} does not exist"),
230 )),
231 Some(test_path) => {
232 let contents = test_path.read();
233 match ERROR_REGEX.captures(&contents) {
234 None => Ok(contents),
235 Some(caps) => match caps.name("error_name") {
236 None => Ok(contents),
237 Some(re_match) => str_to_err(re_match.as_str()),
238 },
239 }
240 },
241 })
242 }
243
244 pub(crate) fn read_file_to_bytes(path: &Path) -> std::io::Result<Vec<u8>> {
245 match read_file_to_string(path) {
246 Ok(contents) => Ok(Vec::from(contents.as_bytes())),
247 Err(x) => Err(x),
248 }
249 }
250
251 use super::*;
256
257 #[test]
258 fn fname_from_path_returns_file_name_even_if_file_does_not_exist() {
259 assert_eq!("some_name", fname_from_path(Path::new("some_name")).unwrap());
260 assert_eq!("some_name", fname_from_path(Path::new("/some_name")).unwrap());
261 assert_eq!("some_name", fname_from_path(Path::new("/a/b/some_name")).unwrap());
262 }
263
264 #[test]
265 fn fname_from_path_fails_when_path_is_empty() {
266 assert_eq!(None, fname_from_path(Path::new("")));
267 }
268
269 #[test]
270 fn fname_from_path_fails_when_path_has_no_filename() {
271 assert_eq!(None, fname_from_path(Path::new("/a/")));
272 }
273
274 #[test]
275 fn map_macro() {
276 let mut one = HashMap::new();
277 one.insert(1, 'a');
278 assert_eq!(one, map! { 1 => 'a' });
279
280 let mut two = HashMap::new();
281 two.insert("a", 1);
282 two.insert("b", 2);
283 assert_eq!(two, map! { "a" => 1, "b" => 2 });
284 }
285
286 #[test]
287 fn btreeset_join() {
288 let empty: BTreeSet<String> = vec![].into_iter().collect();
289 assert_eq!("", empty.join(""));
290 assert_eq!("", empty.join(","));
291
292 let one: BTreeSet<String> = vec!["a"].into_iter().map(|s| s.to_owned()).collect();
293 assert_eq!("a", one.join(""));
294 assert_eq!("a", one.join(","));
295
296 let two: BTreeSet<String> = vec!["a", "b"].into_iter().map(|s| s.to_owned()).collect();
297 assert_eq!("ab", two.join(""));
298 assert_eq!("a,b", two.join(","));
299 }
300}