Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/walkdir.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2019-05-05 20:20:59 +0300
committerAndrew Gallant <jamslam@gmail.com>2019-07-20 20:37:00 +0300
commit4235b6357cd14ff172508d579ff11ce8acab0066 (patch)
tree46a2a4e06f11604c82c390e7701d8d320c19ff20
parent011973f170f581e897ce6753693240c32fd4b43a (diff)
tests: rewrite them
This gets rid of a lot of unnecessary infrastructure around maintaining the directory hierarchy in a tree. This was principally used in order to support effective quickcheck tests, but since we dropped quickcheck, we no longer need such things. We know center tests around the Dir type, which makes setting up the tests simpler and easier to understand.
-rw-r--r--src/tests/mod.rs5
-rw-r--r--src/tests/old.rs786
-rw-r--r--src/tests/recursive.rs992
-rw-r--r--src/tests/util.rs251
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
+ }
+}