1 #![cfg_attr(windows, allow(dead_code, unused_imports))]
2 
3 use std::cmp;
4 use std::env;
5 use std::fs::{self, File};
6 use std::io;
7 use std::path::{Path, PathBuf};
8 use std::collections::HashMap;
9 
10 use quickcheck::{Arbitrary, Gen, QuickCheck, StdGen};
11 use rand::{self, Rng, RngCore};
12 
13 use super::{DirEntry, WalkDir, IntoIter, Error, ErrorInner};
14 
15 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
16 enum Tree {
17     Dir(PathBuf, Vec<Tree>),
18     File(PathBuf),
19     Symlink {
20         src: PathBuf,
21         dst: PathBuf,
22         dir: bool,
23     }
24 }
25 
26 impl Tree {
from_walk_with<P, F>( p: P, f: F, ) -> io::Result<Tree> where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir27     fn from_walk_with<P, F>(
28         p: P,
29         f: F,
30     ) -> io::Result<Tree>
31     where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir {
32         let mut stack = vec![Tree::Dir(p.as_ref().to_path_buf(), vec![])];
33         let it: WalkEventIter = f(WalkDir::new(p)).into();
34         for ev in it {
35             match try!(ev) {
36                 WalkEvent::Exit => {
37                     let tree = stack.pop().unwrap();
38                     if stack.is_empty() {
39                         return Ok(tree);
40                     }
41                     stack.last_mut().unwrap().children_mut().push(tree);
42                 }
43                 WalkEvent::Dir(dent) => {
44                     stack.push(Tree::Dir(pb(dent.file_name()), vec![]));
45                 }
46                 WalkEvent::File(dent) => {
47                     let node = if dent.file_type().is_symlink() {
48                         let src = try!(dent.path().read_link());
49                         let dst = pb(dent.file_name());
50                         let dir = dent.path().is_dir();
51                         Tree::Symlink { src: src, dst: dst, dir: dir }
52                     } else {
53                         Tree::File(pb(dent.file_name()))
54                     };
55                     stack.last_mut().unwrap().children_mut().push(node);
56                 }
57             }
58         }
59         assert_eq!(stack.len(), 1);
60         Ok(stack.pop().unwrap())
61     }
62 
from_walk_with_contents_first<P, F>( p: P, f: F, ) -> io::Result<Tree> where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir63     fn from_walk_with_contents_first<P, F>(
64         p: P,
65         f: F,
66     ) -> io::Result<Tree>
67     where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir {
68         let mut contents_of_dir_at_depth = HashMap::new();
69         let mut min_depth = ::std::usize::MAX;
70         let top_level_path = p.as_ref().to_path_buf();
71         for result in f(WalkDir::new(p).contents_first(true)) {
72             let dentry = try!(result);
73 
74             let tree =
75             if dentry.file_type().is_dir() {
76                 let any_contents = contents_of_dir_at_depth.remove(
77                     &(dentry.depth+1));
78             Tree::Dir(pb(dentry.file_name()), any_contents.unwrap_or_default())
79             } else {
80                 if dentry.file_type().is_symlink() {
81                     let src = try!(dentry.path().read_link());
82                     let dst = pb(dentry.file_name());
83                     let dir = dentry.path().is_dir();
84                     Tree::Symlink { src: src, dst: dst, dir: dir }
85                 } else {
86                     Tree::File(pb(dentry.file_name()))
87                 }
88             };
89             contents_of_dir_at_depth.entry(
90                     dentry.depth).or_insert(vec!()).push(tree);
91             min_depth = cmp::min(min_depth, dentry.depth);
92         }
93         Ok(Tree::Dir(top_level_path,
94                 contents_of_dir_at_depth.remove(&min_depth)
95                 .unwrap_or_default()))
96     }
97 
name(&self) -> &Path98     fn name(&self) -> &Path {
99         match *self {
100             Tree::Dir(ref pb, _) => pb,
101             Tree::File(ref pb) => pb,
102             Tree::Symlink { ref dst, .. } => dst,
103         }
104     }
105 
unwrap_singleton(self) -> Tree106     fn unwrap_singleton(self) -> Tree {
107         match self {
108             Tree::File(_) | Tree::Symlink { .. } => {
109                 panic!("cannot unwrap file or link as dir");
110             }
111             Tree::Dir(_, mut childs) => {
112                 assert_eq!(childs.len(), 1);
113                 childs.pop().unwrap()
114             }
115         }
116     }
117 
unwrap_dir(self) -> Vec<Tree>118     fn unwrap_dir(self) -> Vec<Tree> {
119         match self {
120             Tree::File(_) | Tree::Symlink { .. } => {
121                 panic!("cannot unwrap file as dir");
122             }
123             Tree::Dir(_, childs) => childs,
124         }
125     }
126 
children_mut(&mut self) -> &mut Vec<Tree>127     fn children_mut(&mut self) -> &mut Vec<Tree> {
128         match *self {
129             Tree::File(_) | Tree::Symlink { .. } => {
130                 panic!("files do not have children");
131             }
132             Tree::Dir(_, ref mut children) => children,
133         }
134     }
135 
create_in<P: AsRef<Path>>(&self, parent: P) -> io::Result<()>136     fn create_in<P: AsRef<Path>>(&self, parent: P) -> io::Result<()> {
137         let parent = parent.as_ref();
138         match *self {
139             Tree::Symlink { ref src, ref dst, dir } => {
140                 if dir {
141                     try!(soft_link_dir(src, parent.join(dst)));
142                 } else {
143                     try!(soft_link_file(src, parent.join(dst)));
144                 }
145             }
146             Tree::File(ref p) => { try!(File::create(parent.join(p))); }
147             Tree::Dir(ref dir, ref children) => {
148                 try!(fs::create_dir(parent.join(dir)));
149                 for child in children {
150                     try!(child.create_in(parent.join(dir)));
151                 }
152             }
153         }
154         Ok(())
155     }
156 
canonical(&self) -> Tree157     fn canonical(&self) -> Tree {
158         match *self {
159             Tree::Symlink { ref src, ref dst, dir } => {
160                 Tree::Symlink { src: src.clone(), dst: dst.clone(), dir: dir }
161             }
162             Tree::File(ref p) => {
163                 Tree::File(p.clone())
164             }
165             Tree::Dir(ref p, ref cs) => {
166                 let mut cs: Vec<Tree> =
167                     cs.iter().map(|c| c.canonical()).collect();
168                 cs.sort();
169                 Tree::Dir(p.clone(), cs)
170             }
171         }
172     }
173 
dedup(&self) -> Tree174     fn dedup(&self) -> Tree {
175         match *self {
176             Tree::Symlink { ref src, ref dst, dir } => {
177                 Tree::Symlink { src: src.clone(), dst: dst.clone(), dir: dir }
178             }
179             Tree::File(ref p) => {
180                 Tree::File(p.clone())
181             }
182             Tree::Dir(ref p, ref cs) => {
183                 let mut nodupes: Vec<Tree> = vec![];
184                 for (i, c1) in cs.iter().enumerate() {
185                     if !cs[i+1..].iter().any(|c2| c1.name() == c2.name())
186                         && !nodupes.iter().any(|c2| c1.name() == c2.name()) {
187                         nodupes.push(c1.dedup());
188                     }
189                 }
190                 Tree::Dir(p.clone(), nodupes)
191             }
192         }
193     }
194 
gen<G: Gen>(g: &mut G, depth: usize) -> Tree195     fn gen<G: Gen>(g: &mut G, depth: usize) -> Tree {
196         #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
197         struct NonEmptyAscii(String);
198 
199         impl Arbitrary for NonEmptyAscii {
200             fn arbitrary<G: Gen>(g: &mut G) -> NonEmptyAscii {
201                 use std::char::from_u32;
202                 let upper_bound = g.size();
203                 // We start with a lower bound of `4` to avoid
204                 // generating the special file name `con` on Windows,
205                 // because such files cannot exist...
206                 let size = g.gen_range(4, upper_bound);
207                 NonEmptyAscii((0..size)
208                 .map(|_| from_u32(g.gen_range(97, 123)).unwrap())
209                 .collect())
210             }
211 
212             fn shrink(&self) -> Box<Iterator<Item=NonEmptyAscii>> {
213                 let mut smaller = vec![];
214                 for i in 1..self.0.len() {
215                     let s: String = self.0.chars().skip(i).collect();
216                     smaller.push(NonEmptyAscii(s));
217                 }
218                 Box::new(smaller.into_iter())
219             }
220         }
221 
222         let name = pb(NonEmptyAscii::arbitrary(g).0);
223         if depth == 0 {
224             Tree::File(name)
225         } else {
226             let children: Vec<Tree> =
227                 (0..g.gen_range(0, 5))
228                 .map(|_| Tree::gen(g, depth-1))
229                 .collect();
230             Tree::Dir(name, children)
231         }
232     }
233 }
234 
235 impl Arbitrary for Tree {
arbitrary<G: Gen>(g: &mut G) -> Tree236     fn arbitrary<G: Gen>(g: &mut G) -> Tree {
237         let depth = g.gen_range(0, 5);
238         Tree::gen(g, depth).dedup()
239     }
240 
shrink(&self) -> Box<Iterator<Item=Tree>>241     fn shrink(&self) -> Box<Iterator<Item=Tree>> {
242         let trees: Box<Iterator<Item=Tree>> = match *self {
243             Tree::Symlink { .. } => unimplemented!(),
244             Tree::File(ref path) => {
245                 let s = path.to_string_lossy().into_owned();
246                 Box::new(s.shrink().map(|s| Tree::File(pb(s))))
247             }
248             Tree::Dir(ref path, ref children) => {
249                 let s = path.to_string_lossy().into_owned();
250                 if children.is_empty() {
251                     Box::new(s.shrink().map(|s| Tree::Dir(pb(s), vec![])))
252                 } else if children.len() == 1 {
253                     let c = &children[0];
254                     Box::new(Some(c.clone()).into_iter().chain(c.shrink()))
255                 } else {
256                     Box::new(children
257                              .shrink()
258                              .map(move |cs| Tree::Dir(pb(s.clone()), cs)))
259                 }
260             }
261         };
262         Box::new(trees.map(|t| t.dedup()))
263     }
264 }
265 
266 #[derive(Debug)]
267 enum WalkEvent {
268     Dir(DirEntry),
269     File(DirEntry),
270     Exit,
271 }
272 
273 struct WalkEventIter {
274     depth: usize,
275     it: IntoIter,
276     next: Option<Result<DirEntry, Error>>,
277 }
278 
279 impl From<WalkDir> for WalkEventIter {
from(it: WalkDir) -> WalkEventIter280     fn from(it: WalkDir) -> WalkEventIter {
281         WalkEventIter { depth: 0, it: it.into_iter(), next: None }
282     }
283 }
284 
285 impl Iterator for WalkEventIter {
286     type Item = io::Result<WalkEvent>;
287 
next(&mut self) -> Option<io::Result<WalkEvent>>288     fn next(&mut self) -> Option<io::Result<WalkEvent>> {
289         let dent = self.next.take().or_else(|| self.it.next());
290         let depth = match dent {
291             None => 0,
292             Some(Ok(ref dent)) => dent.depth(),
293             Some(Err(ref err)) => err.depth(),
294         };
295         if depth < self.depth {
296             self.depth -= 1;
297             self.next = dent;
298             return Some(Ok(WalkEvent::Exit));
299         }
300         self.depth = depth;
301         match dent {
302             None => None,
303             Some(Err(err)) => Some(Err(From::from(err))),
304             Some(Ok(dent)) => {
305                 if dent.file_type().is_dir() {
306                     self.depth += 1;
307                     Some(Ok(WalkEvent::Dir(dent)))
308                 } else {
309                     Some(Ok(WalkEvent::File(dent)))
310                 }
311             }
312         }
313     }
314 }
315 
316 struct TempDir(PathBuf);
317 
318 impl TempDir {
path<'a>(&'a self) -> &'a Path319     fn path<'a>(&'a self) -> &'a Path {
320         &self.0
321     }
322 }
323 
324 impl Drop for TempDir {
drop(&mut self)325     fn drop(&mut self) {
326         fs::remove_dir_all(&self.0).unwrap();
327     }
328 }
329 
tmpdir() -> TempDir330 fn tmpdir() -> TempDir {
331     let p = env::temp_dir();
332     let mut r = rand::thread_rng();
333     let ret = p.join(&format!("rust-{}", r.next_u32()));
334     fs::create_dir(&ret).unwrap();
335     TempDir(ret)
336 }
337 
dir_setup_with<F>(t: &Tree, f: F) -> (TempDir, Tree) where F: Fn(WalkDir) -> WalkDir338 fn dir_setup_with<F>(t: &Tree, f: F) -> (TempDir, Tree)
339         where F: Fn(WalkDir) -> WalkDir {
340     let tmp = tmpdir();
341     t.create_in(tmp.path()).unwrap();
342     let got = Tree::from_walk_with(tmp.path(), &f).unwrap();
343     let got_cf = Tree::from_walk_with_contents_first(tmp.path(), &f).unwrap();
344     assert_eq!(got, got_cf);
345 
346     (tmp, got.unwrap_singleton().unwrap_singleton())
347 }
348 
dir_setup(t: &Tree) -> (TempDir, Tree)349 fn dir_setup(t: &Tree) -> (TempDir, Tree) {
350     dir_setup_with(t, |wd| wd)
351 }
352 
canon(unix: &str) -> String353 fn canon(unix: &str) -> String {
354     if cfg!(windows) {
355         unix.replace("/", "\\")
356     } else {
357         unix.to_string()
358     }
359 }
360 
361 fn pb<P: AsRef<Path>>(p: P) -> PathBuf { p.as_ref().to_path_buf() }
362 fn td<P: AsRef<Path>>(p: P, cs: Vec<Tree>) -> Tree {
363     Tree::Dir(pb(p), cs)
364 }
365 fn tf<P: AsRef<Path>>(p: P) -> Tree {
366     Tree::File(pb(p))
367 }
368 fn tld<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Tree {
369     Tree::Symlink { src: pb(src), dst: pb(dst), dir: true }
370 }
371 fn tlf<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Tree {
372     Tree::Symlink { src: pb(src), dst: pb(dst), dir: false }
373 }
374 
375 #[cfg(unix)]
376 fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>(
377     src: P,
378     dst: Q,
379 ) -> io::Result<()> {
380     use std::os::unix::fs::symlink;
381     symlink(src, dst)
382 }
383 
384 #[cfg(unix)]
385 fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
386     src: P,
387     dst: Q,
388 ) -> io::Result<()> {
389     soft_link_dir(src, dst)
390 }
391 
392 #[cfg(windows)]
393 fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>(
394     src: P,
395     dst: Q,
396 ) -> io::Result<()> {
397     use std::os::windows::fs::symlink_dir;
398     symlink_dir(src, dst)
399 }
400 
401 #[cfg(windows)]
402 fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
403     src: P,
404     dst: Q,
405 ) -> io::Result<()> {
406     use std::os::windows::fs::symlink_file;
407     symlink_file(src, dst)
408 }
409 
410 macro_rules! assert_tree_eq {
411     ($e1:expr, $e2:expr) => {
412         assert_eq!($e1.canonical(), $e2.canonical());
413     }
414 }
415 
416 #[test]
417 fn walk_dir_1() {
418     let exp = td("foo", vec![]);
419     let (_tmp, got) = dir_setup(&exp);
420     assert_tree_eq!(exp, got);
421 }
422 
423 #[test]
424 fn walk_dir_2() {
425     let exp = tf("foo");
426     let (_tmp, got) = dir_setup(&exp);
427     assert_tree_eq!(exp, got);
428 }
429 
430 #[test]
431 fn walk_dir_3() {
432     let exp = td("foo", vec![tf("bar")]);
433     let (_tmp, got) = dir_setup(&exp);
434     assert_tree_eq!(exp, got);
435 }
436 
437 #[test]
438 fn walk_dir_4() {
439     let exp = td("foo", vec![tf("foo"), tf("bar"), tf("baz")]);
440     let (_tmp, got) = dir_setup(&exp);
441     assert_tree_eq!(exp, got);
442 }
443 
444 #[test]
445 fn walk_dir_5() {
446     let exp = td("foo", vec![td("bar", vec![])]);
447     let (_tmp, got) = dir_setup(&exp);
448     assert_tree_eq!(exp, got);
449 }
450 
451 #[test]
452 fn walk_dir_6() {
453     let exp = td("foo", vec![
454         td("bar", vec![
455            tf("baz"), td("bat", vec![]),
456         ]),
457     ]);
458     let (_tmp, got) = dir_setup(&exp);
459     assert_tree_eq!(exp, got);
460 }
461 
462 #[test]
463 fn walk_dir_7() {
464     let exp = td("foo", vec![
465         td("bar", vec![
466            tf("baz"), td("bat", vec![]),
467         ]),
468         td("a", vec![tf("b"), tf("c"), tf("d")]),
469     ]);
470     let (_tmp, got) = dir_setup(&exp);
471     assert_tree_eq!(exp, got);
472 }
473 
474 #[test]
475 fn walk_dir_sym_1() {
476     let exp = td("foo", vec![tf("bar"), tlf("bar", "baz")]);
477     let (_tmp, got) = dir_setup(&exp);
478     assert_tree_eq!(exp, got);
479 }
480 
481 #[test]
482 fn walk_dir_sym_2() {
483     let exp = td("foo", vec![
484         td("a", vec![tf("a1"), tf("a2")]),
485         tld("a", "alink"),
486     ]);
487     let (_tmp, got) = dir_setup(&exp);
488     assert_tree_eq!(exp, got);
489 }
490 
491 #[test]
492 fn walk_dir_sym_root() {
493     let exp = td("foo", vec![
494         td("bar", vec![tf("a"), tf("b")]),
495         tld("bar", "alink"),
496     ]);
497     let tmp = tmpdir();
498     let tmp_path = tmp.path();
499     let tmp_len = tmp_path.to_str().unwrap().len();
500     exp.create_in(tmp_path).unwrap();
501 
502     let it = WalkDir::new(tmp_path.join("foo").join("alink")).into_iter();
503     let mut got = it
504         .map(|d| d.unwrap().path().to_str().unwrap()[tmp_len+1..].into())
505         .collect::<Vec<String>>();
506     got.sort();
507     assert_eq!(got, vec![
508         canon("foo/alink"), canon("foo/alink/a"), canon("foo/alink/b"),
509     ]);
510 
511     let it = WalkDir::new(tmp_path.join("foo/alink/")).into_iter();
512     let mut got = it
513         .map(|d| d.unwrap().path().to_str().unwrap()[tmp_len+1..].into())
514         .collect::<Vec<String>>();
515     got.sort();
516     assert_eq!(got, vec!["foo/alink/", "foo/alink/a", "foo/alink/b"]);
517 }
518 
519 // See: https://github.com/BurntSushi/ripgrep/issues/984
520 #[test]
521 #[cfg(unix)]
522 fn first_path_not_symlink() {
523     let exp = td("foo", vec![]);
524     let (tmp, _got) = dir_setup(&exp);
525 
526     let dents = WalkDir::new(tmp.path().join("foo"))
527         .into_iter()
528         .collect::<Result<Vec<_>, _>>()
529         .unwrap();
530     assert_eq!(1, dents.len());
531     assert!(!dents[0].path_is_symlink());
532 }
533 
534 // Like first_path_not_symlink, but checks that the first path is not reported
535 // as a symlink even when we are supposed to be following them.
536 #[test]
537 #[cfg(unix)]
538 fn first_path_not_symlink_follow() {
539     let exp = td("foo", vec![]);
540     let (tmp, _got) = dir_setup(&exp);
541 
542     let dents = WalkDir::new(tmp.path().join("foo"))
543         .follow_links(true)
544         .into_iter()
545         .collect::<Result<Vec<_>, _>>()
546         .unwrap();
547     assert_eq!(1, dents.len());
548     assert!(!dents[0].path_is_symlink());
549 }
550 
551 // See: https://github.com/BurntSushi/walkdir/issues/115
552 #[test]
553 #[cfg(unix)]
554 fn first_path_is_followed() {
555     let exp = td("foo", vec![
556         td("a", vec![tf("a1"), tf("a2")]),
557         td("b", vec![tlf("../a/a1", "alink")]),
558     ]);
559     let (tmp, _got) = dir_setup(&exp);
560 
561     let dents = WalkDir::new(tmp.path().join("foo/b/alink"))
562         .into_iter()
563         .collect::<Result<Vec<_>, _>>()
564         .unwrap();
565     assert_eq!(1, dents.len());
566     assert!(dents[0].file_type().is_symlink());
567     assert!(dents[0].metadata().unwrap().file_type().is_symlink());
568 
569     let dents = WalkDir::new(tmp.path().join("foo/b/alink"))
570         .follow_links(true)
571         .into_iter()
572         .collect::<Result<Vec<_>, _>>()
573         .unwrap();
574     assert_eq!(1, dents.len());
575     assert!(!dents[0].file_type().is_symlink());
576     assert!(!dents[0].metadata().unwrap().file_type().is_symlink());
577 }
578 
579 #[test]
580 #[cfg(unix)]
581 fn walk_dir_sym_detect_no_follow_no_loop() {
582     let exp = td("foo", vec![
583         td("a", vec![tf("a1"), tf("a2")]),
584         td("b", vec![tld("../a", "alink")]),
585     ]);
586     let (_tmp, got) = dir_setup(&exp);
587     assert_tree_eq!(exp, got);
588 }
589 
590 #[test]
591 #[cfg(unix)]
592 fn walk_dir_sym_follow_dir() {
593     let actual = td("foo", vec![
594         td("a", vec![tf("a1"), tf("a2")]),
595         td("b", vec![tld("../a", "alink")]),
596     ]);
597     let followed = td("foo", vec![
598         td("a", vec![tf("a1"), tf("a2")]),
599         td("b", vec![td("alink", vec![tf("a1"), tf("a2")])]),
600     ]);
601     let (_tmp, got) = dir_setup_with(&actual, |wd| wd.follow_links(true));
602     assert_tree_eq!(followed, got);
603 }
604 
605 #[test]
606 #[cfg(unix)]
607 fn walk_dir_sym_detect_loop() {
608     let actual = td("foo", vec![
609         td("a", vec![tlf("../b", "blink"), tf("a1"), tf("a2")]),
610         td("b", vec![tlf("../a", "alink")]),
611     ]);
612     let tmp = tmpdir();
613     actual.create_in(tmp.path()).unwrap();
614     let got = WalkDir::new(tmp.path())
615                       .follow_links(true)
616                       .into_iter()
617                       .collect::<Result<Vec<_>, _>>();
618     match got {
619         Ok(x) => panic!("expected loop error, got no error: {:?}", x),
620         Err(err @ Error { inner: ErrorInner::Io { .. }, .. }) => {
621             panic!("expected loop error, got generic IO error: {:?}", err);
622         }
623         Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {}
624     }
625 }
626 
627 #[test]
628 fn walk_dir_sym_infinite() {
629     let actual = tlf("a", "a");
630     let tmp = tmpdir();
631     actual.create_in(tmp.path()).unwrap();
632     let got = WalkDir::new(tmp.path())
633                       .follow_links(true)
634                       .into_iter()
635                       .collect::<Result<Vec<_>, _>>();
636     match got {
637         Ok(x) => panic!("expected IO error, got no error: {:?}", x),
638         Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {
639             panic!("expected IO error, but got loop error");
640         }
641         Err(Error { inner: ErrorInner::Io { .. }, .. }) => {}
642     }
643 }
644 
645 #[test]
646 fn walk_dir_min_depth_1() {
647     let exp = td("foo", vec![tf("bar")]);
648     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.min_depth(1));
649     assert_tree_eq!(tf("bar"), got);
650 }
651 
652 #[test]
653 fn walk_dir_min_depth_2() {
654     let exp = td("foo", vec![tf("bar"), tf("baz")]);
655     let tmp = tmpdir();
656     exp.create_in(tmp.path()).unwrap();
657     let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(2))
658                    .unwrap().unwrap_dir();
659     let got_cf = Tree::from_walk_with_contents_first(
660                     tmp.path(), |wd| wd.min_depth(2))
661                    .unwrap().unwrap_dir();
662     assert_eq!(got, got_cf);
663     assert_tree_eq!(exp, td("foo", got));
664 }
665 
666 #[test]
667 fn walk_dir_min_depth_3() {
668     let exp = td("foo", vec![
669         tf("bar"),
670         td("abc", vec![tf("xyz")]),
671         tf("baz"),
672     ]);
673     let tmp = tmpdir();
674     exp.create_in(tmp.path()).unwrap();
675     let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(3))
676                    .unwrap().unwrap_dir();
677     assert_eq!(vec![tf("xyz")], got);
678     let got_cf = Tree::from_walk_with_contents_first(
679                     tmp.path(), |wd| wd.min_depth(3))
680                    .unwrap().unwrap_dir();
681     assert_eq!(got, got_cf);
682 }
683 
684 #[test]
685 fn walk_dir_max_depth_1() {
686     let exp = td("foo", vec![tf("bar")]);
687     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
688     assert_tree_eq!(td("foo", vec![]), got);
689 }
690 
691 #[test]
692 fn walk_dir_max_depth_2() {
693     let exp = td("foo", vec![tf("bar"), tf("baz")]);
694     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
695     assert_tree_eq!(td("foo", vec![]), got);
696 }
697 
698 #[test]
699 fn walk_dir_max_depth_3() {
700     let exp = td("foo", vec![
701         tf("bar"),
702         td("abc", vec![tf("xyz")]),
703         tf("baz"),
704     ]);
705     let exp_trimmed = td("foo", vec![
706         tf("bar"),
707         td("abc", vec![]),
708         tf("baz"),
709     ]);
710     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(2));
711     assert_tree_eq!(exp_trimmed, got);
712 }
713 
714 #[test]
715 fn walk_dir_min_max_depth() {
716     let exp = td("foo", vec![
717         tf("bar"),
718         td("abc", vec![tf("xyz")]),
719         tf("baz"),
720     ]);
721     let tmp = tmpdir();
722     exp.create_in(tmp.path()).unwrap();
723     let got = Tree::from_walk_with(tmp.path(),
724                                    |wd| wd.min_depth(2).max_depth(2))
725                    .unwrap().unwrap_dir();
726     let got_cf = Tree::from_walk_with_contents_first(tmp.path(),
727                                    |wd| wd.min_depth(2).max_depth(2))
728                    .unwrap().unwrap_dir();
729     assert_eq!(got, got_cf);
730     assert_tree_eq!(
731         td("foo", vec![tf("bar"), td("abc", vec![]), tf("baz")]),
732         td("foo", got));
733 }
734 
735 #[test]
736 fn walk_dir_skip() {
737     let exp = td("foo", vec![
738         tf("bar"),
739         td("abc", vec![tf("xyz")]),
740         tf("baz"),
741     ]);
742     let tmp = tmpdir();
743     exp.create_in(tmp.path()).unwrap();
744     let mut got = vec![];
745     let mut it = WalkDir::new(tmp.path()).min_depth(1).into_iter();
746     loop {
747         let dent = match it.next().map(|x| x.unwrap()) {
748             None => break,
749             Some(dent) => dent,
750         };
751         let name = dent.file_name().to_str().unwrap().to_owned();
752         if name == "abc" {
753             it.skip_current_dir();
754         }
755         got.push(name);
756     }
757     got.sort();
758     assert_eq!(got, vec!["abc", "bar", "baz", "foo"]); // missing xyz!
759 }
760 
761 #[test]
762 fn walk_dir_filter() {
763     let exp = td("foo", vec![
764         tf("bar"),
765         td("abc", vec![tf("fit")]),
766         tf("faz"),
767     ]);
768     let tmp = tmpdir();
769     let tmp_path = tmp.path().to_path_buf();
770     exp.create_in(tmp.path()).unwrap();
771     let it = WalkDir::new(tmp.path()).min_depth(1)
772                      .into_iter()
773                      .filter_entry(move |d| {
774                          let n = d.file_name().to_string_lossy().into_owned();
775                          !d.file_type().is_dir()
776                          || n.starts_with("f")
777                          || d.path() == &*tmp_path
778                      });
779     let mut got = it.map(|d| d.unwrap().file_name().to_str().unwrap().into())
780                     .collect::<Vec<String>>();
781     got.sort();
782     assert_eq!(got, vec!["bar", "faz", "foo"]);
783 }
784 
785 #[test]
786 fn qc_roundtrip() {
787     fn p(exp: Tree) -> bool {
788         let (_tmp, got) = dir_setup(&exp);
789         exp.canonical() == got.canonical()
790     }
791     QuickCheck::new()
792                .gen(StdGen::new(rand::thread_rng(), 15))
793                .tests(1_000)
794                .max_tests(10_000)
795                .quickcheck(p as fn(Tree) -> bool);
796 }
797 
798 // Same as `qc_roundtrip`, but makes sure `follow_links` doesn't change
799 // the behavior of walking a directory *without* symlinks.
800 #[test]
801 fn qc_roundtrip_no_symlinks_with_follow() {
802     fn p(exp: Tree) -> bool {
803         let (_tmp, got) = dir_setup_with(&exp, |wd| wd.follow_links(true));
804         exp.canonical() == got.canonical()
805     }
806     QuickCheck::new()
807                .gen(StdGen::new(rand::thread_rng(), 15))
808                .tests(1_000)
809                .max_tests(10_000)
810                .quickcheck(p as fn(Tree) -> bool);
811 }
812 
813 #[test]
814 fn walk_dir_sort() {
815     let exp = td("foo", vec![
816         tf("bar"),
817         td("abc", vec![tf("fit")]),
818         tf("faz"),
819     ]);
820     let tmp = tmpdir();
821     let tmp_path = tmp.path();
822     let tmp_len = tmp_path.to_str().unwrap().len();
823     exp.create_in(tmp_path).unwrap();
824     let it = WalkDir::new(tmp_path)
825         .sort_by(|a,b| a.file_name().cmp(b.file_name()))
826         .into_iter();
827     let got = it.map(|d| {
828         let path = d.unwrap();
829         let path = &path.path().to_str().unwrap()[tmp_len..];
830         path.replace("\\", "/")
831     }).collect::<Vec<String>>();
832     assert_eq!(
833         got,
834         ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]);
835 }
836 
837 #[test]
838 fn walk_dir_sort_small_fd_max() {
839     let exp = td("foo", vec![
840         tf("bar"),
841         td("abc", vec![tf("fit")]),
842         tf("faz"),
843     ]);
844     let tmp = tmpdir();
845     let tmp_path = tmp.path();
846     let tmp_len = tmp_path.to_str().unwrap().len();
847     exp.create_in(tmp_path).unwrap();
848     let it = WalkDir::new(tmp_path)
849         .max_open(1)
850         .sort_by(|a,b| a.file_name().cmp(b.file_name()))
851         .into_iter();
852     let got = it.map(|d| {
853         let path = d.unwrap();
854         let path = &path.path().to_str().unwrap()[tmp_len..];
855         path.replace("\\", "/")
856     }).collect::<Vec<String>>();
857     assert_eq!(
858         got,
859         ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]);
860 }
861 
862 #[test]
863 fn walk_dir_send_sync_traits() {
864     use FilterEntry;
865 
866     fn assert_send<T: Send>() {}
867     fn assert_sync<T: Sync>() {}
868 
869     assert_send::<WalkDir>();
870     assert_sync::<WalkDir>();
871     assert_send::<IntoIter>();
872     assert_sync::<IntoIter>();
873     assert_send::<FilterEntry<IntoIter, u8>>();
874     assert_sync::<FilterEntry<IntoIter, u8>>();
875 }
876 
877 // We cannot mount different volumes for the sake of the test, but
878 // on Linux systems we can assume that /sys is a mounted volume.
879 #[test]
880 #[cfg(target_os = "linux")]
881 fn walk_dir_stay_on_file_system() {
882     // If for some reason /sys doesn't exist or isn't a directory, just skip
883     // this test.
884     if !Path::new("/sys").is_dir() {
885         return;
886     }
887 
888     let actual = td("same_file", vec![
889         td("a", vec![tld("/sys", "alink")]),
890     ]);
891     let unfollowed = td("same_file", vec![
892         td("a", vec![tld("/sys", "alink")]),
893     ]);
894     let (_tmp, got) = dir_setup_with(&actual, |wd| wd);
895     assert_tree_eq!(unfollowed, got);
896 
897     // Create a symlink to sys and enable following symlinks. If the
898     // same_file_system option doesn't work, then this probably will hit a
899     // permission error. Otherwise, it should just skip over the symlink
900     // completely.
901     let actual = td("same_file", vec![
902         td("a", vec![tld("/sys", "alink")]),
903     ]);
904     let followed = td("same_file", vec![
905         td("a", vec![td("alink", vec![])]),
906     ]);
907     let (_tmp, got) = dir_setup_with(&actual, |wd| {
908         wd.follow_links(true).same_file_system(true)
909     });
910     assert_tree_eq!(followed, got);
911 }
912 
913