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:
-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
+ }
+}