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};
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 #[test]
520 #[cfg(unix)]
521 fn walk_dir_sym_detect_no_follow_no_loop() {
522     let exp = td("foo", vec![
523         td("a", vec![tf("a1"), tf("a2")]),
524         td("b", vec![tld("../a", "alink")]),
525     ]);
526     let (_tmp, got) = dir_setup(&exp);
527     assert_tree_eq!(exp, got);
528 }
529 
530 #[test]
531 #[cfg(unix)]
532 fn walk_dir_sym_follow_dir() {
533     let actual = td("foo", vec![
534         td("a", vec![tf("a1"), tf("a2")]),
535         td("b", vec![tld("../a", "alink")]),
536     ]);
537     let followed = td("foo", vec![
538         td("a", vec![tf("a1"), tf("a2")]),
539         td("b", vec![td("alink", vec![tf("a1"), tf("a2")])]),
540     ]);
541     let (_tmp, got) = dir_setup_with(&actual, |wd| wd.follow_links(true));
542     assert_tree_eq!(followed, got);
543 }
544 
545 #[test]
546 #[cfg(unix)]
547 fn walk_dir_sym_detect_loop() {
548     let actual = td("foo", vec![
549         td("a", vec![tlf("../b", "blink"), tf("a1"), tf("a2")]),
550         td("b", vec![tlf("../a", "alink")]),
551     ]);
552     let tmp = tmpdir();
553     actual.create_in(tmp.path()).unwrap();
554     let got = WalkDir::new(tmp.path())
555                       .follow_links(true)
556                       .into_iter()
557                       .collect::<Result<Vec<_>, _>>();
558     match got {
559         Ok(x) => panic!("expected loop error, got no error: {:?}", x),
560         Err(err @ Error { inner: ErrorInner::Io { .. }, .. }) => {
561             panic!("expected loop error, got generic IO error: {:?}", err);
562         }
563         Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {}
564     }
565 }
566 
567 #[test]
568 fn walk_dir_sym_infinite() {
569     let actual = tlf("a", "a");
570     let tmp = tmpdir();
571     actual.create_in(tmp.path()).unwrap();
572     let got = WalkDir::new(tmp.path())
573                       .follow_links(true)
574                       .into_iter()
575                       .collect::<Result<Vec<_>, _>>();
576     match got {
577         Ok(x) => panic!("expected IO error, got no error: {:?}", x),
578         Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {
579             panic!("expected IO error, but got loop error");
580         }
581         Err(Error { inner: ErrorInner::Io { .. }, .. }) => {}
582     }
583 }
584 
585 #[test]
586 fn walk_dir_min_depth_1() {
587     let exp = td("foo", vec![tf("bar")]);
588     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.min_depth(1));
589     assert_tree_eq!(tf("bar"), got);
590 }
591 
592 #[test]
593 fn walk_dir_min_depth_2() {
594     let exp = td("foo", vec![tf("bar"), tf("baz")]);
595     let tmp = tmpdir();
596     exp.create_in(tmp.path()).unwrap();
597     let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(2))
598                    .unwrap().unwrap_dir();
599     let got_cf = Tree::from_walk_with_contents_first(
600                     tmp.path(), |wd| wd.min_depth(2))
601                    .unwrap().unwrap_dir();
602     assert_eq!(got, got_cf);
603     assert_tree_eq!(exp, td("foo", got));
604 }
605 
606 #[test]
607 fn walk_dir_min_depth_3() {
608     let exp = td("foo", vec![
609         tf("bar"),
610         td("abc", vec![tf("xyz")]),
611         tf("baz"),
612     ]);
613     let tmp = tmpdir();
614     exp.create_in(tmp.path()).unwrap();
615     let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(3))
616                    .unwrap().unwrap_dir();
617     assert_eq!(vec![tf("xyz")], got);
618     let got_cf = Tree::from_walk_with_contents_first(
619                     tmp.path(), |wd| wd.min_depth(3))
620                    .unwrap().unwrap_dir();
621     assert_eq!(got, got_cf);
622 }
623 
624 #[test]
625 fn walk_dir_max_depth_1() {
626     let exp = td("foo", vec![tf("bar")]);
627     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
628     assert_tree_eq!(td("foo", vec![]), got);
629 }
630 
631 #[test]
632 fn walk_dir_max_depth_2() {
633     let exp = td("foo", vec![tf("bar"), tf("baz")]);
634     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
635     assert_tree_eq!(td("foo", vec![]), got);
636 }
637 
638 #[test]
639 fn walk_dir_max_depth_3() {
640     let exp = td("foo", vec![
641         tf("bar"),
642         td("abc", vec![tf("xyz")]),
643         tf("baz"),
644     ]);
645     let exp_trimmed = td("foo", vec![
646         tf("bar"),
647         td("abc", vec![]),
648         tf("baz"),
649     ]);
650     let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(2));
651     assert_tree_eq!(exp_trimmed, got);
652 }
653 
654 #[test]
655 fn walk_dir_min_max_depth() {
656     let exp = td("foo", vec![
657         tf("bar"),
658         td("abc", vec![tf("xyz")]),
659         tf("baz"),
660     ]);
661     let tmp = tmpdir();
662     exp.create_in(tmp.path()).unwrap();
663     let got = Tree::from_walk_with(tmp.path(),
664                                    |wd| wd.min_depth(2).max_depth(2))
665                    .unwrap().unwrap_dir();
666     let got_cf = Tree::from_walk_with_contents_first(tmp.path(),
667                                    |wd| wd.min_depth(2).max_depth(2))
668                    .unwrap().unwrap_dir();
669     assert_eq!(got, got_cf);
670     assert_tree_eq!(
671         td("foo", vec![tf("bar"), td("abc", vec![]), tf("baz")]),
672         td("foo", got));
673 }
674 
675 #[test]
676 fn walk_dir_skip() {
677     let exp = td("foo", vec![
678         tf("bar"),
679         td("abc", vec![tf("xyz")]),
680         tf("baz"),
681     ]);
682     let tmp = tmpdir();
683     exp.create_in(tmp.path()).unwrap();
684     let mut got = vec![];
685     let mut it = WalkDir::new(tmp.path()).min_depth(1).into_iter();
686     loop {
687         let dent = match it.next().map(|x| x.unwrap()) {
688             None => break,
689             Some(dent) => dent,
690         };
691         let name = dent.file_name().to_str().unwrap().to_owned();
692         if name == "abc" {
693             it.skip_current_dir();
694         }
695         got.push(name);
696     }
697     got.sort();
698     assert_eq!(got, vec!["abc", "bar", "baz", "foo"]); // missing xyz!
699 }
700 
701 #[test]
702 fn walk_dir_filter() {
703     let exp = td("foo", vec![
704         tf("bar"),
705         td("abc", vec![tf("fit")]),
706         tf("faz"),
707     ]);
708     let tmp = tmpdir();
709     let tmp_path = tmp.path().to_path_buf();
710     exp.create_in(tmp.path()).unwrap();
711     let it = WalkDir::new(tmp.path()).min_depth(1)
712                      .into_iter()
713                      .filter_entry(move |d| {
714                          let n = d.file_name().to_string_lossy().into_owned();
715                          !d.file_type().is_dir()
716                          || n.starts_with("f")
717                          || d.path() == &*tmp_path
718                      });
719     let mut got = it.map(|d| d.unwrap().file_name().to_str().unwrap().into())
720                     .collect::<Vec<String>>();
721     got.sort();
722     assert_eq!(got, vec!["bar", "faz", "foo"]);
723 }
724 
725 #[test]
726 fn qc_roundtrip() {
727     fn p(exp: Tree) -> bool {
728         let (_tmp, got) = dir_setup(&exp);
729         exp.canonical() == got.canonical()
730     }
731     QuickCheck::new()
732                .gen(StdGen::new(rand::thread_rng(), 15))
733                .tests(1_000)
734                .max_tests(10_000)
735                .quickcheck(p as fn(Tree) -> bool);
736 }
737 
738 // Same as `qc_roundtrip`, but makes sure `follow_links` doesn't change
739 // the behavior of walking a directory *without* symlinks.
740 #[test]
741 fn qc_roundtrip_no_symlinks_with_follow() {
742     fn p(exp: Tree) -> bool {
743         let (_tmp, got) = dir_setup_with(&exp, |wd| wd.follow_links(true));
744         exp.canonical() == got.canonical()
745     }
746     QuickCheck::new()
747                .gen(StdGen::new(rand::thread_rng(), 15))
748                .tests(1_000)
749                .max_tests(10_000)
750                .quickcheck(p as fn(Tree) -> bool);
751 }
752 
753 #[test]
754 fn walk_dir_sort() {
755     let exp = td("foo", vec![
756         tf("bar"),
757         td("abc", vec![tf("fit")]),
758         tf("faz"),
759     ]);
760     let tmp = tmpdir();
761     let tmp_path = tmp.path();
762     let tmp_len = tmp_path.to_str().unwrap().len();
763     exp.create_in(tmp_path).unwrap();
764     let it = WalkDir::new(tmp_path)
765         .sort_by(|a,b| a.file_name().cmp(b.file_name()))
766         .into_iter();
767     let got = it.map(|d| {
768         let path = d.unwrap();
769         let path = &path.path().to_str().unwrap()[tmp_len..];
770         path.replace("\\", "/")
771     }).collect::<Vec<String>>();
772     assert_eq!(
773         got,
774         ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]);
775 }
776 
777 #[test]
778 fn walk_dir_sort_small_fd_max() {
779     let exp = td("foo", vec![
780         tf("bar"),
781         td("abc", vec![tf("fit")]),
782         tf("faz"),
783     ]);
784     let tmp = tmpdir();
785     let tmp_path = tmp.path();
786     let tmp_len = tmp_path.to_str().unwrap().len();
787     exp.create_in(tmp_path).unwrap();
788     let it = WalkDir::new(tmp_path)
789         .max_open(1)
790         .sort_by(|a,b| a.file_name().cmp(b.file_name()))
791         .into_iter();
792     let got = it.map(|d| {
793         let path = d.unwrap();
794         let path = &path.path().to_str().unwrap()[tmp_len..];
795         path.replace("\\", "/")
796     }).collect::<Vec<String>>();
797     assert_eq!(
798         got,
799         ["", "/foo", "/foo/abc", "/foo/abc/fit", "/foo/bar", "/foo/faz"]);
800 }
801 
802 #[test]
803 fn walk_dir_send_sync_traits() {
804     use FilterEntry;
805 
806     fn assert_send<T: Send>() {}
807     fn assert_sync<T: Sync>() {}
808 
809     assert_send::<WalkDir>();
810     assert_sync::<WalkDir>();
811     assert_send::<IntoIter>();
812     assert_sync::<IntoIter>();
813     assert_send::<FilterEntry<IntoIter, u8>>();
814     assert_sync::<FilterEntry<IntoIter, u8>>();
815 }
816