virtual_fs/
union_fs.rs

1//! Another implementation of the union that uses paths,
2//! its not as simple as TmpFs. not currently used but was used by
3//! the previoulsy implementation of Deploy - now using TmpFs
4
5use dashmap::DashMap;
6
7use crate::*;
8
9use std::{path::Path, sync::Arc};
10
11#[derive(Debug)]
12pub struct MountPoint {
13    pub path: PathBuf,
14    pub name: String,
15    pub fs: Arc<Box<dyn FileSystem + Send + Sync>>,
16}
17
18impl MountPoint {
19    pub fn fs(&self) -> &(dyn FileSystem + Send + Sync) {
20        self.fs.as_ref()
21    }
22
23    pub fn mount_point_ref(&self) -> MountPointRef<'_> {
24        MountPointRef {
25            path: self.path.clone(),
26            name: self.name.clone(),
27            fs: &self.fs,
28        }
29    }
30}
31
32/// Allows different filesystems of different types
33/// to be mounted at various mount points
34#[derive(Debug, Default)]
35pub struct UnionFileSystem {
36    pub mounts: DashMap<PathBuf, MountPoint>,
37}
38
39impl UnionFileSystem {
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    pub fn clear(&mut self) {
45        self.mounts.clear();
46    }
47
48    fn is_root(&self) -> bool {
49        self.mounts.len() == 1 && self.mounts.contains_key(&PathBuf::from("/"))
50    }
51
52    fn prepare_path(&self, path: &Path) -> PathBuf {
53        if self.is_root() {
54            path.to_owned()
55        } else {
56            path.strip_prefix(PathBuf::from("/"))
57                .unwrap_or(path)
58                .to_owned()
59        }
60    }
61}
62
63impl UnionFileSystem {
64    #[allow(clippy::type_complexity)]
65    fn find_mount(
66        &self,
67        path: PathBuf,
68    ) -> Option<(PathBuf, PathBuf, Arc<Box<dyn FileSystem + Send + Sync>>)> {
69        let mut components = path.components().collect::<Vec<_>>();
70
71        if let Some(c) = components.first().copied() {
72            components.remove(0);
73
74            let sub_path = components.into_iter().collect::<PathBuf>();
75
76            if let Some(mount) = self.mounts.get(&PathBuf::from(c.as_os_str())) {
77                return Some((
78                    PathBuf::from(c.as_os_str()),
79                    PathBuf::from("/").join(sub_path),
80                    mount.fs.clone(),
81                ));
82            }
83        }
84
85        None
86    }
87}
88
89impl FileSystem for UnionFileSystem {
90    fn readlink(&self, path: &Path) -> Result<PathBuf> {
91        let path = self.prepare_path(path);
92
93        if path.as_os_str().is_empty() {
94            Err(FsError::NotAFile)
95        } else {
96            match self.find_mount(path.to_owned()) {
97                Some((_, path, fs)) => fs.readlink(&path),
98                _ => Err(FsError::EntryNotFound),
99            }
100        }
101    }
102
103    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
104        let path = self.prepare_path(path);
105
106        if path.as_os_str().is_empty() {
107            let entries = self
108                .mounts
109                .iter()
110                .map(|i| DirEntry {
111                    path: PathBuf::from("/").join(i.key()),
112                    metadata: Ok(Metadata {
113                        ft: FileType::new_dir(),
114                        accessed: 0,
115                        created: 0,
116                        modified: 0,
117                        len: 0,
118                    }),
119                })
120                .collect::<Vec<_>>();
121
122            Ok(ReadDir::new(entries))
123        } else {
124            match self.find_mount(path.to_owned()) {
125                Some((prefix, path, fs)) => {
126                    let mut entries = fs.read_dir(&path)?;
127
128                    for entry in &mut entries.data {
129                        let path: PathBuf = entry.path.components().skip(1).collect();
130                        entry.path = PathBuf::from("/").join(PathBuf::from(&prefix).join(path));
131                    }
132
133                    Ok(entries)
134                }
135                _ => Err(FsError::EntryNotFound),
136            }
137        }
138    }
139
140    fn create_dir(&self, path: &Path) -> Result<()> {
141        let path = self.prepare_path(path);
142
143        if path.as_os_str().is_empty() {
144            Ok(())
145        } else {
146            match self.find_mount(path.to_owned()) {
147                Some((_, path, fs)) => {
148                    let result = fs.create_dir(&path);
149
150                    if let Err(e) = result {
151                        if e == FsError::AlreadyExists {
152                            return Ok(());
153                        }
154                    }
155
156                    result
157                }
158                _ => Err(FsError::EntryNotFound),
159            }
160        }
161    }
162    fn remove_dir(&self, path: &Path) -> Result<()> {
163        let path = self.prepare_path(path);
164
165        if path.as_os_str().is_empty() {
166            Err(FsError::PermissionDenied)
167        } else {
168            match self.find_mount(path.to_owned()) {
169                Some((_, path, fs)) => fs.remove_dir(&path),
170                _ => Err(FsError::EntryNotFound),
171            }
172        }
173    }
174    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
175        Box::pin(async move {
176            let from = self.prepare_path(from);
177            let to = self.prepare_path(to);
178
179            if from.as_os_str().is_empty() {
180                Err(FsError::PermissionDenied)
181            } else {
182                match self.find_mount(from.to_owned()) {
183                    Some((prefix, path, fs)) => {
184                        let to = to.strip_prefix(prefix).map_err(|_| FsError::InvalidInput)?;
185
186                        let to = PathBuf::from("/").join(to);
187
188                        fs.rename(&path, &to).await
189                    }
190                    _ => Err(FsError::EntryNotFound),
191                }
192            }
193        })
194    }
195    fn metadata(&self, path: &Path) -> Result<Metadata> {
196        let path = self.prepare_path(path);
197
198        if path.as_os_str().is_empty() {
199            Ok(Metadata {
200                ft: FileType::new_dir(),
201                accessed: 0,
202                created: 0,
203                modified: 0,
204                len: 0,
205            })
206        } else {
207            match self.find_mount(path.to_owned()) {
208                Some((_, path, fs)) => fs.metadata(&path),
209                _ => Err(FsError::EntryNotFound),
210            }
211        }
212    }
213    fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
214        let path = self.prepare_path(path);
215
216        if path.as_os_str().is_empty() {
217            Ok(Metadata {
218                ft: FileType::new_dir(),
219                accessed: 0,
220                created: 0,
221                modified: 0,
222                len: 0,
223            })
224        } else {
225            match self.find_mount(path.to_owned()) {
226                Some((_, path, fs)) => fs.symlink_metadata(&path),
227                _ => Err(FsError::EntryNotFound),
228            }
229        }
230    }
231    fn remove_file(&self, path: &Path) -> Result<()> {
232        let path = self.prepare_path(path);
233
234        if path.as_os_str().is_empty() {
235            Err(FsError::NotAFile)
236        } else {
237            match self.find_mount(path.to_owned()) {
238                Some((_, path, fs)) => fs.remove_file(&path),
239                _ => Err(FsError::EntryNotFound),
240            }
241        }
242    }
243    fn new_open_options(&self) -> OpenOptions<'_> {
244        OpenOptions::new(self)
245    }
246
247    fn mount(
248        &self,
249        name: String,
250        path: &Path,
251        fs: Box<dyn FileSystem + Send + Sync>,
252    ) -> Result<()> {
253        let mut components = path.components().collect::<Vec<_>>();
254        if let Some(c) = components.first().copied() {
255            components.remove(0);
256
257            let sub_path = components.into_iter().collect::<PathBuf>();
258
259            if let Some(mount) = self.mounts.get(&PathBuf::from(c.as_os_str())) {
260                return mount.fs.mount(name, sub_path.as_path(), fs);
261            }
262
263            let fs = if sub_path.components().next().is_none() {
264                fs
265            } else {
266                let union = UnionFileSystem::new();
267                union.mount(name.clone(), sub_path.as_path(), fs)?;
268
269                Box::new(union)
270            };
271
272            let fs = Arc::new(fs);
273
274            let mount = MountPoint {
275                path: PathBuf::from(c.as_os_str()),
276                name,
277                fs,
278            };
279
280            self.mounts.insert(PathBuf::from(c.as_os_str()), mount);
281        } else {
282            return Err(FsError::EntryNotFound);
283        }
284
285        Ok(())
286    }
287}
288
289#[derive(Debug)]
290pub struct MountPointRef<'a> {
291    pub path: PathBuf,
292    pub name: String,
293    pub fs: &'a dyn FileSystem,
294}
295
296impl FileOpener for UnionFileSystem {
297    fn open(
298        &self,
299        path: &Path,
300        conf: &OpenOptionsConfig,
301    ) -> Result<Box<dyn VirtualFile + Send + Sync>> {
302        let path = self.prepare_path(path);
303
304        if path.as_os_str().is_empty() {
305            Err(FsError::NotAFile)
306        } else {
307            let parent = path.parent().unwrap();
308            let file_name = path.file_name().unwrap();
309            match self.find_mount(parent.to_owned()) {
310                Some((_, path, fs)) => fs
311                    .new_open_options()
312                    .options(conf.clone())
313                    .open(path.join(file_name)),
314                _ => Err(FsError::EntryNotFound),
315            }
316        }
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use std::{
323        collections::HashSet,
324        path::{Path, PathBuf},
325    };
326
327    use tokio::io::AsyncWriteExt;
328
329    use crate::{FileSystem as FileSystemTrait, FsError, UnionFileSystem, mem_fs};
330
331    use super::{FileOpener, OpenOptionsConfig};
332
333    fn gen_filesystem() -> UnionFileSystem {
334        let union = UnionFileSystem::new();
335        let a = mem_fs::FileSystem::default();
336        let b = mem_fs::FileSystem::default();
337        let c = mem_fs::FileSystem::default();
338        let d = mem_fs::FileSystem::default();
339        let e = mem_fs::FileSystem::default();
340        let f = mem_fs::FileSystem::default();
341        let g = mem_fs::FileSystem::default();
342        let h = mem_fs::FileSystem::default();
343
344        union
345            .mount(
346                "mem_fs_1".to_string(),
347                PathBuf::from("/test_new_filesystem").as_path(),
348                Box::new(a),
349            )
350            .unwrap();
351        union
352            .mount(
353                "mem_fs_2".to_string(),
354                PathBuf::from("/test_create_dir").as_path(),
355                Box::new(b),
356            )
357            .unwrap();
358        union
359            .mount(
360                "mem_fs_3".to_string(),
361                PathBuf::from("/test_remove_dir").as_path(),
362                Box::new(c),
363            )
364            .unwrap();
365        union
366            .mount(
367                "mem_fs_4".to_string(),
368                PathBuf::from("/test_rename").as_path(),
369                Box::new(d),
370            )
371            .unwrap();
372        union
373            .mount(
374                "mem_fs_5".to_string(),
375                PathBuf::from("/test_metadata").as_path(),
376                Box::new(e),
377            )
378            .unwrap();
379        union
380            .mount(
381                "mem_fs_6".to_string(),
382                PathBuf::from("/test_remove_file").as_path(),
383                Box::new(f),
384            )
385            .unwrap();
386        union
387            .mount(
388                "mem_fs_6".to_string(),
389                PathBuf::from("/test_readdir").as_path(),
390                Box::new(g),
391            )
392            .unwrap();
393        union
394            .mount(
395                "mem_fs_6".to_string(),
396                PathBuf::from("/test_canonicalize").as_path(),
397                Box::new(h),
398            )
399            .unwrap();
400
401        union
402    }
403
404    fn gen_nested_filesystem() -> UnionFileSystem {
405        let union = UnionFileSystem::new();
406        let a = mem_fs::FileSystem::default();
407        a.open(
408            &PathBuf::from("/data-a.txt"),
409            &OpenOptionsConfig {
410                read: true,
411                write: true,
412                create_new: false,
413                create: true,
414                append: false,
415                truncate: false,
416            },
417        )
418        .unwrap();
419        let b = mem_fs::FileSystem::default();
420        b.open(
421            &PathBuf::from("/data-b.txt"),
422            &OpenOptionsConfig {
423                read: true,
424                write: true,
425                create_new: false,
426                create: true,
427                append: false,
428                truncate: false,
429            },
430        )
431        .unwrap();
432
433        union
434            .mount(
435                "mem_fs_1".to_string(),
436                PathBuf::from("/app/a").as_path(),
437                Box::new(a),
438            )
439            .unwrap();
440        union
441            .mount(
442                "mem_fs_2".to_string(),
443                PathBuf::from("/app/b").as_path(),
444                Box::new(b),
445            )
446            .unwrap();
447
448        union
449    }
450
451    #[tokio::test]
452    async fn test_nested_read_dir() {
453        let fs = gen_nested_filesystem();
454
455        let root_contents: Vec<PathBuf> = fs
456            .read_dir(&PathBuf::from("/"))
457            .unwrap()
458            .map(|e| e.unwrap().path.clone())
459            .collect();
460        assert_eq!(root_contents, vec![PathBuf::from("/app")]);
461
462        let app_contents: HashSet<PathBuf> = fs
463            .read_dir(&PathBuf::from("/app"))
464            .unwrap()
465            .map(|e| e.unwrap().path)
466            .collect();
467        assert_eq!(
468            app_contents,
469            HashSet::from_iter([PathBuf::from("/app/a"), PathBuf::from("/app/b")].into_iter())
470        );
471
472        let a_contents: Vec<PathBuf> = fs
473            .read_dir(&PathBuf::from("/app/a"))
474            .unwrap()
475            .map(|e| e.unwrap().path.clone())
476            .collect();
477        assert_eq!(a_contents, vec![PathBuf::from("/app/a/data-a.txt")]);
478
479        let b_contents: Vec<PathBuf> = fs
480            .read_dir(&PathBuf::from("/app/b"))
481            .unwrap()
482            .map(|e| e.unwrap().path)
483            .collect();
484        assert_eq!(b_contents, vec![PathBuf::from("/app/b/data-b.txt")]);
485    }
486
487    #[tokio::test]
488    async fn test_nested_metadata() {
489        let fs = gen_nested_filesystem();
490
491        assert!(fs.metadata(&PathBuf::from("/")).is_ok());
492        assert!(fs.metadata(&PathBuf::from("/app")).is_ok());
493        assert!(fs.metadata(&PathBuf::from("/app/a")).is_ok());
494        assert!(fs.metadata(&PathBuf::from("/app/b")).is_ok());
495        assert!(fs.metadata(&PathBuf::from("/app/a/data-a.txt")).is_ok());
496        assert!(fs.metadata(&PathBuf::from("/app/b/data-b.txt")).is_ok());
497    }
498
499    #[tokio::test]
500    async fn test_nested_symlink_metadata() {
501        let fs = gen_nested_filesystem();
502
503        assert!(fs.symlink_metadata(&PathBuf::from("/")).is_ok());
504        assert!(fs.symlink_metadata(&PathBuf::from("/app")).is_ok());
505        assert!(fs.symlink_metadata(&PathBuf::from("/app/a")).is_ok());
506        assert!(fs.symlink_metadata(&PathBuf::from("/app/b")).is_ok());
507        assert!(
508            fs.symlink_metadata(&PathBuf::from("/app/a/data-a.txt"))
509                .is_ok()
510        );
511        assert!(
512            fs.symlink_metadata(&PathBuf::from("/app/b/data-b.txt"))
513                .is_ok()
514        );
515    }
516
517    #[tokio::test]
518    async fn test_new_filesystem() {
519        let fs = gen_filesystem();
520        assert!(
521            fs.read_dir(Path::new("/test_new_filesystem")).is_ok(),
522            "hostfs can read root"
523        );
524        let mut file_write = fs
525            .new_open_options()
526            .read(true)
527            .write(true)
528            .create_new(true)
529            .open(Path::new("/test_new_filesystem/foo2.txt"))
530            .unwrap();
531        file_write.write_all(b"hello").await.unwrap();
532        let _ = std::fs::remove_file("/test_new_filesystem/foo2.txt");
533    }
534
535    #[tokio::test]
536    async fn test_create_dir() {
537        let fs = gen_filesystem();
538
539        assert_eq!(fs.create_dir(Path::new("/")), Ok(()));
540
541        assert_eq!(fs.create_dir(Path::new("/test_create_dir")), Ok(()));
542
543        assert_eq!(
544            fs.create_dir(Path::new("/test_create_dir/foo")),
545            Ok(()),
546            "creating a directory",
547        );
548
549        let cur_dir = read_dir_names(&fs, "/test_create_dir");
550
551        if !cur_dir.contains(&"foo".to_string()) {
552            panic!("cur_dir does not contain foo: {cur_dir:#?}");
553        }
554
555        assert!(
556            cur_dir.contains(&"foo".to_string()),
557            "the root is updated and well-defined"
558        );
559
560        assert_eq!(
561            fs.create_dir(Path::new("/test_create_dir/foo/bar")),
562            Ok(()),
563            "creating a sub-directory",
564        );
565
566        let foo_dir = read_dir_names(&fs, "/test_create_dir/foo");
567
568        assert!(
569            foo_dir.contains(&"bar".to_string()),
570            "the foo directory is updated and well-defined"
571        );
572
573        let bar_dir = read_dir_names(&fs, "/test_create_dir/foo/bar");
574
575        assert!(
576            bar_dir.is_empty(),
577            "the foo directory is updated and well-defined"
578        );
579        let _ = fs_extra::remove_items(&["/test_create_dir"]);
580    }
581
582    #[tokio::test]
583    async fn test_remove_dir() {
584        let fs = gen_filesystem();
585
586        assert_eq!(
587            fs.remove_dir(Path::new("/")),
588            Err(FsError::PermissionDenied),
589            "cannot remove the root directory",
590        );
591
592        assert_eq!(
593            fs.remove_dir(Path::new("/foo")),
594            Err(FsError::EntryNotFound),
595            "cannot remove a directory that doesn't exist",
596        );
597
598        assert_eq!(fs.create_dir(Path::new("/test_remove_dir")), Ok(()));
599
600        assert_eq!(
601            fs.create_dir(Path::new("/test_remove_dir/foo")),
602            Ok(()),
603            "creating a directory",
604        );
605
606        assert_eq!(
607            fs.create_dir(Path::new("/test_remove_dir/foo/bar")),
608            Ok(()),
609            "creating a sub-directory",
610        );
611
612        assert!(
613            read_dir_names(&fs, "/test_remove_dir/foo").contains(&"bar".to_string()),
614            "./foo/bar exists"
615        );
616
617        assert_eq!(
618            fs.remove_dir(Path::new("/test_remove_dir/foo")),
619            Err(FsError::DirectoryNotEmpty),
620            "removing a directory that has children",
621        );
622
623        assert_eq!(
624            fs.remove_dir(Path::new("/test_remove_dir/foo/bar")),
625            Ok(()),
626            "removing a sub-directory",
627        );
628
629        assert_eq!(
630            fs.remove_dir(Path::new("/test_remove_dir/foo")),
631            Ok(()),
632            "removing a directory",
633        );
634
635        assert!(
636            !read_dir_names(&fs, "/test_remove_dir").contains(&"foo".to_string()),
637            "the foo directory still exists"
638        );
639    }
640
641    fn read_dir_names(fs: &dyn crate::FileSystem, path: &str) -> Vec<String> {
642        fs.read_dir(Path::new(path))
643            .unwrap()
644            .filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
645            .collect::<Vec<_>>()
646    }
647
648    #[tokio::test]
649    async fn test_rename() {
650        let fs = gen_filesystem();
651
652        assert_eq!(
653            fs.rename(Path::new("/"), Path::new("/bar")).await,
654            Err(FsError::PermissionDenied),
655            "renaming a directory that has no parent",
656        );
657        assert_eq!(
658            fs.rename(Path::new("/foo"), Path::new("/")).await,
659            Err(FsError::EntryNotFound),
660            "renaming to a directory that has no parent",
661        );
662
663        assert_eq!(fs.create_dir(Path::new("/test_rename")), Ok(()));
664        assert_eq!(fs.create_dir(Path::new("/test_rename/foo")), Ok(()));
665        assert_eq!(fs.create_dir(Path::new("/test_rename/foo/qux")), Ok(()));
666
667        assert_eq!(
668            fs.rename(
669                Path::new("/test_rename/foo"),
670                Path::new("/test_rename/bar/baz")
671            )
672            .await,
673            Err(FsError::EntryNotFound),
674            "renaming to a directory that has parent that doesn't exist",
675        );
676
677        assert_eq!(fs.create_dir(Path::new("/test_rename/bar")), Ok(()));
678
679        assert_eq!(
680            fs.rename(Path::new("/test_rename/foo"), Path::new("/test_rename/bar"))
681                .await,
682            Ok(()),
683            "renaming to a directory that has parent that exists",
684        );
685
686        assert!(
687            fs.new_open_options()
688                .write(true)
689                .create_new(true)
690                .open(Path::new("/test_rename/bar/hello1.txt"))
691                .is_ok(),
692            "creating a new file (`hello1.txt`)",
693        );
694        assert!(
695            fs.new_open_options()
696                .write(true)
697                .create_new(true)
698                .open(Path::new("/test_rename/bar/hello2.txt"))
699                .is_ok(),
700            "creating a new file (`hello2.txt`)",
701        );
702
703        let cur_dir = read_dir_names(&fs, "/test_rename");
704
705        assert!(
706            !cur_dir.contains(&"foo".to_string()),
707            "the foo directory still exists"
708        );
709
710        assert!(
711            cur_dir.contains(&"bar".to_string()),
712            "the bar directory still exists"
713        );
714
715        let bar_dir = read_dir_names(&fs, "/test_rename/bar");
716
717        if !bar_dir.contains(&"qux".to_string()) {
718            println!("qux does not exist: {bar_dir:?}")
719        }
720
721        let qux_dir = read_dir_names(&fs, "/test_rename/bar/qux");
722
723        assert!(qux_dir.is_empty(), "the qux directory is empty");
724
725        assert!(
726            read_dir_names(&fs, "/test_rename/bar").contains(&"hello1.txt".to_string()),
727            "the /bar/hello1.txt file exists"
728        );
729
730        assert!(
731            read_dir_names(&fs, "/test_rename/bar").contains(&"hello2.txt".to_string()),
732            "the /bar/hello2.txt file exists"
733        );
734
735        assert_eq!(
736            fs.create_dir(Path::new("/test_rename/foo")),
737            Ok(()),
738            "create ./foo again",
739        );
740
741        assert_eq!(
742            fs.rename(
743                Path::new("/test_rename/bar/hello2.txt"),
744                Path::new("/test_rename/foo/world2.txt")
745            )
746            .await,
747            Ok(()),
748            "renaming (and moving) a file",
749        );
750
751        assert_eq!(
752            fs.rename(
753                Path::new("/test_rename/foo"),
754                Path::new("/test_rename/bar/baz")
755            )
756            .await,
757            Ok(()),
758            "renaming a directory",
759        );
760
761        assert_eq!(
762            fs.rename(
763                Path::new("/test_rename/bar/hello1.txt"),
764                Path::new("/test_rename/bar/world1.txt")
765            )
766            .await,
767            Ok(()),
768            "renaming a file (in the same directory)",
769        );
770
771        assert!(
772            read_dir_names(&fs, "/test_rename").contains(&"bar".to_string()),
773            "./bar exists"
774        );
775
776        assert!(
777            read_dir_names(&fs, "/test_rename/bar").contains(&"baz".to_string()),
778            "/bar/baz exists"
779        );
780        assert!(
781            !read_dir_names(&fs, "/test_rename").contains(&"foo".to_string()),
782            "foo does not exist anymore"
783        );
784        assert!(
785            read_dir_names(&fs, "/test_rename/bar/baz").contains(&"world2.txt".to_string()),
786            "/bar/baz/world2.txt exists"
787        );
788        assert!(
789            read_dir_names(&fs, "/test_rename/bar").contains(&"world1.txt".to_string()),
790            "/bar/world1.txt (ex hello1.txt) exists"
791        );
792        assert!(
793            !read_dir_names(&fs, "/test_rename/bar").contains(&"hello1.txt".to_string()),
794            "hello1.txt was moved"
795        );
796        assert!(
797            !read_dir_names(&fs, "/test_rename/bar").contains(&"hello2.txt".to_string()),
798            "hello2.txt was moved"
799        );
800        assert!(
801            read_dir_names(&fs, "/test_rename/bar/baz").contains(&"world2.txt".to_string()),
802            "world2.txt was moved to the correct place"
803        );
804
805        let _ = fs_extra::remove_items(&["/test_rename"]);
806    }
807
808    #[tokio::test]
809    async fn test_metadata() {
810        use std::thread::sleep;
811        use std::time::Duration;
812
813        let fs = gen_filesystem();
814
815        let root_metadata = fs.metadata(Path::new("/test_metadata")).unwrap();
816
817        assert!(root_metadata.ft.dir);
818        assert_eq!(root_metadata.accessed, root_metadata.created);
819        assert_eq!(root_metadata.modified, root_metadata.created);
820        assert!(root_metadata.modified > 0);
821
822        assert_eq!(fs.create_dir(Path::new("/test_metadata/foo")), Ok(()));
823
824        let foo_metadata = fs.metadata(Path::new("/test_metadata/foo"));
825        assert!(foo_metadata.is_ok());
826        let foo_metadata = foo_metadata.unwrap();
827
828        assert!(foo_metadata.ft.dir);
829        assert!(foo_metadata.accessed == foo_metadata.created);
830        assert!(foo_metadata.modified == foo_metadata.created);
831        assert!(foo_metadata.modified > 0);
832
833        sleep(Duration::from_secs(3));
834
835        assert_eq!(
836            fs.rename(
837                Path::new("/test_metadata/foo"),
838                Path::new("/test_metadata/bar")
839            )
840            .await,
841            Ok(())
842        );
843
844        let bar_metadata = fs.metadata(Path::new("/test_metadata/bar")).unwrap();
845        assert!(bar_metadata.ft.dir);
846        assert!(bar_metadata.accessed == foo_metadata.accessed);
847        assert!(bar_metadata.created == foo_metadata.created);
848        assert!(bar_metadata.modified > foo_metadata.modified);
849
850        let root_metadata = fs.metadata(Path::new("/test_metadata/bar")).unwrap();
851        assert!(
852            root_metadata.modified > foo_metadata.modified,
853            "the parent modified time was updated"
854        );
855
856        let _ = fs_extra::remove_items(&["/test_metadata"]);
857    }
858
859    #[tokio::test]
860    async fn test_remove_file() {
861        let fs = gen_filesystem();
862
863        assert!(
864            fs.new_open_options()
865                .write(true)
866                .create_new(true)
867                .open(Path::new("/test_remove_file/foo.txt"))
868                .is_ok(),
869            "creating a new file",
870        );
871
872        assert!(read_dir_names(&fs, "/test_remove_file").contains(&"foo.txt".to_string()));
873
874        assert_eq!(
875            fs.remove_file(Path::new("/test_remove_file/foo.txt")),
876            Ok(()),
877            "removing a file that exists",
878        );
879
880        assert!(!read_dir_names(&fs, "/test_remove_file").contains(&"foo.txt".to_string()));
881
882        assert_eq!(
883            fs.remove_file(Path::new("/test_remove_file/foo.txt")),
884            Err(FsError::EntryNotFound),
885            "removing a file that doesn't exists",
886        );
887
888        let _ = fs_extra::remove_items(&["./test_remove_file"]);
889    }
890
891    #[tokio::test]
892    async fn test_readdir() {
893        let fs = gen_filesystem();
894
895        assert_eq!(
896            fs.create_dir(Path::new("/test_readdir/foo")),
897            Ok(()),
898            "creating `foo`"
899        );
900        assert_eq!(
901            fs.create_dir(Path::new("/test_readdir/foo/sub")),
902            Ok(()),
903            "creating `sub`"
904        );
905        assert_eq!(
906            fs.create_dir(Path::new("/test_readdir/bar")),
907            Ok(()),
908            "creating `bar`"
909        );
910        assert_eq!(
911            fs.create_dir(Path::new("/test_readdir/baz")),
912            Ok(()),
913            "creating `bar`"
914        );
915        assert!(
916            fs.new_open_options()
917                .write(true)
918                .create_new(true)
919                .open(Path::new("/test_readdir/a.txt"))
920                .is_ok(),
921            "creating `a.txt`",
922        );
923        assert!(
924            fs.new_open_options()
925                .write(true)
926                .create_new(true)
927                .open(Path::new("/test_readdir/b.txt"))
928                .is_ok(),
929            "creating `b.txt`",
930        );
931
932        println!("fs: {fs:?}");
933
934        let readdir = fs.read_dir(Path::new("/test_readdir"));
935
936        assert!(readdir.is_ok(), "reading the directory `/test_readdir/`");
937
938        let mut readdir = readdir.unwrap();
939
940        let next = readdir.next().unwrap().unwrap();
941        assert!(next.path.ends_with("foo"), "checking entry #1");
942        println!("entry 1: {next:#?}");
943        assert!(next.file_type().unwrap().is_dir(), "checking entry #1");
944
945        let next = readdir.next().unwrap().unwrap();
946        assert!(next.path.ends_with("bar"), "checking entry #2");
947        assert!(next.file_type().unwrap().is_dir(), "checking entry #2");
948
949        let next = readdir.next().unwrap().unwrap();
950        assert!(next.path.ends_with("baz"), "checking entry #3");
951        assert!(next.file_type().unwrap().is_dir(), "checking entry #3");
952
953        let next = readdir.next().unwrap().unwrap();
954        assert!(next.path.ends_with("a.txt"), "checking entry #2");
955        assert!(next.file_type().unwrap().is_file(), "checking entry #4");
956
957        let next = readdir.next().unwrap().unwrap();
958        assert!(next.path.ends_with("b.txt"), "checking entry #2");
959        assert!(next.file_type().unwrap().is_file(), "checking entry #5");
960
961        if let Some(s) = readdir.next() {
962            panic!("next: {s:?}");
963        }
964
965        let _ = fs_extra::remove_items(&["./test_readdir"]);
966    }
967
968    /*
969    #[tokio::test]
970    async fn test_canonicalize() {
971        let fs = gen_filesystem();
972
973        let root_dir = env!("CARGO_MANIFEST_DIR");
974
975        let _ = fs_extra::remove_items(&["./test_canonicalize"]);
976
977        assert_eq!(
978            fs.create_dir(Path::new("./test_canonicalize")),
979            Ok(()),
980            "creating `test_canonicalize`"
981        );
982
983        assert_eq!(
984            fs.create_dir(Path::new("./test_canonicalize/foo")),
985            Ok(()),
986            "creating `foo`"
987        );
988        assert_eq!(
989            fs.create_dir(Path::new("./test_canonicalize/foo/bar")),
990            Ok(()),
991            "creating `bar`"
992        );
993        assert_eq!(
994            fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz")),
995            Ok(()),
996            "creating `baz`",
997        );
998        assert_eq!(
999            fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz/qux")),
1000            Ok(()),
1001            "creating `qux`",
1002        );
1003        assert!(
1004            matches!(
1005                fs.new_open_options()
1006                    .write(true)
1007                    .create_new(true)
1008                    .open(Path::new("./test_canonicalize/foo/bar/baz/qux/hello.txt")),
1009                Ok(_)
1010            ),
1011            "creating `hello.txt`",
1012        );
1013
1014        assert_eq!(
1015            fs.canonicalize(Path::new("./test_canonicalize")),
1016            Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
1017            "canonicalizing `/`",
1018        );
1019        assert_eq!(
1020            fs.canonicalize(Path::new("foo")),
1021            Err(FsError::InvalidInput),
1022            "canonicalizing `foo`",
1023        );
1024        assert_eq!(
1025            fs.canonicalize(Path::new("./test_canonicalize/././././foo/")),
1026            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo")).to_path_buf()),
1027            "canonicalizing `/././././foo/`",
1028        );
1029        assert_eq!(
1030            fs.canonicalize(Path::new("./test_canonicalize/foo/bar//")),
1031            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
1032            "canonicalizing `/foo/bar//`",
1033        );
1034        assert_eq!(
1035            fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../bar")),
1036            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
1037            "canonicalizing `/foo/bar/../bar`",
1038        );
1039        assert_eq!(
1040            fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../..")),
1041            Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
1042            "canonicalizing `/foo/bar/../..`",
1043        );
1044        assert_eq!(
1045            fs.canonicalize(Path::new("/foo/bar/../../..")),
1046            Err(FsError::InvalidInput),
1047            "canonicalizing `/foo/bar/../../..`",
1048        );
1049        assert_eq!(
1050            fs.canonicalize(Path::new("C:/foo/")),
1051            Err(FsError::InvalidInput),
1052            "canonicalizing `C:/foo/`",
1053        );
1054        assert_eq!(
1055            fs.canonicalize(Path::new(
1056                "./test_canonicalize/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
1057            )),
1058            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar/baz/qux/hello.txt")).to_path_buf()),
1059            "canonicalizing a crazily stupid path name",
1060        );
1061
1062        let _ = fs_extra::remove_items(&["./test_canonicalize"]);
1063    }
1064    */
1065}