1use std::io::{Read, Seek, Write};
5
6use server::{CheckedPermissions, MessageAllowed};
7
8use crate::{messages::*, DirEntry, Error, FileSystem, Location, Metadata, OpenFlags};
9
10pub trait BasicFsPermissions:
13 CheckedPermissions
14 + MessageAllowed<OpenDirMessage>
15 + MessageAllowed<OpenFileMessage>
16 + MessageAllowed<CloseFile>
17 + MessageAllowed<CloseDir>
18 + MessageAllowed<CreateDirMessage>
19 + MessageAllowed<ReadFile>
20 + MessageAllowed<SeekFile>
21 + MessageAllowed<WriteFile>
22 + MessageAllowed<TruncateFile>
23 + MessageAllowed<SetLen>
24 + MessageAllowed<GetMetadata>
25 + MessageAllowed<NextEntry>
26 + MessageAllowed<Flush>
27 + MessageAllowed<FlushFs>
28 + MessageAllowed<Remove>
29 + MessageAllowed<Rename>
30 + MessageAllowed<AtomicCopy>
31 + MessageAllowed<AsyncRead>
32 + MessageAllowed<AsyncWrite>
33 + MessageAllowed<AsyncCopyBlock>
34 + MessageAllowed<SubscribeFilesystemEvent>
35{
36}
37
38impl<P> BasicFsPermissions for P where
39 P: CheckedPermissions
40 + MessageAllowed<OpenDirMessage>
41 + MessageAllowed<OpenFileMessage>
42 + MessageAllowed<CloseFile>
43 + MessageAllowed<CloseDir>
44 + MessageAllowed<CreateDirMessage>
45 + MessageAllowed<ReadFile>
46 + MessageAllowed<SeekFile>
47 + MessageAllowed<WriteFile>
48 + MessageAllowed<TruncateFile>
49 + MessageAllowed<SetLen>
50 + MessageAllowed<GetMetadata>
51 + MessageAllowed<NextEntry>
52 + MessageAllowed<Flush>
53 + MessageAllowed<FlushFs>
54 + MessageAllowed<Remove>
55 + MessageAllowed<Rename>
56 + MessageAllowed<AtomicCopy>
57 + MessageAllowed<AsyncRead>
58 + MessageAllowed<AsyncWrite>
59 + MessageAllowed<AsyncCopyBlock>
60 + MessageAllowed<SubscribeFilesystemEvent>
61{
62}
63
64pub trait FsAdapter {
69 type File: FileAdapter<Self::Permissions>;
70 type Permissions: CheckedPermissions;
71 type DirIter: Iterator<Item = Result<DirEntry, Error>>;
72
73 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error>
74 where
75 Self::Permissions: MessageAllowed<CreateDirMessage>,
76 Self::Permissions: MessageAllowed<CloseDir>;
77
78 fn remove(&self, path: &str, location: Location) -> Result<(), Error>
79 where
80 Self::Permissions: MessageAllowed<Remove>;
81
82 fn atomic_copy(
83 &self,
84 src: &str,
85 dest: &str,
86 rename: Option<String>,
87 location: Location,
88 ) -> Result<(), Error>
89 where
90 Self::Permissions: MessageAllowed<AtomicCopy>;
91
92 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error>
93 where
94 Self::Permissions: MessageAllowed<OpenFileMessage>,
95 Self::Permissions: MessageAllowed<CloseFile>;
96
97 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error>
98 where
99 Self::Permissions: MessageAllowed<OpenDirMessage>,
100 Self::Permissions: MessageAllowed<CloseDir>,
101 Self::Permissions: MessageAllowed<NextEntry>;
102
103 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
104 where
105 Self::Permissions: MessageAllowed<GetMetadata>;
106
107 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error>
108 where
109 Self::Permissions: MessageAllowed<Rename>;
110
111 fn flush(&mut self, location: Location) -> Result<(), Error>
112 where
113 Self::Permissions: MessageAllowed<FlushFs>;
114
115 fn walk_dir(&self, path: &str, location: Location) -> Result<DirWalker<Self>, Error>
116 where
117 Self: Clone,
118 Self::Permissions: MessageAllowed<OpenDirMessage>,
119 Self::Permissions: MessageAllowed<CloseDir>,
120 Self::Permissions: MessageAllowed<NextEntry>,
121 {
122 DirWalker::new(self.clone(), path, location)
123 }
124
125 fn ensure_parent_dir_exists(&self, path: &str, location: Location) -> Result<(), Error>
126 where
127 Self::Permissions: MessageAllowed<CreateDirMessage>,
128 Self::Permissions: MessageAllowed<CloseDir>,
129 {
130 crate::ensure_parent_dir_exists_impl(|dir| self.create_dir(dir, location), path)
131 }
132
133 fn remove_if_exists(&self, path: &str, location: Location) -> Result<(), Error>
134 where
135 Self::Permissions: MessageAllowed<Remove>,
136 {
137 match self.remove(path, location) {
138 Ok(_) => Ok(()),
139 Err(Error::FileNotFound) => Ok(()),
140 Err(e) => Err(e),
141 }
142 }
143}
144
145impl<P> FsAdapter for FileSystem<P>
146where
147 P: CheckedPermissions
148 + MessageAllowed<CloseFile>
149 + MessageAllowed<CloseDir>
150 + MessageAllowed<NextEntry>
151 + MessageAllowed<ReadFile>
152 + MessageAllowed<WriteFile>
153 + MessageAllowed<Flush>
154 + MessageAllowed<SeekFile>,
155{
156 type DirIter = DirIterator<P>;
157 type File = crate::File<P>;
158 type Permissions = P;
159
160 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error>
161 where
162 P: MessageAllowed<CreateDirMessage>,
163 P: MessageAllowed<CloseDir>,
164 {
165 Ok(self.create_dir(path, location).map(|_| ())?)
166 }
167
168 fn remove(&self, path: &str, location: Location) -> Result<(), Error>
169 where
170 P: MessageAllowed<Remove>,
171 {
172 Ok(self.remove(path, location)?)
173 }
174
175 fn atomic_copy(
176 &self,
177 src: &str,
178 dest: &str,
179 rename: Option<String>,
180 location: Location,
181 ) -> Result<(), Error>
182 where
183 P: MessageAllowed<AtomicCopy>,
184 {
185 Ok(self.atomic_copy(src, dest, rename, location)?)
186 }
187
188 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error>
189 where
190 P: MessageAllowed<OpenFileMessage>,
191 P: MessageAllowed<CloseFile>,
192 {
193 Ok(self.open_file(path, location, flags)?)
194 }
195
196 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error>
197 where
198 P: MessageAllowed<OpenDirMessage>,
199 P: MessageAllowed<CloseDir>,
200 P: MessageAllowed<NextEntry>,
201 {
202 let dir = self.open_dir(path, location)?;
203 Ok(DirIterator { dir })
204 }
205
206 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
207 where
208 Self::Permissions: MessageAllowed<GetMetadata>,
209 {
210 Ok(self.metadata(path, location)?)
211 }
212
213 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error>
214 where
215 P: MessageAllowed<Rename>,
216 {
217 Ok(self.rename(src, dest, location)?)
218 }
219
220 fn flush(&mut self, location: Location) -> Result<(), Error>
221 where
222 P: MessageAllowed<FlushFs>,
223 {
224 Ok(FileSystem::flush(self, location)?)
225 }
226}
227
228pub trait FileAdapter<P: CheckedPermissions>: Read + Write + Seek {
229 fn metadata(&self) -> Result<Metadata, Error>
230 where
231 P: MessageAllowed<GetMetadata>;
232
233 fn truncate(&mut self) -> Result<(), Error>
234 where
235 P: MessageAllowed<TruncateFile>;
236
237 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
238 where
239 P: MessageAllowed<SetMtime>;
240
241 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
242 where
243 P: MessageAllowed<AsyncCopyBlock>;
244}
245
246impl<P> FileAdapter<P> for crate::File<P>
247where
248 P: CheckedPermissions
249 + MessageAllowed<CloseFile>
250 + MessageAllowed<ReadFile>
251 + MessageAllowed<WriteFile>
252 + MessageAllowed<Flush>
253 + MessageAllowed<SeekFile>,
254{
255 fn metadata(&self) -> Result<Metadata, Error>
256 where
257 P: MessageAllowed<GetMetadata>,
258 {
259 self.metadata()
260 }
261
262 fn truncate(&mut self) -> Result<(), Error>
263 where
264 P: MessageAllowed<TruncateFile>,
265 {
266 self.truncate()
267 }
268
269 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
270 where
271 P: MessageAllowed<SetMtime>,
272 {
273 self.set_mtime(datetime)
274 }
275
276 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
277 where
278 P: MessageAllowed<AsyncCopyBlock>,
279 {
280 self.copy_block_to(to, len)
281 }
282}
283
284pub struct DirIterator<P: CheckedPermissions + MessageAllowed<CloseDir>> {
285 dir: crate::Dir<P>,
286}
287
288impl<P: CheckedPermissions + MessageAllowed<CloseDir>> Iterator for DirIterator<P>
289where
290 P: MessageAllowed<NextEntry>,
291{
292 type Item = Result<DirEntry, Error>;
293
294 fn next(&mut self) -> Option<Self::Item> {
295 match self.dir.next_entry() {
296 Ok(Some(entry)) => Some(Ok(entry)),
297 Ok(None) => None,
298 Err(e) => Some(Err(e)),
299 }
300 }
301}
302
303pub struct DirWalker<F>
304where
305 F: FsAdapter,
306 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
307{
308 fs: F,
309 current_iter: Option<F::DirIter>,
311 current_path: String,
313 stack: Vec<String>,
315 location: Location,
316}
317
318impl<F> DirWalker<F>
319where
320 F: FsAdapter,
321 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
322{
323 pub fn new(fs: F, path: impl Into<String>, location: Location) -> Result<Self, Error> {
324 let path = path.into();
325 let current_iter = fs.open_dir(&path, location)?;
326
327 Ok(Self { fs, current_iter: Some(current_iter), current_path: path, stack: Vec::new(), location })
328 }
329}
330
331impl<F> Iterator for DirWalker<F>
332where
333 F: FsAdapter,
334 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
335{
336 type Item = Result<(String, DirEntry), Error>;
337
338 fn next(&mut self) -> Option<Self::Item> {
339 loop {
340 if let Some(iter) = &mut self.current_iter {
341 match iter.next() {
342 Some(Ok(entry)) => {
343 if entry.name == "." || entry.name == ".." {
344 continue;
345 }
346
347 let full_path = if self.current_path.is_empty() || self.current_path == "/" {
348 entry.name.clone()
349 } else {
350 format!("{}/{}", self.current_path.trim_end_matches('/'), entry.name)
351 };
352
353 if entry.is_dir {
354 self.stack.push(full_path.clone());
355 }
356
357 return Some(Ok((full_path, entry)));
358 }
359 Some(Err(e)) => {
360 return Some(Err(e));
361 }
362 None => {
363 self.current_iter = None;
364 }
365 }
366 }
367
368 if let Some(next_path) = self.stack.pop() {
370 match self.fs.open_dir(&next_path, self.location) {
371 Ok(iter) => {
372 self.current_path = next_path;
373 self.current_iter = Some(iter);
374 }
375 Err(e) => {
376 return Some(Err(e));
377 }
378 }
379 } else {
380 return None;
381 }
382 }
383 }
384}
385
386#[cfg(feature = "test")]
387pub mod test_utils {
388 use std::collections::HashMap;
389 use std::marker::PhantomData;
390 use std::path::PathBuf;
391 use std::sync::Arc;
392
393 use chrono::{DateTime, Datelike, Local, Timelike};
394 use server::AllPermissions;
395
396 use super::*;
397
398 pub struct TestFile<P: CheckedPermissions> {
400 file: std::fs::File,
401 _phantom: PhantomData<P>,
402 }
403
404 impl<P: CheckedPermissions> TestFile<P> {
405 pub fn new(file: std::fs::File) -> Self { Self { file, _phantom: PhantomData } }
406 }
407
408 impl<P: CheckedPermissions> Read for TestFile<P> {
409 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.file.read(buf) }
410 }
411
412 impl<P: CheckedPermissions> Write for TestFile<P> {
413 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.file.write(buf) }
414
415 fn flush(&mut self) -> std::io::Result<()> { self.file.flush() }
416 }
417
418 impl<P: CheckedPermissions> Seek for TestFile<P> {
419 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { self.file.seek(pos) }
420 }
421
422 impl<P: CheckedPermissions> FileAdapter<P> for TestFile<P> {
423 fn metadata(&self) -> Result<Metadata, Error>
424 where
425 P: MessageAllowed<GetMetadata>,
426 {
427 let metadata: Metadata = self.file.metadata()?.try_into()?;
428 Ok(metadata)
429 }
430
431 fn truncate(&mut self) -> Result<(), Error>
432 where
433 P: MessageAllowed<TruncateFile>,
434 {
435 let pos = self.file.stream_position()?;
436 self.file.set_len(pos)?;
437 Ok(())
438 }
439
440 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
441 where
442 P: MessageAllowed<SetMtime>,
443 {
444 use chrono::{Local, TimeZone};
445
446 let datetime_local = Local
447 .with_ymd_and_hms(
448 datetime.date.year as i32,
449 datetime.date.month as u32,
450 datetime.date.day as u32,
451 datetime.time.hour as u32,
452 datetime.time.min as u32,
453 datetime.time.sec as u32,
454 )
455 .single()
456 .ok_or(Error::Io)?;
457 let system_time: std::time::SystemTime = datetime_local.into();
458
459 self.file.set_modified(system_time)?;
460 Ok(())
461 }
462
463 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
464 where
465 P: MessageAllowed<AsyncCopyBlock>,
466 {
467 use std::io::{Read, Write};
468
469 let mut buf = vec![0u8; len];
470 let bytes_read = self.file.read(&mut buf)?;
471 to.file.write_all(&buf[..bytes_read])?;
472 Ok(bytes_read)
473 }
474 }
475
476 pub struct TestDirIterator {
477 entries: std::vec::IntoIter<std::result::Result<std::fs::DirEntry, std::io::Error>>,
478 }
479
480 impl Iterator for TestDirIterator {
481 type Item = Result<DirEntry, Error>;
482
483 fn next(&mut self) -> Option<Self::Item> {
484 use chrono::Local;
485
486 self.entries.next().map(|entry| {
487 let entry = entry?;
488 let metadata = entry.metadata()?;
489 let modified: chrono::DateTime<Local> = metadata.modified()?.into();
490 let modified = modified.into();
491
492 Ok(DirEntry {
493 name: entry.file_name().to_string_lossy().to_string(),
494 modified,
495 len: metadata.len(),
496 is_dir: metadata.is_dir(),
497 is_file: metadata.is_file(),
498 })
499 })
500 }
501 }
502
503 #[derive(Clone)]
505 pub struct FsTest {
506 _temp_dir: Arc<tempfile::TempDir>,
507 roots: Arc<HashMap<Location, PathBuf>>,
508 }
509
510 impl Default for FsTest {
511 fn default() -> Self {
512 let temp_dir = tempfile::TempDir::new().unwrap();
513 let base = temp_dir.path();
514
515 let mut roots = HashMap::new();
516 roots.insert(Location::EncryptedRoot, base.join("encrypted"));
517 roots.insert(Location::System, base.join("system"));
518 roots.insert(Location::SystemAppData, base.join(crate::SYSTEM_STATE_ROOT));
519 roots.insert(Location::CommonAssets, base.join("common"));
520 roots.insert(Location::AppData, base.join("appdata"));
521 roots.insert(Location::Usb, base.join("usb"));
522 roots.insert(Location::User, base.join("user"));
523 roots.insert(Location::Boot, base.join("boot"));
524 roots.insert(Location::AppResources, base.join("app-resources"));
525
526 for path in roots.values() {
527 std::fs::create_dir_all(path).unwrap();
528 }
529
530 Self { _temp_dir: Arc::new(temp_dir), roots: Arc::new(roots) }
531 }
532 }
533
534 impl FsTest {
535 fn root(&self, location: Location) -> &PathBuf { self.roots.get(&location).unwrap() }
536
537 pub fn write_file(&self, path: &str, contents: &[u8], location: Location) {
538 let parts: Vec<&str> = path.rsplitn(2, '/').collect();
539 if parts.len() == 2 {
540 self.create_dir(parts[1], location).unwrap();
541 }
542 let mut file = self.open_file(path, location, OpenFlags::CREATE).unwrap();
543 file.write_all(contents).unwrap();
544 }
545
546 pub fn read_file_contents(&self, path: &str, location: Location) -> Result<Vec<u8>, Error> {
547 let mut file = self.open_file(path, location, OpenFlags::READ_ONLY)?;
548 let mut contents = Vec::new();
549 file.read_to_end(&mut contents)?;
550 Ok(contents)
551 }
552
553 pub fn print_tree(&self, location: Location) {
556 let root = self.root(location);
557 println!("-----");
558 self.print_tree_recursive(root, 0, None);
559 println!("-----");
560 }
561
562 pub fn print_tree_with_depth(&self, location: Location, max_depth: usize) {
564 let root = self.root(location);
565 println!("-----");
566 self.print_tree_recursive(root, 0, Some(max_depth));
567 println!("-----");
568 }
569
570 fn print_tree_recursive(&self, dir_path: &std::path::Path, depth: usize, max_depth: Option<usize>) {
571 if let Some(max) = max_depth {
572 if depth >= max {
573 return;
574 }
575 }
576
577 let Ok(entries) = std::fs::read_dir(dir_path) else {
578 return;
579 };
580
581 for entry in entries.flatten() {
582 let name = entry.file_name();
583 let name_str = name.to_string_lossy();
584
585 for _ in 0..depth {
586 print!("\t");
587 }
588 println!("{name_str}");
589
590 if entry.path().is_dir() {
591 self.print_tree_recursive(&entry.path(), depth + 1, max_depth);
592 }
593 }
594 }
595 }
596
597 impl TryFrom<std::fs::Metadata> for Metadata {
598 type Error = std::io::Error;
599
600 fn try_from(metadata: std::fs::Metadata) -> Result<Self, Self::Error> {
601 let created: DateTime<Local> = metadata.created()?.into();
602 let accessed: DateTime<Local> = metadata.accessed()?.into();
603 let modified: DateTime<Local> = metadata.modified()?.into();
604
605 let accessed_date = accessed.date_naive();
606 Ok(crate::Metadata {
607 is_dir: metadata.is_dir(),
608 size: metadata.len(),
609 created: created.into(),
610 accessed: crate::Date {
611 year: accessed_date.year() as u16,
612 month: accessed_date.month() as u16,
613 day: accessed_date.day() as u16,
614 },
615 modified: modified.into(),
616 })
617 }
618 }
619
620 impl FsAdapter for FsTest {
621 type DirIter = TestDirIterator;
622 type File = TestFile<AllPermissions>;
623 type Permissions = AllPermissions;
624
625 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error> {
626 let root = self.root(location);
627 std::fs::create_dir_all(root.join(path.trim_start_matches('/')))?;
628 Ok(())
629 }
630
631 fn remove(&self, path: &str, location: Location) -> Result<(), Error> {
632 let root = self.root(location);
633 let full_path = root.join(path.trim_start_matches('/'));
634 if full_path.is_dir() {
635 std::fs::remove_dir_all(&full_path)?;
636 } else {
637 std::fs::remove_file(&full_path)?;
638 }
639 Ok(())
640 }
641
642 fn atomic_copy(
643 &self,
644 src: &str,
645 dest: &str,
646 rename: Option<String>,
647 location: Location,
648 ) -> Result<(), Error> {
649 fn copy_recursive(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error> {
650 if src.is_dir() {
651 std::fs::create_dir(dest)?;
652 for entry in std::fs::read_dir(src)? {
653 let entry = entry?;
654 copy_recursive(&entry.path(), &dest.join(entry.file_name()))?;
655 }
656 } else {
657 std::fs::copy(src, dest)?;
658 }
659 Ok(())
660 }
661 let root = self.root(location);
662 let src_path = root.join(src.trim_start_matches('/'));
663 let dest_path = root.join(dest.trim_start_matches('/'));
664
665 if !dest_path.exists() {
666 return Err(Error::FileNotFound);
667 }
668
669 let final_dest = if let Some(new_name) = rename {
670 dest_path.join(new_name)
671 } else {
672 dest_path.join(src_path.file_name().unwrap())
673 };
674
675 copy_recursive(&src_path, &final_dest)
676 }
677
678 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error> {
679 let root = self.root(location);
680 let full_path = root.join(path.trim_start_matches('/'));
681
682 let file = std::fs::OpenOptions::new()
683 .read(flags.read)
684 .write(flags.write)
685 .create(flags.create)
686 .truncate(flags.create)
687 .open(&full_path)?;
688
689 Ok(TestFile::new(file))
690 }
691
692 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error> {
693 let root = self.root(location);
694 let full_path = root.join(path.trim_start_matches('/'));
695
696 let entries: Vec<_> = std::fs::read_dir(&full_path)?.collect();
697
698 Ok(TestDirIterator { entries: entries.into_iter() })
699 }
700
701 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error> {
702 let root = self.root(location);
703 let src_path = root.join(src.trim_start_matches('/'));
704 let dest_path = root.join(dest.trim_start_matches('/'));
705 std::fs::rename(&src_path, &dest_path)?;
706 Ok(())
707 }
708
709 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
710 where
711 Self::Permissions: MessageAllowed<GetMetadata>,
712 {
713 let root = self.root(location);
714 Ok(std::fs::metadata(root.join(path.trim_start_matches('/')))?.try_into()?)
715 }
716
717 fn flush(&mut self, _location: Location) -> Result<(), Error> { Ok(()) }
718 }
719
720 impl From<chrono::DateTime<Local>> for crate::DateTime {
721 fn from(dt: chrono::DateTime<Local>) -> Self {
722 crate::DateTime {
723 date: crate::Date { year: dt.year() as u16, month: dt.month() as u16, day: dt.day() as u16 },
724 time: crate::Time {
725 hour: dt.hour() as u16,
726 min: dt.minute() as u16,
727 sec: dt.second() as u16,
728 millis: (dt.nanosecond() / 1_000_000) as u16,
729 },
730 }
731 }
732 }
733
734 #[cfg(test)]
735 mod tests {
736 use super::*;
737
738 #[test]
739 fn test_dir_walker() {
740 let fs = FsTest::default();
741 let location = Location::AppData;
742
743 fs.write_file("root.txt", b"root", location);
744 fs.write_file("dir1/file1.txt", b"file1", location);
745 fs.write_file("dir1/file2.txt", b"file2", location);
746 fs.write_file("dir1/subdir/file3.txt", b"file3", location);
747 fs.write_file("dir2/file4.txt", b"file4", location);
748
749 let walker = fs.walk_dir("/", location).unwrap();
750 let mut paths: Vec<String> = walker.map(|r| r.unwrap().0).collect();
751 paths.sort();
752
753 let expected = vec![
754 "dir1",
755 "dir1/file1.txt",
756 "dir1/file2.txt",
757 "dir1/subdir",
758 "dir1/subdir/file3.txt",
759 "dir2",
760 "dir2/file4.txt",
761 "root.txt",
762 ];
763
764 assert_eq!(paths, expected);
765 }
766 }
767}