blob: ddce7958e9d88ed24fdedc2dae0bd5cad9080509 [file] [log] [blame]
#![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 quickcheck::{Arbitrary, Gen, QuickCheck, StdGen};
use rand::{self, Rng, RngCore};
use super::{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 name(&self) -> &Path {
match *self {
Tree::Dir(ref pb, _) => pb,
Tree::File(ref pb) => pb,
Tree::Symlink { ref dst, .. } => dst,
}
}
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)
}
}
}
fn dedup(&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 nodupes: Vec<Tree> = vec![];
for (i, c1) in cs.iter().enumerate() {
if !cs[i+1..].iter().any(|c2| c1.name() == c2.name())
&& !nodupes.iter().any(|c2| c1.name() == c2.name()) {
nodupes.push(c1.dedup());
}
}
Tree::Dir(p.clone(), nodupes)
}
}
}
fn gen<G: Gen>(g: &mut G, depth: usize) -> Tree {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct NonEmptyAscii(String);
impl Arbitrary for NonEmptyAscii {
fn arbitrary<G: Gen>(g: &mut G) -> NonEmptyAscii {
use std::char::from_u32;
let upper_bound = g.size();
// We start with a lower bound of `4` to avoid
// generating the special file name `con` on Windows,
// because such files cannot exist...
let size = g.gen_range(4, upper_bound);
NonEmptyAscii((0..size)
.map(|_| from_u32(g.gen_range(97, 123)).unwrap())
.collect())
}
fn shrink(&self) -> Box<Iterator<Item=NonEmptyAscii>> {
let mut smaller = vec![];
for i in 1..self.0.len() {
let s: String = self.0.chars().skip(i).collect();
smaller.push(NonEmptyAscii(s));
}
Box::new(smaller.into_iter())
}
}
let name = pb(NonEmptyAscii::arbitrary(g).0);
if depth == 0 {
Tree::File(name)
} else {
let children: Vec<Tree> =
(0..g.gen_range(0, 5))
.map(|_| Tree::gen(g, depth-1))
.collect();
Tree::Dir(name, children)
}
}
}
impl Arbitrary for Tree {
fn arbitrary<G: Gen>(g: &mut G) -> Tree {
let depth = g.gen_range(0, 5);
Tree::gen(g, depth).dedup()
}
fn shrink(&self) -> Box<Iterator<Item=Tree>> {
let trees: Box<Iterator<Item=Tree>> = match *self {
Tree::Symlink { .. } => unimplemented!(),
Tree::File(ref path) => {
let s = path.to_string_lossy().into_owned();
Box::new(s.shrink().map(|s| Tree::File(pb(s))))
}
Tree::Dir(ref path, ref children) => {
let s = path.to_string_lossy().into_owned();
if children.is_empty() {
Box::new(s.shrink().map(|s| Tree::Dir(pb(s), vec![])))
} else if children.len() == 1 {
let c = &children[0];
Box::new(Some(c.clone()).into_iter().chain(c.shrink()))
} else {
Box::new(children
.shrink()
.map(move |cs| Tree::Dir(pb(s.clone()), cs)))
}
}
};
Box::new(trees.map(|t| t.dedup()))
}
}
#[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 {
let p = env::temp_dir();
let mut r = rand::thread_rng();
let ret = p.join(&format!("rust-{}", r.next_u32()));
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 qc_roundtrip() {
fn p(exp: Tree) -> bool {
let (_tmp, got) = dir_setup(&exp);
exp.canonical() == got.canonical()
}
QuickCheck::new()
.gen(StdGen::new(rand::thread_rng(), 15))
.tests(1_000)
.max_tests(10_000)
.quickcheck(p as fn(Tree) -> bool);
}
// Same as `qc_roundtrip`, but makes sure `follow_links` doesn't change
// the behavior of walking a directory *without* symlinks.
#[test]
fn qc_roundtrip_no_symlinks_with_follow() {
fn p(exp: Tree) -> bool {
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.follow_links(true));
exp.canonical() == got.canonical()
}
QuickCheck::new()
.gen(StdGen::new(rand::thread_rng(), 15))
.tests(1_000)
.max_tests(10_000)
.quickcheck(p as fn(Tree) -> bool);
}
#[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);
}