1 use std::env;
2 use std::error;
3 use std::fs::{self, File};
4 use std::io;
5 use std::path::{Path, PathBuf};
6 use std::result;
7 
8 use crate::{DirEntry, Error};
9 
10 /// Create an error from a format!-like syntax.
11 #[macro_export]
12 macro_rules! err {
13     ($($tt:tt)*) => {
14         Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*))
15     }
16 }
17 
18 /// A convenient result type alias.
19 pub type Result<T> = result::Result<T, Box<dyn error::Error + Send + Sync>>;
20 
21 /// The result of running a recursive directory iterator on a single directory.
22 #[derive(Debug)]
23 pub struct RecursiveResults {
24     ents: Vec<DirEntry>,
25     errs: Vec<Error>,
26 }
27 
28 impl RecursiveResults {
29     /// Return all of the errors encountered during traversal.
errs(&self) -> &[Error]30     pub fn errs(&self) -> &[Error] {
31         &self.errs
32     }
33 
34     /// Assert that no errors have occurred.
assert_no_errors(&self)35     pub fn assert_no_errors(&self) {
36         assert!(
37             self.errs.is_empty(),
38             "expected to find no errors, but found: {:?}",
39             self.errs
40         );
41     }
42 
43     /// Return all the successfully retrieved directory entries in the order
44     /// in which they were retrieved.
ents(&self) -> &[DirEntry]45     pub fn ents(&self) -> &[DirEntry] {
46         &self.ents
47     }
48 
49     /// Return all paths from all successfully retrieved directory entries.
50     ///
51     /// This does not include paths that correspond to an error.
paths(&self) -> Vec<PathBuf>52     pub fn paths(&self) -> Vec<PathBuf> {
53         self.ents.iter().map(|d| d.path().to_path_buf()).collect()
54     }
55 
56     /// Return all the successfully retrieved directory entries, sorted
57     /// lexicographically by their full file path.
sorted_ents(&self) -> Vec<DirEntry>58     pub fn sorted_ents(&self) -> Vec<DirEntry> {
59         let mut ents = self.ents.clone();
60         ents.sort_by(|e1, e2| e1.path().cmp(e2.path()));
61         ents
62     }
63 
64     /// Return all paths from all successfully retrieved directory entries,
65     /// sorted lexicographically.
66     ///
67     /// This does not include paths that correspond to an error.
sorted_paths(&self) -> Vec<PathBuf>68     pub fn sorted_paths(&self) -> Vec<PathBuf> {
69         self.sorted_ents().into_iter().map(|d| d.into_path()).collect()
70     }
71 }
72 
73 /// A helper for managing a directory in which to run tests.
74 ///
75 /// When manipulating paths within this directory, paths are interpreted
76 /// relative to this directory.
77 #[derive(Debug)]
78 pub struct Dir {
79     dir: TempDir,
80 }
81 
82 impl Dir {
83     /// Create a new empty temporary directory.
tmp() -> Dir84     pub fn tmp() -> Dir {
85         let dir = TempDir::new().unwrap();
86         Dir { dir }
87     }
88 
89     /// Return the path to this directory.
path(&self) -> &Path90     pub fn path(&self) -> &Path {
91         self.dir.path()
92     }
93 
94     /// Return a path joined to the path to this directory.
join<P: AsRef<Path>>(&self, path: P) -> PathBuf95     pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
96         self.path().join(path)
97     }
98 
99     /// Run the given iterator and return the result as a distinct collection
100     /// of directory entries and errors.
run_recursive<I>(&self, it: I) -> RecursiveResults where I: IntoIterator<Item = result::Result<DirEntry, Error>>,101     pub fn run_recursive<I>(&self, it: I) -> RecursiveResults
102     where
103         I: IntoIterator<Item = result::Result<DirEntry, Error>>,
104     {
105         let mut results = RecursiveResults { ents: vec![], errs: vec![] };
106         for result in it {
107             match result {
108                 Ok(ent) => results.ents.push(ent),
109                 Err(err) => results.errs.push(err),
110             }
111         }
112         results
113     }
114 
115     /// Create a directory at the given path, while creating all intermediate
116     /// directories as needed.
mkdirp<P: AsRef<Path>>(&self, path: P)117     pub fn mkdirp<P: AsRef<Path>>(&self, path: P) {
118         let full = self.join(path);
119         fs::create_dir_all(&full)
120             .map_err(|e| {
121                 err!("failed to create directory {}: {}", full.display(), e)
122             })
123             .unwrap();
124     }
125 
126     /// Create an empty file at the given path. All ancestor directories must
127     /// already exists.
touch<P: AsRef<Path>>(&self, path: P)128     pub fn touch<P: AsRef<Path>>(&self, path: P) {
129         let full = self.join(path);
130         File::create(&full)
131             .map_err(|e| {
132                 err!("failed to create file {}: {}", full.display(), e)
133             })
134             .unwrap();
135     }
136 
137     /// Create empty files at the given paths. All ancestor directories must
138     /// already exists.
touch_all<P: AsRef<Path>>(&self, paths: &[P])139     pub fn touch_all<P: AsRef<Path>>(&self, paths: &[P]) {
140         for p in paths {
141             self.touch(p);
142         }
143     }
144 
145     /// Create a file symlink to the given src with the given link name.
symlink_file<P1: AsRef<Path>, P2: AsRef<Path>>( &self, src: P1, link_name: P2, )146     pub fn symlink_file<P1: AsRef<Path>, P2: AsRef<Path>>(
147         &self,
148         src: P1,
149         link_name: P2,
150     ) {
151         #[cfg(windows)]
152         fn imp(src: &Path, link_name: &Path) -> io::Result<()> {
153             use std::os::windows::fs::symlink_file;
154             symlink_file(src, link_name)
155         }
156 
157         #[cfg(unix)]
158         fn imp(src: &Path, link_name: &Path) -> io::Result<()> {
159             use std::os::unix::fs::symlink;
160             symlink(src, link_name)
161         }
162 
163         let (src, link_name) = (self.join(src), self.join(link_name));
164         imp(&src, &link_name)
165             .map_err(|e| {
166                 err!(
167                     "failed to symlink file {} with target {}: {}",
168                     src.display(),
169                     link_name.display(),
170                     e
171                 )
172             })
173             .unwrap()
174     }
175 
176     /// Create a directory symlink to the given src with the given link name.
symlink_dir<P1: AsRef<Path>, P2: AsRef<Path>>( &self, src: P1, link_name: P2, )177     pub fn symlink_dir<P1: AsRef<Path>, P2: AsRef<Path>>(
178         &self,
179         src: P1,
180         link_name: P2,
181     ) {
182         #[cfg(windows)]
183         fn imp(src: &Path, link_name: &Path) -> io::Result<()> {
184             use std::os::windows::fs::symlink_dir;
185             symlink_dir(src, link_name)
186         }
187 
188         #[cfg(unix)]
189         fn imp(src: &Path, link_name: &Path) -> io::Result<()> {
190             use std::os::unix::fs::symlink;
191             symlink(src, link_name)
192         }
193 
194         let (src, link_name) = (self.join(src), self.join(link_name));
195         imp(&src, &link_name)
196             .map_err(|e| {
197                 err!(
198                     "failed to symlink directory {} with target {}: {}",
199                     src.display(),
200                     link_name.display(),
201                     e
202                 )
203             })
204             .unwrap()
205     }
206 }
207 
208 /// A simple wrapper for creating a temporary directory that is automatically
209 /// deleted when it's dropped.
210 ///
211 /// We use this in lieu of tempfile because tempfile brings in too many
212 /// dependencies.
213 #[derive(Debug)]
214 pub struct TempDir(PathBuf);
215 
216 impl Drop for TempDir {
drop(&mut self)217     fn drop(&mut self) {
218         fs::remove_dir_all(&self.0).unwrap();
219     }
220 }
221 
222 impl TempDir {
223     /// Create a new empty temporary directory under the system's configured
224     /// temporary directory.
new() -> Result<TempDir>225     pub fn new() -> Result<TempDir> {
226         #[allow(deprecated)]
227         use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
228 
229         static TRIES: usize = 100;
230         #[allow(deprecated)]
231         static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
232 
233         let tmpdir = env::temp_dir();
234         for _ in 0..TRIES {
235             let count = COUNTER.fetch_add(1, Ordering::SeqCst);
236             let path = tmpdir.join("rust-walkdir").join(count.to_string());
237             if path.is_dir() {
238                 continue;
239             }
240             fs::create_dir_all(&path).map_err(|e| {
241                 err!("failed to create {}: {}", path.display(), e)
242             })?;
243             return Ok(TempDir(path));
244         }
245         Err(err!("failed to create temp dir after {} tries", TRIES))
246     }
247 
248     /// Return the underlying path to this temporary directory.
path(&self) -> &Path249     pub fn path(&self) -> &Path {
250         &self.0
251     }
252 }
253