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