diff options
-rw-r--r-- | src/tests/mod.rs | 5 | ||||
-rw-r--r-- | src/tests/old.rs | 786 | ||||
-rw-r--r-- | src/tests/recursive.rs | 992 | ||||
-rw-r--r-- | src/tests/util.rs | 251 |
4 files changed, 1247 insertions, 787 deletions
diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 60b8de9..ebf952d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1 +1,4 @@ -mod old; +#[macro_use] +mod util; + +mod recursive; diff --git a/src/tests/old.rs b/src/tests/old.rs deleted file mode 100644 index 72649ff..0000000 --- a/src/tests/old.rs +++ /dev/null @@ -1,786 +0,0 @@ -#![cfg_attr(windows, allow(dead_code, unused_imports))] - -use std::cmp; -use std::env; -use std::fs::{self, File}; -use std::io; -use std::path::{Path, PathBuf}; -use std::collections::HashMap; - -use {DirEntry, WalkDir, IntoIter, Error, ErrorInner}; - -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -enum Tree { - Dir(PathBuf, Vec<Tree>), - File(PathBuf), - Symlink { - src: PathBuf, - dst: PathBuf, - dir: bool, - } -} - -impl Tree { - fn from_walk_with<P, F>( - p: P, - f: F, - ) -> io::Result<Tree> - where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir { - let mut stack = vec![Tree::Dir(p.as_ref().to_path_buf(), vec![])]; - let it: WalkEventIter = f(WalkDir::new(p)).into(); - for ev in it { - match try!(ev) { - WalkEvent::Exit => { - let tree = stack.pop().unwrap(); - if stack.is_empty() { - return Ok(tree); - } - stack.last_mut().unwrap().children_mut().push(tree); - } - WalkEvent::Dir(dent) => { - stack.push(Tree::Dir(pb(dent.file_name()), vec![])); - } - WalkEvent::File(dent) => { - let node = if dent.file_type().is_symlink() { - let src = try!(dent.path().read_link()); - let dst = pb(dent.file_name()); - let dir = dent.path().is_dir(); - Tree::Symlink { src: src, dst: dst, dir: dir } - } else { - Tree::File(pb(dent.file_name())) - }; - stack.last_mut().unwrap().children_mut().push(node); - } - } - } - assert_eq!(stack.len(), 1); - Ok(stack.pop().unwrap()) - } - - fn from_walk_with_contents_first<P, F>( - p: P, - f: F, - ) -> io::Result<Tree> - where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir { - let mut contents_of_dir_at_depth = HashMap::new(); - let mut min_depth = ::std::usize::MAX; - let top_level_path = p.as_ref().to_path_buf(); - for result in f(WalkDir::new(p).contents_first(true)) { - let dentry = try!(result); - - let tree = - if dentry.file_type().is_dir() { - let any_contents = contents_of_dir_at_depth.remove( - &(dentry.depth+1)); - Tree::Dir(pb(dentry.file_name()), any_contents.unwrap_or_default()) - } else { - if dentry.file_type().is_symlink() { - let src = try!(dentry.path().read_link()); - let dst = pb(dentry.file_name()); - let dir = dentry.path().is_dir(); - Tree::Symlink { src: src, dst: dst, dir: dir } - } else { - Tree::File(pb(dentry.file_name())) - } - }; - contents_of_dir_at_depth.entry( - dentry.depth).or_insert(vec!()).push(tree); - min_depth = cmp::min(min_depth, dentry.depth); - } - Ok(Tree::Dir(top_level_path, - contents_of_dir_at_depth.remove(&min_depth) - .unwrap_or_default())) - } - - fn unwrap_singleton(self) -> Tree { - match self { - Tree::File(_) | Tree::Symlink { .. } => { - panic!("cannot unwrap file or link as dir"); - } - Tree::Dir(_, mut childs) => { - assert_eq!(childs.len(), 1); - childs.pop().unwrap() - } - } - } - - fn unwrap_dir(self) -> Vec<Tree> { - match self { - Tree::File(_) | Tree::Symlink { .. } => { - panic!("cannot unwrap file as dir"); - } - Tree::Dir(_, childs) => childs, - } - } - - fn children_mut(&mut self) -> &mut Vec<Tree> { - match *self { - Tree::File(_) | Tree::Symlink { .. } => { - panic!("files do not have children"); - } - Tree::Dir(_, ref mut children) => children, - } - } - - fn create_in<P: AsRef<Path>>(&self, parent: P) -> io::Result<()> { - let parent = parent.as_ref(); - match *self { - Tree::Symlink { ref src, ref dst, dir } => { - if dir { - try!(soft_link_dir(src, parent.join(dst))); - } else { - try!(soft_link_file(src, parent.join(dst))); - } - } - Tree::File(ref p) => { try!(File::create(parent.join(p))); } - Tree::Dir(ref dir, ref children) => { - try!(fs::create_dir(parent.join(dir))); - for child in children { - try!(child.create_in(parent.join(dir))); - } - } - } - Ok(()) - } - - fn canonical(&self) -> Tree { - match *self { - Tree::Symlink { ref src, ref dst, dir } => { - Tree::Symlink { src: src.clone(), dst: dst.clone(), dir: dir } - } - Tree::File(ref p) => { - Tree::File(p.clone()) - } - Tree::Dir(ref p, ref cs) => { - let mut cs: Vec<Tree> = - cs.iter().map(|c| c.canonical()).collect(); - cs.sort(); - Tree::Dir(p.clone(), cs) - } - } - } -} - -#[derive(Debug)] -enum WalkEvent { - Dir(DirEntry), - File(DirEntry), - Exit, -} - -struct WalkEventIter { - depth: usize, - it: IntoIter, - next: Option<Result<DirEntry, Error>>, -} - -impl From<WalkDir> for WalkEventIter { - fn from(it: WalkDir) -> WalkEventIter { - WalkEventIter { depth: 0, it: it.into_iter(), next: None } - } -} - -impl Iterator for WalkEventIter { - type Item = io::Result<WalkEvent>; - - fn next(&mut self) -> Option<io::Result<WalkEvent>> { - let dent = self.next.take().or_else(|| self.it.next()); - let depth = match dent { - None => 0, - Some(Ok(ref dent)) => dent.depth(), - Some(Err(ref err)) => err.depth(), - }; - if depth < self.depth { - self.depth -= 1; - self.next = dent; - return Some(Ok(WalkEvent::Exit)); - } - self.depth = depth; - match dent { - None => None, - Some(Err(err)) => Some(Err(From::from(err))), - Some(Ok(dent)) => { - if dent.file_type().is_dir() { - self.depth += 1; - Some(Ok(WalkEvent::Dir(dent))) - } else { - Some(Ok(WalkEvent::File(dent))) - } - } - } - } -} - -struct TempDir(PathBuf); - -impl TempDir { - fn path<'a>(&'a self) -> &'a Path { - &self.0 - } -} - -impl Drop for TempDir { - fn drop(&mut self) { - fs::remove_dir_all(&self.0).unwrap(); - } -} - -fn tmpdir() -> TempDir { - use std::sync::atomic::{AtomicUsize, Ordering}; - - static COUNTER: AtomicUsize = AtomicUsize::new(0); - - let p = env::temp_dir(); - let idx = COUNTER.fetch_add(1, Ordering::SeqCst); - let ret = p.join(&format!("rust-{}", idx)); - fs::create_dir(&ret).unwrap(); - TempDir(ret) -} - -fn dir_setup_with<F>(t: &Tree, f: F) -> (TempDir, Tree) - where F: Fn(WalkDir) -> WalkDir { - let tmp = tmpdir(); - t.create_in(tmp.path()).unwrap(); - let got = Tree::from_walk_with(tmp.path(), &f).unwrap(); - let got_cf = Tree::from_walk_with_contents_first(tmp.path(), &f).unwrap(); - assert_eq!(got, got_cf); - - (tmp, got.unwrap_singleton().unwrap_singleton()) -} - -fn dir_setup(t: &Tree) -> (TempDir, Tree) { - dir_setup_with(t, |wd| wd) -} - -fn canon(unix: &str) -> String { - if cfg!(windows) { - unix.replace("/", "\\") - } else { - unix.to_string() - } -} - -fn pb<P: AsRef<Path>>(p: P) -> PathBuf { p.as_ref().to_path_buf() } -fn td<P: AsRef<Path>>(p: P, cs: Vec<Tree>) -> Tree { - Tree::Dir(pb(p), cs) -} -fn tf<P: AsRef<Path>>(p: P) -> Tree { - Tree::File(pb(p)) -} -fn tld<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Tree { - Tree::Symlink { src: pb(src), dst: pb(dst), dir: true } -} -fn tlf<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Tree { - Tree::Symlink { src: pb(src), dst: pb(dst), dir: false } -} - -#[cfg(unix)] -fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>( - src: P, - dst: Q, -) -> io::Result<()> { - use std::os::unix::fs::symlink; - symlink(src, dst) -} - -#[cfg(unix)] -fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>( - src: P, - dst: Q, -) -> io::Result<()> { - soft_link_dir(src, dst) -} - -#[cfg(windows)] -fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>( - src: P, - dst: Q, -) -> io::Result<()> { - use std::os::windows::fs::symlink_dir; - symlink_dir(src, dst) -} - -#[cfg(windows)] -fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>( - src: P, - dst: Q, -) -> io::Result<()> { - use std::os::windows::fs::symlink_file; - symlink_file(src, dst) -} - -macro_rules! assert_tree_eq { - ($e1:expr, $e2:expr) => { - assert_eq!($e1.canonical(), $e2.canonical()); - } -} - -#[test] -fn walk_dir_1() { - let exp = td("foo", vec![]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_2() { - let exp = tf("foo"); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_3() { - let exp = td("foo", vec![tf("bar")]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_4() { - let exp = td("foo", vec![tf("foo"), tf("bar"), tf("baz")]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_5() { - let exp = td("foo", vec![td("bar", vec![])]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_6() { - let exp = td("foo", vec![ - td("bar", vec![ - tf("baz"), td("bat", vec![]), - ]), - ]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_7() { - let exp = td("foo", vec![ - td("bar", vec![ - tf("baz"), td("bat", vec![]), - ]), - td("a", vec![tf("b"), tf("c"), tf("d")]), - ]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_sym_1() { - let exp = td("foo", vec![tf("bar"), tlf("bar", "baz")]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_sym_2() { - let exp = td("foo", vec![ - td("a", vec![tf("a1"), tf("a2")]), - tld("a", "alink"), - ]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -fn walk_dir_sym_root() { - let exp = td("foo", vec![ - td("bar", vec![tf("a"), tf("b")]), - tld("bar", "alink"), - ]); - let tmp = tmpdir(); - let tmp_path = tmp.path(); - let tmp_len = tmp_path.to_str().unwrap().len(); - exp.create_in(tmp_path).unwrap(); - - let it = WalkDir::new(tmp_path.join("foo").join("alink")).into_iter(); - let mut got = it - .map(|d| d.unwrap().path().to_str().unwrap()[tmp_len+1..].into()) - .collect::<Vec<String>>(); - got.sort(); - assert_eq!(got, vec![ - canon("foo/alink"), canon("foo/alink/a"), canon("foo/alink/b"), - ]); - - let it = WalkDir::new(tmp_path.join("foo/alink/")).into_iter(); - let mut got = it - .map(|d| d.unwrap().path().to_str().unwrap()[tmp_len+1..].into()) - .collect::<Vec<String>>(); - got.sort(); - assert_eq!(got, vec!["foo/alink/", "foo/alink/a", "foo/alink/b"]); -} - -// See: https://github.com/BurntSushi/ripgrep/issues/984 -#[test] -#[cfg(unix)] -fn first_path_not_symlink() { - let exp = td("foo", vec![]); - let (tmp, _got) = dir_setup(&exp); - - let dents = WalkDir::new(tmp.path().join("foo")) - .into_iter() - .collect::<Result<Vec<_>, _>>() - .unwrap(); - assert_eq!(1, dents.len()); - assert!(!dents[0].path_is_symlink()); -} - -// Like first_path_not_symlink, but checks that the first path is not reported -// as a symlink even when we are supposed to be following them. -#[test] -#[cfg(unix)] -fn first_path_not_symlink_follow() { - let exp = td("foo", vec![]); - let (tmp, _got) = dir_setup(&exp); - - let dents = WalkDir::new(tmp.path().join("foo")) - .follow_links(true) - .into_iter() - .collect::<Result<Vec<_>, _>>() - .unwrap(); - assert_eq!(1, dents.len()); - assert!(!dents[0].path_is_symlink()); -} - -// See: https://github.com/BurntSushi/walkdir/issues/115 -#[test] -#[cfg(unix)] -fn first_path_is_followed() { - let exp = td("foo", vec![ - td("a", vec![tf("a1"), tf("a2")]), - td("b", vec![tlf("../a/a1", "alink")]), - ]); - let (tmp, _got) = dir_setup(&exp); - - let dents = WalkDir::new(tmp.path().join("foo/b/alink")) - .into_iter() - .collect::<Result<Vec<_>, _>>() - .unwrap(); - assert_eq!(1, dents.len()); - assert!(dents[0].file_type().is_symlink()); - assert!(dents[0].metadata().unwrap().file_type().is_symlink()); - - let dents = WalkDir::new(tmp.path().join("foo/b/alink")) - .follow_links(true) - .into_iter() - .collect::<Result<Vec<_>, _>>() - .unwrap(); - assert_eq!(1, dents.len()); - assert!(!dents[0].file_type().is_symlink()); - assert!(!dents[0].metadata().unwrap().file_type().is_symlink()); -} - -#[test] -#[cfg(unix)] -fn walk_dir_sym_detect_no_follow_no_loop() { - let exp = td("foo", vec![ - td("a", vec![tf("a1"), tf("a2")]), - td("b", vec![tld("../a", "alink")]), - ]); - let (_tmp, got) = dir_setup(&exp); - assert_tree_eq!(exp, got); -} - -#[test] -#[cfg(unix)] -fn walk_dir_sym_follow_dir() { - let actual = td("foo", vec![ - td("a", vec![tf("a1"), tf("a2")]), - td("b", vec![tld("../a", "alink")]), - ]); - let followed = td("foo", vec![ - td("a", vec![tf("a1"), tf("a2")]), - td("b", vec![td("alink", vec![tf("a1"), tf("a2")])]), - ]); - let (_tmp, got) = dir_setup_with(&actual, |wd| wd.follow_links(true)); - assert_tree_eq!(followed, got); -} - -#[test] -#[cfg(unix)] -fn walk_dir_sym_detect_loop() { - let actual = td("foo", vec![ - td("a", vec![tlf("../b", "blink"), tf("a1"), tf("a2")]), - td("b", vec![tlf("../a", "alink")]), - ]); - let tmp = tmpdir(); - actual.create_in(tmp.path()).unwrap(); - let got = WalkDir::new(tmp.path()) - .follow_links(true) - .into_iter() - .collect::<Result<Vec<_>, _>>(); - match got { - Ok(x) => panic!("expected loop error, got no error: {:?}", x), - Err(err @ Error { inner: ErrorInner::Io { .. }, .. }) => { - panic!("expected loop error, got generic IO error: {:?}", err); - } - Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {} - } -} - -#[test] -fn walk_dir_sym_infinite() { - let actual = tlf("a", "a"); - let tmp = tmpdir(); - actual.create_in(tmp.path()).unwrap(); - let got = WalkDir::new(tmp.path()) - .follow_links(true) - .into_iter() - .collect::<Result<Vec<_>, _>>(); - match got { - Ok(x) => panic!("expected IO error, got no error: {:?}", x), - Err(Error { inner: ErrorInner::Loop { .. }, .. }) => { - panic!("expected IO error, but got loop error"); - } - Err(Error { inner: ErrorInner::Io { .. }, .. }) => {} - } -} - -#[test] -fn walk_dir_min_depth_1() { - let exp = td("foo", vec![tf("bar")]); - let (_tmp, got) = dir_setup_with(&exp, |wd| wd.min_depth(1)); - assert_tree_eq!(tf("bar"), got); -} - -#[test] -fn walk_dir_min_depth_2() { - let exp = td("foo", vec![tf("bar"), tf("baz")]); - let tmp = tmpdir(); - exp.create_in(tmp.path()).unwrap(); - let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(2)) - .unwrap().unwrap_dir(); - let got_cf = Tree::from_walk_with_contents_first( - tmp.path(), |wd| wd.min_depth(2)) - .unwrap().unwrap_dir(); - assert_eq!(got, got_cf); - assert_tree_eq!(exp, td("foo", got)); -} - -#[test] -fn walk_dir_min_depth_3() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("xyz")]), - tf("baz"), - ]); - let tmp = tmpdir(); - exp.create_in(tmp.path()).unwrap(); - let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(3)) - .unwrap().unwrap_dir(); - assert_eq!(vec![tf("xyz")], got); - let got_cf = Tree::from_walk_with_contents_first( - tmp.path(), |wd| wd.min_depth(3)) - .unwrap().unwrap_dir(); - assert_eq!(got, got_cf); -} - -#[test] -fn walk_dir_max_depth_1() { - let exp = td("foo", vec![tf("bar")]); - let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1)); - assert_tree_eq!(td("foo", vec![]), got); -} - -#[test] -fn walk_dir_max_depth_2() { - let exp = td("foo", vec![tf("bar"), tf("baz")]); - let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1)); - assert_tree_eq!(td("foo", vec![]), got); -} - -#[test] -fn walk_dir_max_depth_3() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("xyz")]), - tf("baz"), - ]); - let exp_trimmed = td("foo", vec![ - tf("bar"), - td("abc", vec![]), - tf("baz"), - ]); - let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(2)); - assert_tree_eq!(exp_trimmed, got); -} - -#[test] -fn walk_dir_min_max_depth() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("xyz")]), - tf("baz"), - ]); - let tmp = tmpdir(); - exp.create_in(tmp.path()).unwrap(); - let got = Tree::from_walk_with(tmp.path(), - |wd| wd.min_depth(2).max_depth(2)) - .unwrap().unwrap_dir(); - let got_cf = Tree::from_walk_with_contents_first(tmp.path(), - |wd| wd.min_depth(2).max_depth(2)) - .unwrap().unwrap_dir(); - assert_eq!(got, got_cf); - assert_tree_eq!( - td("foo", vec![tf("bar"), td("abc", vec![]), tf("baz")]), - td("foo", got)); -} - -#[test] -fn walk_dir_skip() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("xyz")]), - tf("baz"), - ]); - let tmp = tmpdir(); - exp.create_in(tmp.path()).unwrap(); - let mut got = vec![]; - let mut it = WalkDir::new(tmp.path()).min_depth(1).into_iter(); - loop { - let dent = match it.next().map(|x| x.unwrap()) { - None => break, - Some(dent) => dent, - }; - let name = dent.file_name().to_str().unwrap().to_owned(); - if name == "abc" { - it.skip_current_dir(); - } - got.push(name); - } - got.sort(); - assert_eq!(got, vec!["abc", "bar", "baz", "foo"]); // missing xyz! -} - -#[test] -fn walk_dir_filter() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("fit")]), - tf("faz"), - ]); - let tmp = tmpdir(); - let tmp_path = tmp.path().to_path_buf(); - exp.create_in(tmp.path()).unwrap(); - let it = WalkDir::new(tmp.path()).min_depth(1) - .into_iter() - .filter_entry(move |d| { - let n = d.file_name().to_string_lossy().into_owned(); - !d.file_type().is_dir() - || n.starts_with("f") - || d.path() == &*tmp_path - }); - let mut got = it.map(|d| d.unwrap().file_name().to_str().unwrap().into()) - .collect::<Vec<String>>(); - got.sort(); - assert_eq!(got, vec!["bar", "faz", "foo"]); -} - -#[test] -fn walk_dir_sort() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("fit")]), - tf("faz"), - ]); - let tmp = tmpdir(); - let tmp_path = tmp.path(); - let tmp_len = tmp_path.to_str().unwrap().len(); - exp.create_in(tmp_path).unwrap(); - let it = WalkDir::new(tmp_path) - .sort_by(|a,b| a.file_name().cmp(b.file_name())) - .into_iter(); - let got = it.map(|d| { - let path = d.unwrap(); - let path = &path.path().to_str().unwrap()[tmp_len..]; - path.replace("\\", "/") - }).collect::<Vec<String>>(); - assert_eq!( - got, - ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]); -} - -#[test] -fn walk_dir_sort_small_fd_max() { - let exp = td("foo", vec![ - tf("bar"), - td("abc", vec![tf("fit")]), - tf("faz"), - ]); - let tmp = tmpdir(); - let tmp_path = tmp.path(); - let tmp_len = tmp_path.to_str().unwrap().len(); - exp.create_in(tmp_path).unwrap(); - let it = WalkDir::new(tmp_path) - .max_open(1) - .sort_by(|a,b| a.file_name().cmp(b.file_name())) - .into_iter(); - let got = it.map(|d| { - let path = d.unwrap(); - let path = &path.path().to_str().unwrap()[tmp_len..]; - path.replace("\\", "/") - }).collect::<Vec<String>>(); - assert_eq!( - got, - ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]); -} - -#[test] -fn walk_dir_send_sync_traits() { - use FilterEntry; - - fn assert_send<T: Send>() {} - fn assert_sync<T: Sync>() {} - - assert_send::<WalkDir>(); - assert_sync::<WalkDir>(); - assert_send::<IntoIter>(); - assert_sync::<IntoIter>(); - assert_send::<FilterEntry<IntoIter, u8>>(); - assert_sync::<FilterEntry<IntoIter, u8>>(); -} - -// We cannot mount different volumes for the sake of the test, but -// on Linux systems we can assume that /sys is a mounted volume. -#[test] -#[cfg(target_os = "linux")] -fn walk_dir_stay_on_file_system() { - // If for some reason /sys doesn't exist or isn't a directory, just skip - // this test. - if !Path::new("/sys").is_dir() { - return; - } - - let actual = td("same_file", vec![ - td("a", vec![tld("/sys", "alink")]), - ]); - let unfollowed = td("same_file", vec![ - td("a", vec![tld("/sys", "alink")]), - ]); - let (_tmp, got) = dir_setup_with(&actual, |wd| wd); - assert_tree_eq!(unfollowed, got); - - // Create a symlink to sys and enable following symlinks. If the - // same_file_system option doesn't work, then this probably will hit a - // permission error. Otherwise, it should just skip over the symlink - // completely. - let actual = td("same_file", vec![ - td("a", vec![tld("/sys", "alink")]), - ]); - let followed = td("same_file", vec![ - td("a", vec![td("alink", vec![])]), - ]); - let (_tmp, got) = dir_setup_with(&actual, |wd| { - wd.follow_links(true).same_file_system(true) - }); - assert_tree_eq!(followed, got); -} - diff --git a/src/tests/recursive.rs b/src/tests/recursive.rs new file mode 100644 index 0000000..27d1590 --- /dev/null +++ b/src/tests/recursive.rs @@ -0,0 +1,992 @@ +use std::fs; +use std::path::PathBuf; + +use tests::util::Dir; +use WalkDir; + +#[test] +fn send_sync_traits() { + use {IntoIter, FilterEntry}; + + fn assert_send<T: Send>() {} + fn assert_sync<T: Sync>() {} + + assert_send::<WalkDir>(); + assert_sync::<WalkDir>(); + assert_send::<IntoIter>(); + assert_sync::<IntoIter>(); + assert_send::<FilterEntry<IntoIter, u8>>(); + assert_sync::<FilterEntry<IntoIter, u8>>(); +} + +#[test] +fn empty() { + let dir = Dir::tmp(); + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + assert_eq!(1, r.ents().len()); + let ent = &r.ents()[0]; + assert!(ent.file_type().is_dir()); + assert!(!ent.path_is_symlink()); + assert_eq!(0, ent.depth()); + assert_eq!(dir.path(), ent.path()); + assert_eq!(dir.path().file_name().unwrap(), ent.file_name()); +} + +#[test] +fn empty_follow() { + let dir = Dir::tmp(); + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + assert_eq!(1, r.ents().len()); + let ent = &r.ents()[0]; + assert!(ent.file_type().is_dir()); + assert!(!ent.path_is_symlink()); + assert_eq!(0, ent.depth()); + assert_eq!(dir.path(), ent.path()); + assert_eq!(dir.path().file_name().unwrap(), ent.file_name()); +} + +#[test] +fn empty_file() { + let dir = Dir::tmp(); + dir.touch("a"); + + let wd = WalkDir::new(dir.path().join("a")); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + assert_eq!(1, r.ents().len()); + let ent = &r.ents()[0]; + assert!(ent.file_type().is_file()); + assert!(!ent.path_is_symlink()); + assert_eq!(0, ent.depth()); + assert_eq!(dir.join("a"), ent.path()); + assert_eq!("a", ent.file_name()); +} + +#[test] +fn empty_file_follow() { + let dir = Dir::tmp(); + dir.touch("a"); + + let wd = WalkDir::new(dir.path().join("a")).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + assert_eq!(1, r.ents().len()); + let ent = &r.ents()[0]; + assert!(ent.file_type().is_file()); + assert!(!ent.path_is_symlink()); + assert_eq!(0, ent.depth()); + assert_eq!(dir.join("a"), ent.path()); + assert_eq!("a", ent.file_name()); +} + +#[test] +fn one_dir() { + let dir = Dir::tmp(); + dir.mkdirp("a"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.ents(); + assert_eq!(2, ents.len()); + let ent = &ents[1]; + assert_eq!(dir.join("a"), ent.path()); + assert_eq!(1, ent.depth()); + assert_eq!("a", ent.file_name()); + assert!(ent.file_type().is_dir()); +} + +#[test] +fn one_file() { + let dir = Dir::tmp(); + dir.touch("a"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.ents(); + assert_eq!(2, ents.len()); + let ent = &ents[1]; + assert_eq!(dir.join("a"), ent.path()); + assert_eq!(1, ent.depth()); + assert_eq!("a", ent.file_name()); + assert!(ent.file_type().is_file()); +} + +#[test] +fn one_dir_one_file() { + let dir = Dir::tmp(); + dir.mkdirp("foo"); + dir.touch("foo/a"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("a"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn many_files() { + let dir = Dir::tmp(); + dir.mkdirp("foo"); + dir.touch_all(&["foo/a", "foo/b", "foo/c"]); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("a"), + dir.join("foo").join("b"), + dir.join("foo").join("c"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn many_dirs() { + let dir = Dir::tmp(); + dir.mkdirp("foo/a"); + dir.mkdirp("foo/b"); + dir.mkdirp("foo/c"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("a"), + dir.join("foo").join("b"), + dir.join("foo").join("c"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn many_mixed() { + let dir = Dir::tmp(); + dir.mkdirp("foo/a"); + dir.mkdirp("foo/c"); + dir.mkdirp("foo/e"); + dir.touch_all(&["foo/b", "foo/d", "foo/f"]); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("a"), + dir.join("foo").join("b"), + dir.join("foo").join("c"), + dir.join("foo").join("d"), + dir.join("foo").join("e"), + dir.join("foo").join("f"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn nested() { + let nested = PathBuf::from( + "a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z", + ); + let dir = Dir::tmp(); + dir.mkdirp(&nested); + dir.touch(nested.join("A")); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + dir.join("a/b"), + dir.join("a/b/c"), + dir.join("a/b/c/d"), + dir.join("a/b/c/d/e"), + dir.join("a/b/c/d/e/f"), + dir.join("a/b/c/d/e/f/g"), + dir.join("a/b/c/d/e/f/g/h"), + dir.join("a/b/c/d/e/f/g/h/i"), + dir.join("a/b/c/d/e/f/g/h/i/j"), + dir.join("a/b/c/d/e/f/g/h/i/j/k"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"), + dir.join(&nested).join("A"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn nested_small_max_open() { + let nested = PathBuf::from( + "a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z", + ); + let dir = Dir::tmp(); + dir.mkdirp(&nested); + dir.touch(nested.join("A")); + + let wd = WalkDir::new(dir.path()).max_open(1); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + dir.join("a/b"), + dir.join("a/b/c"), + dir.join("a/b/c/d"), + dir.join("a/b/c/d/e"), + dir.join("a/b/c/d/e/f"), + dir.join("a/b/c/d/e/f/g"), + dir.join("a/b/c/d/e/f/g/h"), + dir.join("a/b/c/d/e/f/g/h/i"), + dir.join("a/b/c/d/e/f/g/h/i/j"), + dir.join("a/b/c/d/e/f/g/h/i/j/k"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y"), + dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"), + dir.join(&nested).join("A"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn siblings() { + let dir = Dir::tmp(); + dir.mkdirp("foo"); + dir.mkdirp("bar"); + dir.touch_all(&["foo/a", "foo/b"]); + dir.touch_all(&["bar/a", "bar/b"]); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("bar"), + dir.join("bar").join("a"), + dir.join("bar").join("b"), + dir.join("foo"), + dir.join("foo").join("a"), + dir.join("foo").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn sym_root_file_nofollow() { + let dir = Dir::tmp(); + dir.touch("a"); + dir.symlink_file("a", "a-link"); + + let wd = WalkDir::new(dir.join("a-link")); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(1, ents.len()); + let link = &ents[0]; + + assert_eq!(dir.join("a-link"), link.path()); + + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(0, link.depth()); + + assert!(link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); +} + +#[test] +fn sym_root_file_follow() { + let dir = Dir::tmp(); + dir.touch("a"); + dir.symlink_file("a", "a-link"); + + let wd = WalkDir::new(dir.join("a-link")).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + let link = &ents[0]; + + assert_eq!(dir.join("a-link"), link.path()); + + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(0, link.depth()); + + assert!(!link.file_type().is_symlink()); + assert!(link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(!link.metadata().unwrap().file_type().is_symlink()); + assert!(link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); +} + +#[test] +fn sym_root_dir_nofollow() { + let dir = Dir::tmp(); + dir.mkdirp("a"); + dir.symlink_dir("a", "a-link"); + dir.touch("a/zzz"); + + let wd = WalkDir::new(dir.join("a-link")); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(2, ents.len()); + let link = &ents[0]; + + assert_eq!(dir.join("a-link"), link.path()); + + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(0, link.depth()); + + assert!(link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); + + let link_zzz = &ents[1]; + assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); + assert!(!link_zzz.path_is_symlink()); +} + +#[test] +fn sym_root_dir_follow() { + let dir = Dir::tmp(); + dir.mkdirp("a"); + dir.symlink_dir("a", "a-link"); + dir.touch("a/zzz"); + + let wd = WalkDir::new(dir.join("a-link")).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(2, ents.len()); + let link = &ents[0]; + + assert_eq!(dir.join("a-link"), link.path()); + + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(0, link.depth()); + + assert!(!link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(link.file_type().is_dir()); + + assert!(!link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(link.metadata().unwrap().is_dir()); + + let link_zzz = &ents[1]; + assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); + assert!(!link_zzz.path_is_symlink()); +} + +#[test] +fn sym_file_nofollow() { + let dir = Dir::tmp(); + dir.touch("a"); + dir.symlink_file("a", "a-link"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(3, ents.len()); + let (src, link) = (&ents[1], &ents[2]); + + assert_eq!(dir.join("a"), src.path()); + assert_eq!(dir.join("a-link"), link.path()); + + assert!(!src.path_is_symlink()); + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(1, src.depth()); + assert_eq!(1, link.depth()); + + assert!(src.file_type().is_file()); + assert!(link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(src.metadata().unwrap().is_file()); + assert!(link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); +} + +#[test] +fn sym_file_follow() { + let dir = Dir::tmp(); + dir.touch("a"); + dir.symlink_file("a", "a-link"); + + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(3, ents.len()); + let (src, link) = (&ents[1], &ents[2]); + + assert_eq!(dir.join("a"), src.path()); + assert_eq!(dir.join("a-link"), link.path()); + + assert!(!src.path_is_symlink()); + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(1, src.depth()); + assert_eq!(1, link.depth()); + + assert!(src.file_type().is_file()); + assert!(!link.file_type().is_symlink()); + assert!(link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(src.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().file_type().is_symlink()); + assert!(link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); +} + +#[test] +fn sym_dir_nofollow() { + let dir = Dir::tmp(); + dir.mkdirp("a"); + dir.symlink_dir("a", "a-link"); + dir.touch("a/zzz"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(4, ents.len()); + let (src, link) = (&ents[1], &ents[3]); + + assert_eq!(dir.join("a"), src.path()); + assert_eq!(dir.join("a-link"), link.path()); + + assert!(!src.path_is_symlink()); + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(1, src.depth()); + assert_eq!(1, link.depth()); + + assert!(src.file_type().is_dir()); + assert!(link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(!link.file_type().is_dir()); + + assert!(src.metadata().unwrap().is_dir()); + assert!(link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(!link.metadata().unwrap().is_dir()); +} + +#[test] +fn sym_dir_follow() { + let dir = Dir::tmp(); + dir.mkdirp("a"); + dir.symlink_dir("a", "a-link"); + dir.touch("a/zzz"); + + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let ents = r.sorted_ents(); + assert_eq!(5, ents.len()); + let (src, link) = (&ents[1], &ents[3]); + + assert_eq!(dir.join("a"), src.path()); + assert_eq!(dir.join("a-link"), link.path()); + + assert!(!src.path_is_symlink()); + assert!(link.path_is_symlink()); + + assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); + + assert_eq!(1, src.depth()); + assert_eq!(1, link.depth()); + + assert!(src.file_type().is_dir()); + assert!(!link.file_type().is_symlink()); + assert!(!link.file_type().is_file()); + assert!(link.file_type().is_dir()); + + assert!(src.metadata().unwrap().is_dir()); + assert!(!link.metadata().unwrap().file_type().is_symlink()); + assert!(!link.metadata().unwrap().is_file()); + assert!(link.metadata().unwrap().is_dir()); + + let (src_zzz, link_zzz) = (&ents[2], &ents[4]); + assert_eq!(dir.join("a").join("zzz"), src_zzz.path()); + assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); + assert!(!src_zzz.path_is_symlink()); + assert!(!link_zzz.path_is_symlink()); +} + +#[test] +fn sym_noloop() { + let dir = Dir::tmp(); + dir.mkdirp("a/b/c"); + dir.symlink_dir("a", "a/b/c/a-link"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + // There's no loop if we aren't following symlinks. + r.assert_no_errors(); + + assert_eq!(5, r.ents().len()); +} + +#[test] +fn sym_loop_detect() { + let dir = Dir::tmp(); + dir.mkdirp("a/b/c"); + dir.symlink_dir("a", "a/b/c/a-link"); + + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + + let (ents, errs) = (r.sorted_ents(), r.errs()); + assert_eq!(4, ents.len()); + assert_eq!(1, errs.len()); + + let err = &errs[0]; + + let expected = dir.join("a/b/c/a-link"); + assert_eq!(Some(&*expected), err.path()); + + let expected = dir.join("a"); + assert_eq!(Some(&*expected), err.loop_ancestor()); + + assert_eq!(4, err.depth()); + assert!(err.io_error().is_none()); +} + +#[test] +fn sym_self_loop_no_error() { + let dir = Dir::tmp(); + dir.symlink_file("a", "a"); + + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + // No errors occur because even though the symlink points to nowhere, it + // is never followed, and thus no error occurs. + r.assert_no_errors(); + assert_eq!(2, r.ents().len()); + + let ent = &r.ents()[1]; + assert_eq!(dir.join("a"), ent.path()); + assert!(ent.path_is_symlink()); + + assert!(ent.file_type().is_symlink()); + assert!(!ent.file_type().is_file()); + assert!(!ent.file_type().is_dir()); + + assert!(ent.metadata().unwrap().file_type().is_symlink()); + assert!(!ent.metadata().unwrap().file_type().is_file()); + assert!(!ent.metadata().unwrap().file_type().is_dir()); +} + +#[test] +fn sym_file_self_loop_io_error() { + let dir = Dir::tmp(); + dir.symlink_file("a", "a"); + + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + + let (ents, errs) = (r.sorted_ents(), r.errs()); + assert_eq!(1, ents.len()); + assert_eq!(1, errs.len()); + + let err = &errs[0]; + + let expected = dir.join("a"); + assert_eq!(Some(&*expected), err.path()); + assert_eq!(1, err.depth()); + assert!(err.loop_ancestor().is_none()); + assert!(err.io_error().is_some()); +} + +#[test] +fn sym_dir_self_loop_io_error() { + let dir = Dir::tmp(); + dir.symlink_dir("a", "a"); + + let wd = WalkDir::new(dir.path()).follow_links(true); + let r = dir.run_recursive(wd); + + let (ents, errs) = (r.sorted_ents(), r.errs()); + assert_eq!(1, ents.len()); + assert_eq!(1, errs.len()); + + let err = &errs[0]; + + let expected = dir.join("a"); + assert_eq!(Some(&*expected), err.path()); + assert_eq!(1, err.depth()); + assert!(err.loop_ancestor().is_none()); + assert!(err.io_error().is_some()); +} + +#[test] +fn min_depth_1() { + let dir = Dir::tmp(); + dir.mkdirp("a/b"); + + let wd = WalkDir::new(dir.path()).min_depth(1); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a"), + dir.join("a").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn min_depth_2() { + let dir = Dir::tmp(); + dir.mkdirp("a/b"); + + let wd = WalkDir::new(dir.path()).min_depth(2); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn max_depth_0() { + let dir = Dir::tmp(); + dir.mkdirp("a/b"); + + let wd = WalkDir::new(dir.path()).max_depth(0); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn max_depth_1() { + let dir = Dir::tmp(); + dir.mkdirp("a/b"); + + let wd = WalkDir::new(dir.path()).max_depth(1); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn max_depth_2() { + let dir = Dir::tmp(); + dir.mkdirp("a/b"); + + let wd = WalkDir::new(dir.path()).max_depth(2); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + dir.join("a").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +// FIXME: This test seems wrong. It should return nothing! +#[test] +fn min_max_depth_diff_nada() { + let dir = Dir::tmp(); + dir.mkdirp("a/b/c"); + + let wd = WalkDir::new(dir.path()).min_depth(3).max_depth(2); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a").join("b").join("c"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn min_max_depth_diff_0() { + let dir = Dir::tmp(); + dir.mkdirp("a/b/c"); + + let wd = WalkDir::new(dir.path()).min_depth(2).max_depth(2); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn min_max_depth_diff_1() { + let dir = Dir::tmp(); + dir.mkdirp("a/b/c"); + + let wd = WalkDir::new(dir.path()).min_depth(1).max_depth(2); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a"), + dir.join("a").join("b"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn contents_first() { + let dir = Dir::tmp(); + dir.touch("a"); + + let wd = WalkDir::new(dir.path()).contents_first(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.join("a"), + dir.path().to_path_buf(), + ]; + assert_eq!(expected, r.paths()); +} + +#[test] +fn skip_current_dir() { + let dir = Dir::tmp(); + dir.mkdirp("foo/bar/baz"); + dir.mkdirp("quux"); + + let mut paths = vec![]; + let mut it = WalkDir::new(dir.path()).into_iter(); + while let Some(result) = it.next() { + let ent = result.unwrap(); + paths.push(ent.path().to_path_buf()); + if ent.file_name() == "bar" { + it.skip_current_dir(); + } + } + paths.sort(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("bar"), + dir.join("quux"), + ]; + assert_eq!(expected, paths); +} + +#[test] +fn filter_entry() { + let dir = Dir::tmp(); + dir.mkdirp("foo/bar/baz/abc"); + dir.mkdirp("quux"); + + let wd = WalkDir::new(dir.path()) + .into_iter() + .filter_entry(|ent| ent.file_name() != "baz"); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("foo"), + dir.join("foo").join("bar"), + dir.join("quux"), + ]; + assert_eq!(expected, r.sorted_paths()); +} + +#[test] +fn sort() { + let dir = Dir::tmp(); + dir.mkdirp("foo/bar/baz/abc"); + dir.mkdirp("quux"); + + let wd = WalkDir::new(dir.path()) + .sort_by(|a,b| a.file_name().cmp(b.file_name()).reverse()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("quux"), + dir.join("foo"), + dir.join("foo").join("bar"), + dir.join("foo").join("bar").join("baz"), + dir.join("foo").join("bar").join("baz").join("abc"), + ]; + assert_eq!(expected, r.paths()); +} + +#[test] +fn sort_max_open() { + let dir = Dir::tmp(); + dir.mkdirp("foo/bar/baz/abc"); + dir.mkdirp("quux"); + + let wd = WalkDir::new(dir.path()) + .max_open(1) + .sort_by(|a,b| a.file_name().cmp(b.file_name()).reverse()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("quux"), + dir.join("foo"), + dir.join("foo").join("bar"), + dir.join("foo").join("bar").join("baz"), + dir.join("foo").join("bar").join("baz").join("abc"), + ]; + assert_eq!(expected, r.paths()); +} + +#[cfg(target_os = "linux")] +#[test] +fn same_file_system() { + use std::path::Path; + + // This test is a little weird since it's not clear whether it's a good + // idea to setup a distinct mounted volume in these tests. Instead, we + // probe for an existing one. + if !Path::new("/sys").is_dir() { + return; + } + + let dir = Dir::tmp(); + dir.touch("a"); + dir.symlink_dir("/sys", "sys-link"); + + // First, do a sanity check that things work without following symlinks. + let wd = WalkDir::new(dir.path()); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + dir.join("sys-link"), + ]; + assert_eq!(expected, r.sorted_paths()); + + // ... now follow symlinks and ensure we don't descend into /sys. + let wd = WalkDir::new(dir.path()) + .same_file_system(true) + .follow_links(true); + let r = dir.run_recursive(wd); + r.assert_no_errors(); + + let expected = vec![ + dir.path().to_path_buf(), + dir.join("a"), + dir.join("sys-link"), + ]; + assert_eq!(expected, r.sorted_paths()); +} diff --git a/src/tests/util.rs b/src/tests/util.rs new file mode 100644 index 0000000..968ee7c --- /dev/null +++ b/src/tests/util.rs @@ -0,0 +1,251 @@ +use std::env; +use std::error; +use std::fs::{self, File}; +use std::io; +use std::path::{Path, PathBuf}; +use std::result; + +use {DirEntry, Error}; + +/// Create an error from a format!-like syntax. +#[macro_export] +macro_rules! err { + ($($tt:tt)*) => { + Box::<error::Error + Send + Sync>::from(format!($($tt)*)) + } +} + +/// A convenient result type alias. +pub type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>; + +/// The result of running a recursive directory iterator on a single directory. +#[derive(Debug)] +pub struct RecursiveResults { + ents: Vec<DirEntry>, + errs: Vec<Error>, +} + +impl RecursiveResults { + /// Return all of the errors encountered during traversal. + pub fn errs(&self) -> &[Error] { + &self.errs + } + + /// Assert that no errors have occurred. + pub fn assert_no_errors(&self) { + assert!( + self.errs.is_empty(), + "expected to find no errors, but found: {:?}", + self.errs + ); + } + + /// Return all the successfully retrieved directory entries in the order + /// in which they were retrieved. + pub fn ents(&self) -> &[DirEntry] { + &self.ents + } + + /// Return all paths from all successfully retrieved directory entries. + /// + /// This does not include paths that correspond to an error. + pub fn paths(&self) -> Vec<PathBuf> { + self.ents.iter().map(|d| d.path().to_path_buf()).collect() + } + + /// Return all the successfully retrieved directory entries, sorted + /// lexicographically by their full file path. + pub fn sorted_ents(&self) -> Vec<DirEntry> { + let mut ents = self.ents.clone(); + ents.sort_by(|e1, e2| e1.path().cmp(e2.path())); + ents + } + + /// Return all paths from all successfully retrieved directory entries, + /// sorted lexicographically. + /// + /// This does not include paths that correspond to an error. + pub fn sorted_paths(&self) -> Vec<PathBuf> { + self.sorted_ents().into_iter().map(|d| d.into_path()).collect() + } +} + +/// A helper for managing a directory in which to run tests. +/// +/// When manipulating paths within this directory, paths are interpreted +/// relative to this directory. +#[derive(Debug)] +pub struct Dir { + dir: TempDir, +} + +impl Dir { + /// Create a new empty temporary directory. + pub fn tmp() -> Dir { + let dir = TempDir::new().unwrap(); + Dir { dir } + } + + /// Return the path to this directory. + pub fn path(&self) -> &Path { + self.dir.path() + } + + /// Return a path joined to the path to this directory. + pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf { + self.path().join(path) + } + + /// Run the given iterator and return the result as a distinct collection + /// of directory entries and errors. + pub fn run_recursive<I>( + &self, + it: I, + ) -> RecursiveResults + where I: IntoIterator<Item=result::Result<DirEntry, Error>> + { + let mut results = RecursiveResults { + ents: vec![], + errs: vec![], + }; + for result in it { + match result { + Ok(ent) => results.ents.push(ent), + Err(err) => results.errs.push(err), + } + } + results + } + + /// Create a directory at the given path, while creating all intermediate + /// directories as needed. + pub fn mkdirp<P: AsRef<Path>>(&self, path: P) { + let full = self.join(path); + fs::create_dir_all(&full) + .map_err(|e| { + err!("failed to create directory {}: {}", full.display(), e) + }) + .unwrap(); + } + + /// Create an empty file at the given path. All ancestor directories must + /// already exists. + pub fn touch<P: AsRef<Path>>(&self, path: P) { + let full = self.join(path); + File::create(&full) + .map_err(|e| { + err!("failed to create file {}: {}", full.display(), e) + }) + .unwrap(); + } + + /// Create empty files at the given paths. All ancestor directories must + /// already exists. + pub fn touch_all<P: AsRef<Path>>(&self, paths: &[P]) { + for p in paths { + self.touch(p); + } + } + + /// Create a file symlink to the given src with the given link name. + pub fn symlink_file<P1: AsRef<Path>, P2: AsRef<Path>>( + &self, + src: P1, + link_name: P2, + ) { + #[cfg(windows)] + fn imp(src: &Path, link_name: &Path) -> io::Result<()> { + use std::os::windows::fs::symlink_file; + symlink_file(src, link_name) + } + + #[cfg(unix)] + fn imp(src: &Path, link_name: &Path) -> io::Result<()> { + use std::os::unix::fs::symlink; + symlink(src, link_name) + } + + let (src, link_name) = (self.join(src), self.join(link_name)); + imp(&src, &link_name) + .map_err(|e| { + err!( + "failed to symlink file {} with target {}: {}", + src.display(), link_name.display(), e + ) + }) + .unwrap() + } + + /// Create a directory symlink to the given src with the given link name. + pub fn symlink_dir<P1: AsRef<Path>, P2: AsRef<Path>>( + &self, + src: P1, + link_name: P2, + ) { + #[cfg(windows)] + fn imp(src: &Path, link_name: &Path) -> io::Result<()> { + use std::os::windows::fs::symlink_dir; + symlink_dir(src, link_name) + } + + #[cfg(unix)] + fn imp(src: &Path, link_name: &Path) -> io::Result<()> { + use std::os::unix::fs::symlink; + symlink(src, link_name) + } + + let (src, link_name) = (self.join(src), self.join(link_name)); + imp(&src, &link_name) + .map_err(|e| { + err!( + "failed to symlink directory {} with target {}: {}", + src.display(), link_name.display(), e + ) + }) + .unwrap() + } +} + +/// A simple wrapper for creating a temporary directory that is automatically +/// deleted when it's dropped. +/// +/// We use this in lieu of tempfile because tempfile brings in too many +/// dependencies. +#[derive(Debug)] +pub struct TempDir(PathBuf); + +impl Drop for TempDir { + fn drop(&mut self) { + fs::remove_dir_all(&self.0).unwrap(); + } +} + +impl TempDir { + /// Create a new empty temporary directory under the system's configured + /// temporary directory. + pub fn new() -> Result<TempDir> { + use std::sync::atomic::{AtomicUsize, Ordering}; + + static TRIES: usize = 100; + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + let tmpdir = env::temp_dir(); + for _ in 0..TRIES { + let count = COUNTER.fetch_add(1, Ordering::SeqCst); + let path = tmpdir.join("rust-walkdir").join(count.to_string()); + if path.is_dir() { + continue; + } + fs::create_dir_all(&path).map_err(|e| { + err!("failed to create {}: {}", path.display(), e) + })?; + return Ok(TempDir(path)); + } + Err(err!("failed to create temp dir after {} tries", TRIES)) + } + + /// Return the underlying path to this temporary directory. + pub fn path(&self) -> &Path { + &self.0 + } +} |