virtual_fs/
mount_fs.rs

1//! A mount-topology filesystem that routes operations by path,
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 crate::*;
6
7use std::{
8    borrow::Cow,
9    collections::{BTreeMap, BTreeSet},
10    ffi::OsString,
11    path::{Path, PathBuf},
12    sync::{Arc, RwLock},
13};
14
15const DEFAULT_METADATE_TIME: u64 = 1_000_000_000; // 1 second in nano seconds
16
17type DynFileSystem = Arc<dyn FileSystem + Send + Sync>;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExactMountConflictMode {
21    Fail,
22    KeepExisting,
23    ReplaceExisting,
24}
25
26#[derive(Debug, Clone)]
27struct MountedFileSystem {
28    fs: DynFileSystem,
29    source_path: PathBuf,
30}
31
32#[derive(Debug, Default)]
33struct MountNode {
34    mount: Option<MountedFileSystem>,
35    children: BTreeMap<OsString, MountNode>,
36}
37
38#[derive(Debug, Clone)]
39struct ExactNode {
40    path: PathBuf,
41    fs: Option<DynFileSystem>,
42    source_path: PathBuf,
43    child_names: BTreeSet<OsString>,
44}
45
46impl ExactNode {
47    fn has_children(&self) -> bool {
48        !self.child_names.is_empty()
49    }
50}
51
52#[derive(Debug, Clone)]
53struct ResolvedMount {
54    mount_path: PathBuf,
55    delegated_path: PathBuf,
56    fs: DynFileSystem,
57}
58
59#[derive(Debug, Clone)]
60pub struct MountPoint {
61    pub path: PathBuf,
62    pub name: String,
63    pub fs: Option<DynFileSystem>,
64    pub children: Option<Arc<MountFileSystem>>,
65}
66
67#[derive(Debug, Clone)]
68pub struct MountEntry {
69    pub path: PathBuf,
70    pub fs: DynFileSystem,
71    pub source_path: PathBuf,
72}
73
74impl MountPoint {
75    pub fn fs(&self) -> Option<&(dyn FileSystem + Send + Sync)> {
76        self.fs.as_deref()
77    }
78
79    pub fn mount_point_ref(&self) -> MountPointRef<'_> {
80        MountPointRef {
81            path: self.path.clone(),
82            name: self.name.clone(),
83            fs: self.fs.as_deref(),
84        }
85    }
86}
87
88/// Allows different filesystems of different types
89/// to be mounted at various mount points
90#[derive(Debug, Default)]
91pub struct MountFileSystem {
92    root: RwLock<MountNode>,
93}
94
95impl MountFileSystem {
96    pub fn new() -> Self {
97        Self::default()
98    }
99
100    pub fn mount(
101        &self,
102        path: impl AsRef<Path>,
103        fs: Arc<dyn FileSystem + Send + Sync>,
104    ) -> Result<()> {
105        self.mount_with_source(path, Path::new("/"), fs)
106    }
107
108    pub fn mount_with_source(
109        &self,
110        path: impl AsRef<Path>,
111        source_path: impl AsRef<Path>,
112        fs: Arc<dyn FileSystem + Send + Sync>,
113    ) -> Result<()> {
114        let path = self.prepare_path(path.as_ref())?;
115        let source_path = Self::normalize_source_path(source_path.as_ref());
116        let mut root = self.root.write().unwrap();
117        let node = Self::mount_node_mut(&mut root, &Self::path_components(&path));
118
119        if node.mount.is_some() {
120            Err(FsError::AlreadyExists)
121        } else {
122            node.mount = Some(MountedFileSystem { fs, source_path });
123            Ok(())
124        }
125    }
126
127    pub fn filesystem_at(
128        &self,
129        path: impl AsRef<Path>,
130    ) -> Option<Arc<dyn FileSystem + Send + Sync>> {
131        self.exact_node(path.as_ref()).and_then(|node| node.fs)
132    }
133
134    pub fn clear(&mut self) {
135        *self.root.write().unwrap() = MountNode::default();
136    }
137
138    fn prepare_path(&self, path: &Path) -> Result<PathBuf> {
139        let mut normalized = PathBuf::new();
140
141        for component in path.components() {
142            match component {
143                std::path::Component::RootDir | std::path::Component::CurDir => {}
144                std::path::Component::ParentDir => {
145                    if !normalized.pop() {
146                        return Err(FsError::InvalidInput);
147                    }
148                }
149                std::path::Component::Normal(part) => normalized.push(part),
150                std::path::Component::Prefix(_) => return Err(FsError::InvalidInput),
151            }
152        }
153
154        Ok(normalized)
155    }
156
157    fn path_components(path: &Path) -> Vec<OsString> {
158        path.components()
159            .map(|component| component.as_os_str().to_os_string())
160            .collect()
161    }
162
163    fn absolute_path(components: &[OsString]) -> PathBuf {
164        let mut path = PathBuf::from("/");
165        for component in components {
166            path.push(component);
167        }
168        path
169    }
170
171    fn normalize_source_path(path: &Path) -> PathBuf {
172        let mut normalized = PathBuf::from("/");
173        normalized.push(path.strip_prefix("/").unwrap_or(path));
174        normalized
175    }
176
177    fn directory_metadata() -> Metadata {
178        Metadata {
179            ft: FileType::new_dir(),
180            accessed: DEFAULT_METADATE_TIME,
181            created: DEFAULT_METADATE_TIME,
182            modified: DEFAULT_METADATE_TIME,
183            len: 0,
184        }
185    }
186
187    fn should_fallback_to_synthetic_dir(error: &FsError) -> bool {
188        matches!(
189            error,
190            FsError::Unsupported | FsError::NotAFile | FsError::BaseNotDirectory
191        )
192    }
193
194    fn synthetic_entry(name: OsString, base: &Path) -> DirEntry {
195        DirEntry {
196            path: base.join(PathBuf::from(name)),
197            metadata: Ok(Self::directory_metadata()),
198        }
199    }
200
201    fn mounted(node: &MountNode) -> Option<MountedFileSystem> {
202        node.mount.clone()
203    }
204
205    fn collect_mount_entries(node: &MountNode, path: &Path, entries: &mut Vec<MountEntry>) {
206        if let Some(mount) = Self::mounted(node) {
207            entries.push(MountEntry {
208                path: path.to_path_buf(),
209                fs: mount.fs,
210                source_path: mount.source_path,
211            });
212        }
213
214        for (child_name, child) in &node.children {
215            let child_path = path.join(child_name);
216            Self::collect_mount_entries(child, &child_path, entries);
217        }
218    }
219
220    fn find_node<'a>(node: &'a MountNode, components: &[OsString]) -> Option<&'a MountNode> {
221        let mut node = node;
222        for component in components {
223            node = node.children.get(component)?;
224        }
225        Some(node)
226    }
227
228    fn exact_node(&self, path: &Path) -> Option<ExactNode> {
229        let path = self.prepare_path(path).ok()?;
230        let components = Self::path_components(&path);
231        let visible_path = Path::new("/").join(&path);
232        let root = self.root.read().unwrap();
233        let node = Self::find_node(&root, &components)?;
234        let mounted = Self::mounted(node);
235
236        Some(ExactNode {
237            path: visible_path.clone(),
238            fs: mounted.as_ref().map(|mount| mount.fs.clone()),
239            source_path: mounted
240                .map(|mount| mount.source_path)
241                .unwrap_or_else(|| PathBuf::from("/")),
242            child_names: node.children.keys().cloned().collect(),
243        })
244    }
245
246    fn resolve_mount(&self, path: impl AsRef<Path>) -> Option<ResolvedMount> {
247        let path = self.prepare_path(path.as_ref()).ok()?;
248        let components = Self::path_components(&path);
249        let root = self.root.read().unwrap();
250        let mut node = &*root;
251        let mut best = Self::mounted(node).map(|mount| ResolvedMount {
252            mount_path: PathBuf::from("/"),
253            delegated_path: mount.source_path.join(
254                Self::absolute_path(&components)
255                    .strip_prefix("/")
256                    .unwrap_or(Path::new("")),
257            ),
258            fs: mount.fs,
259        });
260
261        for (index, component) in components.iter().enumerate() {
262            let Some(child) = node.children.get(component) else {
263                break;
264            };
265            node = child;
266
267            if let Some(mount) = Self::mounted(node) {
268                best = Some(ResolvedMount {
269                    mount_path: Self::absolute_path(&components[..=index]),
270                    delegated_path: mount.source_path.join(
271                        Self::absolute_path(&components[index + 1..])
272                            .strip_prefix("/")
273                            .unwrap_or(Path::new("")),
274                    ),
275                    fs: mount.fs,
276                });
277            }
278        }
279
280        best
281    }
282
283    fn rebase_entries(entries: &mut ReadDir, source_prefix: &Path, target_prefix: &Path) {
284        for entry in &mut entries.data {
285            let suffix = entry.path.strip_prefix(source_prefix).unwrap_or_else(|_| {
286                entry
287                    .path
288                    .strip_prefix(Path::new("/"))
289                    .unwrap_or(&entry.path)
290            });
291            entry.path = target_prefix.join(suffix);
292        }
293    }
294
295    fn read_dir_from_exact_node(&self, node: &ExactNode) -> Result<ReadDir> {
296        let mut entries = Vec::new();
297
298        let backing = if let Some(fs) = &node.fs {
299            Some((
300                fs.read_dir(&node.source_path),
301                Cow::Borrowed(node.source_path.as_path()),
302            ))
303        } else {
304            self.resolve_mount(&node.path).map(|resolved| {
305                (
306                    resolved.fs.read_dir(&resolved.delegated_path),
307                    Cow::Owned(resolved.delegated_path),
308                )
309            })
310        };
311
312        if let Some((base_entries, source_path)) = backing {
313            match base_entries {
314                Ok(mut base_entries) => {
315                    Self::rebase_entries(&mut base_entries, &source_path, &node.path);
316                    entries.extend(base_entries.data.into_iter().filter(|entry| {
317                        entry
318                            .path
319                            .file_name()
320                            .map(|name| !node.child_names.contains(name))
321                            .unwrap_or(true)
322                    }));
323                }
324                Err(FsError::EntryNotFound) if node.has_children() => {}
325                Err(error)
326                    if node.has_children() && Self::should_fallback_to_synthetic_dir(&error) => {}
327                Err(error) => return Err(error),
328            }
329        }
330
331        entries.extend(
332            node.child_names
333                .iter()
334                .cloned()
335                .map(|name| Self::synthetic_entry(name, &node.path)),
336        );
337
338        Ok(ReadDir::new(entries))
339    }
340
341    fn mount_node_mut<'a>(node: &'a mut MountNode, components: &[OsString]) -> &'a mut MountNode {
342        let mut node = node;
343        for component in components {
344            node = node.children.entry(component.clone()).or_default();
345        }
346
347        node
348    }
349
350    fn clear_descendants(node: &mut MountNode) {
351        node.children.clear();
352    }
353
354    /// Overwrite the mount at `path`.
355    pub fn set_mount(
356        &self,
357        path: impl AsRef<Path>,
358        fs: Arc<dyn FileSystem + Send + Sync>,
359    ) -> Result<()> {
360        let path = self.prepare_path(path.as_ref())?;
361        let mut root = self.root.write().unwrap();
362        let node = Self::mount_node_mut(&mut root, &Self::path_components(&path));
363        node.mount = Some(MountedFileSystem {
364            fs,
365            source_path: PathBuf::from("/"),
366        });
367        Ok(())
368    }
369
370    pub fn add_mount_entries_with_mode(
371        &self,
372        entries: impl IntoIterator<Item = MountEntry>,
373        conflict_mode: ExactMountConflictMode,
374    ) -> Result<()> {
375        let mut skipped_subtrees = Vec::<PathBuf>::new();
376
377        for entry in entries {
378            if skipped_subtrees
379                .iter()
380                .any(|prefix| entry.path.starts_with(prefix))
381            {
382                continue;
383            }
384
385            let exact_conflict = self.filesystem_at(&entry.path).is_some();
386            if exact_conflict {
387                match conflict_mode {
388                    ExactMountConflictMode::Fail => return Err(FsError::AlreadyExists),
389                    ExactMountConflictMode::KeepExisting => {
390                        skipped_subtrees.push(entry.path);
391                        continue;
392                    }
393                    ExactMountConflictMode::ReplaceExisting => {
394                        let mut root = self.root.write().unwrap();
395                        let node = Self::mount_node_mut(
396                            &mut root,
397                            &Self::path_components(&self.prepare_path(&entry.path)?),
398                        );
399                        Self::clear_descendants(node);
400                        node.mount = Some(MountedFileSystem {
401                            fs: entry.fs,
402                            source_path: entry.source_path,
403                        });
404                        continue;
405                    }
406                }
407            }
408
409            self.mount_with_source(&entry.path, &entry.source_path, entry.fs)?;
410        }
411
412        Ok(())
413    }
414    pub fn mount_entries(&self) -> Vec<MountEntry> {
415        let mut entries = Vec::new();
416        let root = self.root.read().unwrap();
417        Self::collect_mount_entries(&root, Path::new("/"), &mut entries);
418        entries
419    }
420}
421
422impl FileSystem for MountFileSystem {
423    fn readlink(&self, path: &Path) -> Result<PathBuf> {
424        let path = self.prepare_path(path)?;
425
426        if path.as_os_str().is_empty() {
427            Err(FsError::NotAFile)
428        } else {
429            if let Some(node) = self.exact_node(&path)
430                && node.fs.is_none()
431            {
432                return Err(FsError::EntryNotFound);
433            }
434
435            match self.resolve_mount(path) {
436                Some(resolved) => resolved.fs.readlink(&resolved.delegated_path),
437                None => Err(FsError::EntryNotFound),
438            }
439        }
440    }
441
442    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
443        let path = self.prepare_path(path)?;
444
445        if let Some(node) = self.exact_node(&path) {
446            return self.read_dir_from_exact_node(&node);
447        }
448
449        match self.resolve_mount(path.clone()) {
450            Some(resolved) => {
451                let mut entries = resolved.fs.read_dir(&resolved.delegated_path)?;
452                Self::rebase_entries(
453                    &mut entries,
454                    &resolved.delegated_path,
455                    &Path::new("/").join(&path),
456                );
457                Ok(entries)
458            }
459            None => Err(FsError::EntryNotFound),
460        }
461    }
462
463    fn create_dir(&self, path: &Path) -> Result<()> {
464        let path = self.prepare_path(path)?;
465
466        if path.as_os_str().is_empty() {
467            return Ok(());
468        }
469
470        if let Some(node) = self.exact_node(&path) {
471            return if let Some(fs) = node.fs {
472                let result = fs.create_dir(Path::new("/"));
473
474                match result {
475                    Ok(()) | Err(FsError::AlreadyExists) => Ok(()),
476                    Err(error) if Self::should_fallback_to_synthetic_dir(&error) => Ok(()),
477                    Err(error) => Err(error),
478                }
479            } else {
480                Ok(())
481            };
482        }
483
484        match self.resolve_mount(path) {
485            Some(resolved) => {
486                let result = resolved.fs.create_dir(&resolved.delegated_path);
487
488                if let Err(error) = result
489                    && error == FsError::AlreadyExists
490                {
491                    return Ok(());
492                }
493
494                result
495            }
496            None => Err(FsError::EntryNotFound),
497        }
498    }
499
500    fn create_symlink(&self, source: &Path, target: &Path) -> Result<()> {
501        let target = self.prepare_path(target)?;
502
503        if target.as_os_str().is_empty() {
504            return Err(FsError::AlreadyExists);
505        }
506
507        if self.exact_node(&target).is_some() {
508            return Err(FsError::AlreadyExists);
509        }
510
511        match self.resolve_mount(target) {
512            Some(resolved) => resolved.fs.create_symlink(source, &resolved.delegated_path),
513            None => Err(FsError::EntryNotFound),
514        }
515    }
516
517    fn remove_dir(&self, path: &Path) -> Result<()> {
518        let path = self.prepare_path(path)?;
519
520        if path.as_os_str().is_empty() {
521            return Err(FsError::PermissionDenied);
522        }
523
524        if let Some(node) = self.exact_node(&path) {
525            return if node.fs.is_some() || node.has_children() {
526                Err(FsError::PermissionDenied)
527            } else {
528                Err(FsError::EntryNotFound)
529            };
530        }
531
532        match self.resolve_mount(path) {
533            Some(resolved) => resolved.fs.remove_dir(&resolved.delegated_path),
534            None => Err(FsError::EntryNotFound),
535        }
536    }
537
538    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
539        Box::pin(async move {
540            let from = self.prepare_path(from)?;
541            let to = self.prepare_path(to)?;
542
543            if from.as_os_str().is_empty() {
544                return Err(FsError::PermissionDenied);
545            }
546
547            if let Some(node) = self.exact_node(&from)
548                && (node.fs.is_some() || node.has_children())
549            {
550                return Err(FsError::PermissionDenied);
551            }
552
553            if let Some(node) = self.exact_node(&to)
554                && (node.fs.is_some() || node.has_children())
555            {
556                return Err(FsError::PermissionDenied);
557            }
558
559            match (self.resolve_mount(from), self.resolve_mount(to)) {
560                (Some(from_mount), Some(to_mount))
561                    if from_mount.mount_path == to_mount.mount_path =>
562                {
563                    from_mount
564                        .fs
565                        .rename(&from_mount.delegated_path, &to_mount.delegated_path)
566                        .await
567                }
568                (Some(from_mount), Some(to_mount)) => {
569                    ops::move_across_filesystems(
570                        from_mount.fs.as_ref(),
571                        to_mount.fs.as_ref(),
572                        &from_mount.delegated_path,
573                        &to_mount.delegated_path,
574                    )
575                    .await
576                }
577                _ => Err(FsError::EntryNotFound),
578            }
579        })
580    }
581
582    fn metadata(&self, path: &Path) -> Result<Metadata> {
583        let path = self.prepare_path(path)?;
584
585        if let Some(node) = self.exact_node(&path) {
586            return if let Some(fs) = node.fs {
587                fs.metadata(&node.source_path).or_else(|error| {
588                    if Self::should_fallback_to_synthetic_dir(&error) {
589                        Ok(Self::directory_metadata())
590                    } else {
591                        Err(error)
592                    }
593                })
594            } else if node.has_children() {
595                Ok(Self::directory_metadata())
596            } else {
597                Err(FsError::EntryNotFound)
598            };
599        }
600
601        match self.resolve_mount(path) {
602            Some(resolved) => resolved.fs.metadata(&resolved.delegated_path),
603            None => Err(FsError::EntryNotFound),
604        }
605    }
606
607    fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
608        let path = self.prepare_path(path)?;
609
610        if let Some(node) = self.exact_node(&path) {
611            return if let Some(fs) = node.fs {
612                fs.symlink_metadata(&node.source_path).or_else(|error| {
613                    if Self::should_fallback_to_synthetic_dir(&error) {
614                        Ok(Self::directory_metadata())
615                    } else {
616                        Err(error)
617                    }
618                })
619            } else if node.has_children() {
620                Ok(Self::directory_metadata())
621            } else {
622                Err(FsError::EntryNotFound)
623            };
624        }
625
626        match self.resolve_mount(path) {
627            Some(resolved) => resolved.fs.symlink_metadata(&resolved.delegated_path),
628            None => Err(FsError::EntryNotFound),
629        }
630    }
631
632    fn remove_file(&self, path: &Path) -> Result<()> {
633        let path = self.prepare_path(path)?;
634
635        if path.as_os_str().is_empty() {
636            return Err(FsError::NotAFile);
637        }
638
639        if let Some(node) = self.exact_node(&path) {
640            return if node.fs.is_some() || node.has_children() {
641                Err(FsError::PermissionDenied)
642            } else {
643                Err(FsError::EntryNotFound)
644            };
645        }
646
647        match self.resolve_mount(path) {
648            Some(resolved) => resolved.fs.remove_file(&resolved.delegated_path),
649            None => Err(FsError::EntryNotFound),
650        }
651    }
652
653    fn new_open_options(&self) -> OpenOptions<'_> {
654        OpenOptions::new(self)
655    }
656}
657
658#[derive(Debug)]
659pub struct MountPointRef<'a> {
660    pub path: PathBuf,
661    pub name: String,
662    pub fs: Option<&'a (dyn FileSystem + Send + Sync)>,
663}
664
665impl FileOpener for MountFileSystem {
666    fn open(
667        &self,
668        path: &Path,
669        conf: &OpenOptionsConfig,
670    ) -> Result<Box<dyn VirtualFile + Send + Sync>> {
671        let path = self.prepare_path(path)?;
672
673        if path.as_os_str().is_empty() {
674            return Err(FsError::NotAFile);
675        }
676
677        if let Some(node) = self.exact_node(&path)
678            && node.fs.is_none()
679        {
680            return Err(FsError::NotAFile);
681        }
682
683        match self.resolve_mount(path) {
684            Some(resolved) => resolved
685                .fs
686                .new_open_options()
687                .options(conf.clone())
688                .open(resolved.delegated_path),
689            None => Err(FsError::EntryNotFound),
690        }
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use std::{
697        collections::HashSet,
698        path::{Path, PathBuf},
699        sync::Arc,
700    };
701
702    use tokio::io::AsyncWriteExt;
703
704    use crate::{FileSystem as FileSystemTrait, FsError, MountFileSystem, TmpFileSystem, mem_fs};
705
706    use super::{FileOpener, OpenOptionsConfig};
707
708    #[derive(Debug, Clone, Default)]
709    struct MountlessFileSystem {
710        inner: mem_fs::FileSystem,
711    }
712
713    #[derive(Debug, Clone, Default)]
714    struct RootOpaqueFileSystem {
715        inner: mem_fs::FileSystem,
716    }
717
718    #[derive(Debug, Clone, Default)]
719    struct RootPermissionDeniedFileSystem;
720
721    impl FileSystemTrait for MountlessFileSystem {
722        fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
723            self.inner.readlink(path)
724        }
725
726        fn read_dir(&self, path: &Path) -> crate::Result<crate::ReadDir> {
727            self.inner.read_dir(path)
728        }
729
730        fn create_dir(&self, path: &Path) -> crate::Result<()> {
731            self.inner.create_dir(path)
732        }
733
734        fn remove_dir(&self, path: &Path) -> crate::Result<()> {
735            self.inner.remove_dir(path)
736        }
737
738        fn rename<'a>(
739            &'a self,
740            from: &'a Path,
741            to: &'a Path,
742        ) -> futures::future::BoxFuture<'a, crate::Result<()>> {
743            Box::pin(async move { self.inner.rename(from, to).await })
744        }
745
746        fn metadata(&self, path: &Path) -> crate::Result<crate::Metadata> {
747            self.inner.metadata(path)
748        }
749
750        fn symlink_metadata(&self, path: &Path) -> crate::Result<crate::Metadata> {
751            self.inner.symlink_metadata(path)
752        }
753
754        fn remove_file(&self, path: &Path) -> crate::Result<()> {
755            self.inner.remove_file(path)
756        }
757
758        fn new_open_options(&self) -> crate::OpenOptions<'_> {
759            self.inner.new_open_options()
760        }
761    }
762
763    impl FileOpener for MountlessFileSystem {
764        fn open(
765            &self,
766            path: &Path,
767            conf: &OpenOptionsConfig,
768        ) -> crate::Result<Box<dyn crate::VirtualFile + Send + Sync>> {
769            self.inner
770                .new_open_options()
771                .options(conf.clone())
772                .open(path)
773        }
774    }
775
776    impl FileSystemTrait for RootOpaqueFileSystem {
777        fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
778            self.inner.readlink(path)
779        }
780
781        fn read_dir(&self, path: &Path) -> crate::Result<crate::ReadDir> {
782            if path == Path::new("/") {
783                Err(FsError::Unsupported)
784            } else {
785                self.inner.read_dir(path)
786            }
787        }
788
789        fn create_dir(&self, path: &Path) -> crate::Result<()> {
790            self.inner.create_dir(path)
791        }
792
793        fn remove_dir(&self, path: &Path) -> crate::Result<()> {
794            self.inner.remove_dir(path)
795        }
796
797        fn rename<'a>(
798            &'a self,
799            from: &'a Path,
800            to: &'a Path,
801        ) -> futures::future::BoxFuture<'a, crate::Result<()>> {
802            Box::pin(async move { self.inner.rename(from, to).await })
803        }
804
805        fn metadata(&self, path: &Path) -> crate::Result<crate::Metadata> {
806            if path == Path::new("/") {
807                Err(FsError::Unsupported)
808            } else {
809                self.inner.metadata(path)
810            }
811        }
812
813        fn symlink_metadata(&self, path: &Path) -> crate::Result<crate::Metadata> {
814            if path == Path::new("/") {
815                Err(FsError::Unsupported)
816            } else {
817                self.inner.symlink_metadata(path)
818            }
819        }
820
821        fn remove_file(&self, path: &Path) -> crate::Result<()> {
822            self.inner.remove_file(path)
823        }
824
825        fn new_open_options(&self) -> crate::OpenOptions<'_> {
826            self.inner.new_open_options()
827        }
828    }
829
830    impl FileOpener for RootOpaqueFileSystem {
831        fn open(
832            &self,
833            path: &Path,
834            conf: &OpenOptionsConfig,
835        ) -> crate::Result<Box<dyn crate::VirtualFile + Send + Sync>> {
836            self.inner
837                .new_open_options()
838                .options(conf.clone())
839                .open(path)
840        }
841    }
842
843    impl FileSystemTrait for RootPermissionDeniedFileSystem {
844        fn readlink(&self, _path: &Path) -> crate::Result<PathBuf> {
845            Err(FsError::PermissionDenied)
846        }
847
848        fn read_dir(&self, _path: &Path) -> crate::Result<crate::ReadDir> {
849            Err(FsError::PermissionDenied)
850        }
851
852        fn create_dir(&self, _path: &Path) -> crate::Result<()> {
853            Err(FsError::PermissionDenied)
854        }
855
856        fn remove_dir(&self, _path: &Path) -> crate::Result<()> {
857            Err(FsError::PermissionDenied)
858        }
859
860        fn rename<'a>(
861            &'a self,
862            _from: &'a Path,
863            _to: &'a Path,
864        ) -> futures::future::BoxFuture<'a, crate::Result<()>> {
865            Box::pin(async { Err(FsError::PermissionDenied) })
866        }
867
868        fn metadata(&self, _path: &Path) -> crate::Result<crate::Metadata> {
869            Err(FsError::PermissionDenied)
870        }
871
872        fn symlink_metadata(&self, _path: &Path) -> crate::Result<crate::Metadata> {
873            Err(FsError::PermissionDenied)
874        }
875
876        fn remove_file(&self, _path: &Path) -> crate::Result<()> {
877            Err(FsError::PermissionDenied)
878        }
879
880        fn new_open_options(&self) -> crate::OpenOptions<'_> {
881            crate::OpenOptions::new(self)
882        }
883    }
884
885    impl FileOpener for RootPermissionDeniedFileSystem {
886        fn open(
887            &self,
888            _path: &Path,
889            _conf: &OpenOptionsConfig,
890        ) -> crate::Result<Box<dyn crate::VirtualFile + Send + Sync>> {
891            Err(FsError::PermissionDenied)
892        }
893    }
894
895    fn gen_filesystem() -> MountFileSystem {
896        let union = MountFileSystem::new();
897        let a = mem_fs::FileSystem::default();
898        let b = mem_fs::FileSystem::default();
899        let c = mem_fs::FileSystem::default();
900        let d = mem_fs::FileSystem::default();
901        let e = mem_fs::FileSystem::default();
902        let f = mem_fs::FileSystem::default();
903        let g = mem_fs::FileSystem::default();
904        let h = mem_fs::FileSystem::default();
905
906        union
907            .mount(PathBuf::from("/test_new_filesystem").as_path(), Arc::new(a))
908            .unwrap();
909        union
910            .mount(PathBuf::from("/test_create_dir").as_path(), Arc::new(b))
911            .unwrap();
912        union
913            .mount(PathBuf::from("/test_remove_dir").as_path(), Arc::new(c))
914            .unwrap();
915        union
916            .mount(PathBuf::from("/test_rename").as_path(), Arc::new(d))
917            .unwrap();
918        union
919            .mount(PathBuf::from("/test_metadata").as_path(), Arc::new(e))
920            .unwrap();
921        union
922            .mount(PathBuf::from("/test_remove_file").as_path(), Arc::new(f))
923            .unwrap();
924        union
925            .mount(PathBuf::from("/test_readdir").as_path(), Arc::new(g))
926            .unwrap();
927        union
928            .mount(PathBuf::from("/test_canonicalize").as_path(), Arc::new(h))
929            .unwrap();
930
931        union
932    }
933
934    fn gen_nested_filesystem() -> MountFileSystem {
935        let union = MountFileSystem::new();
936        let a = mem_fs::FileSystem::default();
937        a.open(
938            &PathBuf::from("/data-a.txt"),
939            &OpenOptionsConfig {
940                read: true,
941                write: true,
942                create_new: false,
943                create: true,
944                append: false,
945                truncate: false,
946            },
947        )
948        .unwrap();
949        let b = mem_fs::FileSystem::default();
950        b.open(
951            &PathBuf::from("/data-b.txt"),
952            &OpenOptionsConfig {
953                read: true,
954                write: true,
955                create_new: false,
956                create: true,
957                append: false,
958                truncate: false,
959            },
960        )
961        .unwrap();
962
963        union
964            .mount(PathBuf::from("/app/a").as_path(), Arc::new(a))
965            .unwrap();
966        union
967            .mount(PathBuf::from("/app/b").as_path(), Arc::new(b))
968            .unwrap();
969
970        union
971    }
972
973    #[tokio::test]
974    async fn test_nested_read_dir() {
975        let fs = gen_nested_filesystem();
976
977        let root_contents: Vec<PathBuf> = fs
978            .read_dir(&PathBuf::from("/"))
979            .unwrap()
980            .map(|e| e.unwrap().path.clone())
981            .collect();
982        assert_eq!(root_contents, vec![PathBuf::from("/app")]);
983
984        let app_contents: HashSet<PathBuf> = fs
985            .read_dir(&PathBuf::from("/app"))
986            .unwrap()
987            .map(|e| e.unwrap().path)
988            .collect();
989        assert_eq!(
990            app_contents,
991            HashSet::from_iter([PathBuf::from("/app/a"), PathBuf::from("/app/b")].into_iter())
992        );
993
994        let a_contents: Vec<PathBuf> = fs
995            .read_dir(&PathBuf::from("/app/a"))
996            .unwrap()
997            .map(|e| e.unwrap().path.clone())
998            .collect();
999        assert_eq!(a_contents, vec![PathBuf::from("/app/a/data-a.txt")]);
1000
1001        let b_contents: Vec<PathBuf> = fs
1002            .read_dir(&PathBuf::from("/app/b"))
1003            .unwrap()
1004            .map(|e| e.unwrap().path)
1005            .collect();
1006        assert_eq!(b_contents, vec![PathBuf::from("/app/b/data-b.txt")]);
1007    }
1008
1009    #[tokio::test]
1010    async fn test_nested_metadata() {
1011        let fs = gen_nested_filesystem();
1012
1013        assert!(fs.metadata(&PathBuf::from("/")).is_ok());
1014        assert!(fs.metadata(&PathBuf::from("/app")).is_ok());
1015        assert!(fs.metadata(&PathBuf::from("/app/a")).is_ok());
1016        assert!(fs.metadata(&PathBuf::from("/app/b")).is_ok());
1017        assert!(fs.metadata(&PathBuf::from("/app/a/data-a.txt")).is_ok());
1018        assert!(fs.metadata(&PathBuf::from("/app/b/data-b.txt")).is_ok());
1019    }
1020
1021    #[tokio::test]
1022    async fn test_nested_symlink_metadata() {
1023        let fs = gen_nested_filesystem();
1024
1025        assert!(fs.symlink_metadata(&PathBuf::from("/")).is_ok());
1026        assert!(fs.symlink_metadata(&PathBuf::from("/app")).is_ok());
1027        assert!(fs.symlink_metadata(&PathBuf::from("/app/a")).is_ok());
1028        assert!(fs.symlink_metadata(&PathBuf::from("/app/b")).is_ok());
1029        assert!(
1030            fs.symlink_metadata(&PathBuf::from("/app/a/data-a.txt"))
1031                .is_ok()
1032        );
1033        assert!(
1034            fs.symlink_metadata(&PathBuf::from("/app/b/data-b.txt"))
1035                .is_ok()
1036        );
1037    }
1038
1039    #[tokio::test]
1040    async fn test_import_mounts_preserves_nested_root_mounts() {
1041        let primary = MountFileSystem::new();
1042        let openssl = mem_fs::FileSystem::default();
1043        openssl.create_dir(Path::new("/certs")).unwrap();
1044        openssl
1045            .new_open_options()
1046            .write(true)
1047            .create_new(true)
1048            .open(Path::new("/certs/ca.pem"))
1049            .unwrap();
1050        primary
1051            .mount(Path::new("/openssl"), Arc::new(openssl))
1052            .unwrap();
1053
1054        let injected = MountFileSystem::new();
1055        let app = mem_fs::FileSystem::default();
1056        app.new_open_options()
1057            .write(true)
1058            .create_new(true)
1059            .open(Path::new("/index.php"))
1060            .unwrap();
1061        injected.mount(Path::new("/app"), Arc::new(app)).unwrap();
1062
1063        let assets = mem_fs::FileSystem::default();
1064        assets.create_dir(Path::new("/css")).unwrap();
1065        assets
1066            .new_open_options()
1067            .write(true)
1068            .create_new(true)
1069            .open(Path::new("/css/site.css"))
1070            .unwrap();
1071        injected
1072            .mount(Path::new("/opt/assets"), Arc::new(assets))
1073            .unwrap();
1074
1075        primary
1076            .add_mount_entries_with_mode(
1077                injected.mount_entries(),
1078                super::ExactMountConflictMode::Fail,
1079            )
1080            .unwrap();
1081
1082        let root_contents = read_dir_names(&primary, "/");
1083        assert!(root_contents.contains(&"app".to_string()));
1084        assert!(root_contents.contains(&"opt".to_string()));
1085        assert!(root_contents.contains(&"openssl".to_string()));
1086        assert!(primary.metadata(Path::new("/app/index.php")).is_ok());
1087        assert!(
1088            primary
1089                .metadata(Path::new("/opt/assets/css/site.css"))
1090                .is_ok()
1091        );
1092        assert!(primary.metadata(Path::new("/openssl/certs/ca.pem")).is_ok());
1093    }
1094
1095    #[tokio::test]
1096    async fn test_nested_mount_under_non_mountable_leaf_is_supported() {
1097        let fs = MountFileSystem::new();
1098
1099        let top = MountlessFileSystem::default();
1100        top.create_dir(Path::new("/bin")).unwrap();
1101        top.new_open_options()
1102            .write(true)
1103            .create_new(true)
1104            .open(Path::new("/bin/tool"))
1105            .unwrap();
1106
1107        let nested = mem_fs::FileSystem::default();
1108        nested.create_dir(Path::new("/css")).unwrap();
1109        nested
1110            .new_open_options()
1111            .write(true)
1112            .create_new(true)
1113            .open(Path::new("/css/site.css"))
1114            .unwrap();
1115
1116        fs.mount(Path::new("/opt"), Arc::new(top)).unwrap();
1117        fs.mount(Path::new("/opt/assets"), Arc::new(nested))
1118            .unwrap();
1119
1120        assert!(fs.metadata(Path::new("/opt/bin/tool")).is_ok());
1121        assert!(fs.metadata(Path::new("/opt/assets/css/site.css")).is_ok());
1122    }
1123
1124    #[tokio::test]
1125    async fn test_normalized_paths_still_route_to_deepest_mount() {
1126        let fs = MountFileSystem::new();
1127
1128        let top = MountlessFileSystem::default();
1129        top.create_dir(Path::new("/bin")).unwrap();
1130        top.new_open_options()
1131            .write(true)
1132            .create_new(true)
1133            .open(Path::new("/bin/tool"))
1134            .unwrap();
1135
1136        let nested = mem_fs::FileSystem::default();
1137        nested.create_dir(Path::new("/css")).unwrap();
1138        nested
1139            .new_open_options()
1140            .write(true)
1141            .create_new(true)
1142            .open(Path::new("/css/site.css"))
1143            .unwrap();
1144
1145        fs.mount(Path::new("/opt"), Arc::new(top)).unwrap();
1146        fs.mount(Path::new("/opt/assets"), Arc::new(nested))
1147            .unwrap();
1148
1149        assert!(
1150            fs.metadata(Path::new("/opt/./assets/../assets/css/site.css"))
1151                .unwrap()
1152                .is_file()
1153        );
1154    }
1155
1156    #[tokio::test]
1157    async fn test_invalid_above_root_path_is_rejected() {
1158        let fs = MountFileSystem::new();
1159        fs.mount(Path::new("/"), Arc::new(mem_fs::FileSystem::default()))
1160            .unwrap();
1161
1162        assert_eq!(fs.metadata(Path::new("../foo")), Err(FsError::InvalidInput));
1163    }
1164
1165    #[tokio::test]
1166    async fn test_exact_mount_metadata_falls_back_to_synthetic_directory() {
1167        let fs = MountFileSystem::new();
1168        fs.mount(
1169            Path::new("/opaque"),
1170            Arc::new(RootOpaqueFileSystem::default()),
1171        )
1172        .unwrap();
1173
1174        assert!(fs.metadata(Path::new("/opaque")).unwrap().is_dir());
1175        assert!(fs.symlink_metadata(Path::new("/opaque")).unwrap().is_dir());
1176        assert_eq!(fs.create_dir(Path::new("/opaque")), Ok(()));
1177    }
1178
1179    #[tokio::test]
1180    async fn test_exact_mount_read_dir_falls_back_to_child_mounts_when_root_is_unlistable() {
1181        let fs = MountFileSystem::new();
1182        fs.mount(
1183            Path::new("/opaque"),
1184            Arc::new(RootOpaqueFileSystem::default()),
1185        )
1186        .unwrap();
1187        fs.mount(
1188            Path::new("/opaque/assets"),
1189            Arc::new(mem_fs::FileSystem::default()),
1190        )
1191        .unwrap();
1192
1193        assert_eq!(read_dir_names(&fs, "/opaque"), vec!["assets".to_string()]);
1194    }
1195
1196    #[tokio::test]
1197    async fn test_exact_mount_fallback_does_not_mask_permission_denied() {
1198        let fs = MountFileSystem::new();
1199        fs.mount(
1200            Path::new("/denied"),
1201            Arc::new(RootPermissionDeniedFileSystem),
1202        )
1203        .unwrap();
1204        fs.mount(
1205            Path::new("/denied/assets"),
1206            Arc::new(mem_fs::FileSystem::default()),
1207        )
1208        .unwrap();
1209
1210        assert_eq!(
1211            fs.metadata(Path::new("/denied")),
1212            Err(FsError::PermissionDenied)
1213        );
1214        assert_eq!(
1215            fs.symlink_metadata(Path::new("/denied")),
1216            Err(FsError::PermissionDenied)
1217        );
1218        assert_eq!(
1219            fs.read_dir(Path::new("/denied")).map(|_| ()),
1220            Err(FsError::PermissionDenied)
1221        );
1222        assert_eq!(
1223            fs.create_dir(Path::new("/denied")),
1224            Err(FsError::PermissionDenied)
1225        );
1226    }
1227
1228    #[tokio::test]
1229    async fn test_keep_existing_conflict_skips_the_other_subtree() {
1230        let primary = MountFileSystem::new();
1231        let user_mount = mem_fs::FileSystem::default();
1232        user_mount
1233            .new_open_options()
1234            .write(true)
1235            .create_new(true)
1236            .open(Path::new("/user.txt"))
1237            .unwrap();
1238        primary
1239            .mount(Path::new("/python"), Arc::new(user_mount))
1240            .unwrap();
1241
1242        let injected = MountFileSystem::new();
1243        let package_mount = mem_fs::FileSystem::default();
1244        package_mount
1245            .new_open_options()
1246            .write(true)
1247            .create_new(true)
1248            .open(Path::new("/pkg.txt"))
1249            .unwrap();
1250        injected
1251            .mount(Path::new("/python"), Arc::new(package_mount))
1252            .unwrap();
1253
1254        let package_child = mem_fs::FileSystem::default();
1255        package_child
1256            .new_open_options()
1257            .write(true)
1258            .create_new(true)
1259            .open(Path::new("/child.txt"))
1260            .unwrap();
1261        injected
1262            .mount(Path::new("/python/lib"), Arc::new(package_child))
1263            .unwrap();
1264
1265        primary
1266            .add_mount_entries_with_mode(
1267                injected.mount_entries(),
1268                super::ExactMountConflictMode::KeepExisting,
1269            )
1270            .unwrap();
1271
1272        assert!(
1273            primary
1274                .metadata(Path::new("/python/user.txt"))
1275                .unwrap()
1276                .is_file()
1277        );
1278        assert_eq!(
1279            primary.metadata(Path::new("/python/pkg.txt")),
1280            Err(FsError::EntryNotFound)
1281        );
1282        assert_eq!(
1283            primary.metadata(Path::new("/python/lib/child.txt")),
1284            Err(FsError::EntryNotFound)
1285        );
1286    }
1287
1288    #[tokio::test]
1289    async fn test_replace_existing_conflict_replaces_the_whole_subtree() {
1290        let primary = MountFileSystem::new();
1291        let user_mount = mem_fs::FileSystem::default();
1292        user_mount
1293            .new_open_options()
1294            .write(true)
1295            .create_new(true)
1296            .open(Path::new("/user.txt"))
1297            .unwrap();
1298        let user_child = mem_fs::FileSystem::default();
1299        user_child
1300            .new_open_options()
1301            .write(true)
1302            .create_new(true)
1303            .open(Path::new("/user-child.txt"))
1304            .unwrap();
1305        primary
1306            .mount(Path::new("/python"), Arc::new(user_mount))
1307            .unwrap();
1308        primary
1309            .mount(Path::new("/python/lib"), Arc::new(user_child))
1310            .unwrap();
1311
1312        let injected = MountFileSystem::new();
1313        let package_mount = mem_fs::FileSystem::default();
1314        package_mount
1315            .new_open_options()
1316            .write(true)
1317            .create_new(true)
1318            .open(Path::new("/pkg.txt"))
1319            .unwrap();
1320        let package_child = mem_fs::FileSystem::default();
1321        package_child
1322            .new_open_options()
1323            .write(true)
1324            .create_new(true)
1325            .open(Path::new("/pkg-child.txt"))
1326            .unwrap();
1327        injected
1328            .mount(Path::new("/python"), Arc::new(package_mount))
1329            .unwrap();
1330        injected
1331            .mount(Path::new("/python/lib"), Arc::new(package_child))
1332            .unwrap();
1333
1334        primary
1335            .add_mount_entries_with_mode(
1336                injected.mount_entries(),
1337                super::ExactMountConflictMode::ReplaceExisting,
1338            )
1339            .unwrap();
1340
1341        assert_eq!(
1342            primary.metadata(Path::new("/python/user.txt")),
1343            Err(FsError::EntryNotFound)
1344        );
1345        assert_eq!(
1346            primary.metadata(Path::new("/python/lib/user-child.txt")),
1347            Err(FsError::EntryNotFound)
1348        );
1349        assert!(
1350            primary
1351                .metadata(Path::new("/python/pkg.txt"))
1352                .unwrap()
1353                .is_file()
1354        );
1355        assert!(
1356            primary
1357                .metadata(Path::new("/python/lib/pkg-child.txt"))
1358                .unwrap()
1359                .is_file()
1360        );
1361    }
1362
1363    #[tokio::test]
1364    async fn test_exact_mountpoints_reject_destructive_mutation() {
1365        let fs = MountFileSystem::new();
1366        let mounted = mem_fs::FileSystem::default();
1367        mounted.create_dir(Path::new("/dir")).unwrap();
1368        mounted
1369            .new_open_options()
1370            .write(true)
1371            .create_new(true)
1372            .open(Path::new("/file.txt"))
1373            .unwrap();
1374
1375        fs.mount(Path::new("/mounted"), Arc::new(mounted)).unwrap();
1376
1377        assert_eq!(
1378            fs.remove_dir(Path::new("/mounted")),
1379            Err(FsError::PermissionDenied)
1380        );
1381        assert_eq!(
1382            fs.remove_file(Path::new("/mounted")),
1383            Err(FsError::PermissionDenied)
1384        );
1385        assert_eq!(
1386            fs.rename(Path::new("/mounted"), Path::new("/other")).await,
1387            Err(FsError::PermissionDenied)
1388        );
1389        assert_eq!(
1390            fs.rename(Path::new("/mounted/file.txt"), Path::new("/mounted"))
1391                .await,
1392            Err(FsError::PermissionDenied)
1393        );
1394    }
1395
1396    #[tokio::test]
1397    async fn test_parent_read_dir_merges_leaf_entries_with_child_mounts() {
1398        let fs = MountFileSystem::new();
1399
1400        let top = MountlessFileSystem::default();
1401        top.create_dir(Path::new("/bin")).unwrap();
1402        top.new_open_options()
1403            .write(true)
1404            .create_new(true)
1405            .open(Path::new("/bin/tool"))
1406            .unwrap();
1407
1408        let nested = mem_fs::FileSystem::default();
1409        nested.create_dir(Path::new("/css")).unwrap();
1410        nested
1411            .new_open_options()
1412            .write(true)
1413            .create_new(true)
1414            .open(Path::new("/css/site.css"))
1415            .unwrap();
1416
1417        fs.mount(Path::new("/opt"), Arc::new(top)).unwrap();
1418        fs.mount(Path::new("/opt/assets"), Arc::new(nested))
1419            .unwrap();
1420
1421        let opt_contents = read_dir_names(&fs, "/opt");
1422        assert!(opt_contents.contains(&"bin".to_string()));
1423        assert!(opt_contents.contains(&"assets".to_string()));
1424    }
1425
1426    #[tokio::test]
1427    async fn test_child_mount_shadows_same_named_parent_entry() {
1428        let fs = MountFileSystem::new();
1429
1430        let top = MountlessFileSystem::default();
1431        top.new_open_options()
1432            .write(true)
1433            .create_new(true)
1434            .open(Path::new("/assets"))
1435            .unwrap();
1436
1437        let nested = mem_fs::FileSystem::default();
1438        nested.create_dir(Path::new("/css")).unwrap();
1439        nested
1440            .new_open_options()
1441            .write(true)
1442            .create_new(true)
1443            .open(Path::new("/css/site.css"))
1444            .unwrap();
1445
1446        fs.mount(Path::new("/opt"), Arc::new(top)).unwrap();
1447        fs.mount(Path::new("/opt/assets"), Arc::new(nested))
1448            .unwrap();
1449
1450        assert!(fs.metadata(Path::new("/opt/assets")).unwrap().is_dir());
1451        assert_eq!(
1452            read_dir_names(&fs, "/opt")
1453                .into_iter()
1454                .filter(|entry| entry == "assets")
1455                .count(),
1456            1,
1457        );
1458        assert!(fs.metadata(Path::new("/opt/assets/css/site.css")).is_ok());
1459    }
1460
1461    #[tokio::test]
1462    async fn test_read_dir_rebases_entries_under_nested_mount_subdirectory() {
1463        let fs = MountFileSystem::new();
1464
1465        let nested = mem_fs::FileSystem::default();
1466        nested.create_dir(Path::new("/css")).unwrap();
1467        nested
1468            .new_open_options()
1469            .write(true)
1470            .create_new(true)
1471            .open(Path::new("/css/site.css"))
1472            .unwrap();
1473
1474        fs.mount(Path::new("/opt/assets"), Arc::new(nested))
1475            .unwrap();
1476
1477        let css_contents: Vec<PathBuf> = fs
1478            .read_dir(Path::new("/opt/assets/css"))
1479            .unwrap()
1480            .map(|entry| entry.unwrap().path)
1481            .collect();
1482
1483        assert_eq!(
1484            css_contents,
1485            vec![PathBuf::from("/opt/assets/css/site.css")]
1486        );
1487    }
1488
1489    #[tokio::test]
1490    async fn test_mount_with_source_path_exposes_subtree() {
1491        let fs = MountFileSystem::new();
1492
1493        let source = mem_fs::FileSystem::default();
1494        source.create_dir(Path::new("/python")).unwrap();
1495        source
1496            .new_open_options()
1497            .write(true)
1498            .create_new(true)
1499            .open(Path::new("/python/lib.py"))
1500            .unwrap();
1501
1502        fs.mount_with_source(
1503            Path::new("/runtime"),
1504            Path::new("/python"),
1505            Arc::new(source),
1506        )
1507        .unwrap();
1508
1509        assert!(fs.metadata(Path::new("/runtime/lib.py")).unwrap().is_file());
1510        assert_eq!(read_dir_names(&fs, "/runtime"), vec!["lib.py".to_string()]);
1511    }
1512
1513    #[tokio::test]
1514    async fn test_nested_mount_inside_tree_preserves_sibling_files() {
1515        let fs = MountFileSystem::new();
1516
1517        let python = mem_fs::FileSystem::default();
1518        create_dir_all(&python, Path::new("/usr/local/lib/python3.13/encodings"));
1519        python
1520            .new_open_options()
1521            .write(true)
1522            .create_new(true)
1523            .open(Path::new("/usr/local/lib/python3.13/encodings/__init__.py"))
1524            .unwrap();
1525
1526        let host = mem_fs::FileSystem::default();
1527        host.new_open_options()
1528            .write(true)
1529            .create_new(true)
1530            .open(Path::new("/marker.txt"))
1531            .unwrap();
1532
1533        fs.mount(Path::new("/"), Arc::new(python)).unwrap();
1534        fs.mount(Path::new("/usr/local/lib/python3.13/test"), Arc::new(host))
1535            .unwrap();
1536
1537        assert!(
1538            fs.metadata(Path::new("/usr/local/lib/python3.13/encodings/__init__.py"))
1539                .unwrap()
1540                .is_file()
1541        );
1542        assert!(
1543            fs.metadata(Path::new("/usr/local/lib/python3.13/test/marker.txt"))
1544                .unwrap()
1545                .is_file()
1546        );
1547
1548        fs.new_open_options()
1549            .read(true)
1550            .open(Path::new("/usr/local/lib/python3.13/encodings/__init__.py"))
1551            .unwrap();
1552        fs.new_open_options()
1553            .read(true)
1554            .open(Path::new("/usr/local/lib/python3.13/test/marker.txt"))
1555            .unwrap();
1556
1557        let mut entries = read_dir_names(&fs, "/usr/local/lib/python3.13");
1558        entries.sort();
1559        assert_eq!(entries, vec!["encodings".to_string(), "test".to_string()]);
1560    }
1561
1562    #[tokio::test]
1563    async fn test_synthetic_parent_without_backing_dir_lists_child_mount() {
1564        let fs = MountFileSystem::new();
1565        fs.mount(Path::new("/"), Arc::new(mem_fs::FileSystem::default()))
1566            .unwrap();
1567
1568        let child = mem_fs::FileSystem::default();
1569        child
1570            .new_open_options()
1571            .write(true)
1572            .create_new(true)
1573            .open(Path::new("/marker.txt"))
1574            .unwrap();
1575        fs.mount(Path::new("/foo/bar"), Arc::new(child)).unwrap();
1576
1577        let entries = read_dir_names(&fs, "/foo");
1578        assert_eq!(entries, vec!["bar".to_string()]);
1579    }
1580
1581    #[tokio::test]
1582    async fn test_import_mounts_allows_shared_prefix_without_exact_mount_conflict() {
1583        let primary = MountFileSystem::new();
1584        let bin = mem_fs::FileSystem::default();
1585        bin.new_open_options()
1586            .write(true)
1587            .create_new(true)
1588            .open(Path::new("/tool"))
1589            .unwrap();
1590        primary.mount(Path::new("/opt/bin"), Arc::new(bin)).unwrap();
1591
1592        let injected = MountFileSystem::new();
1593        let assets = mem_fs::FileSystem::default();
1594        assets
1595            .new_open_options()
1596            .write(true)
1597            .create_new(true)
1598            .open(Path::new("/logo.svg"))
1599            .unwrap();
1600        injected
1601            .mount(Path::new("/opt/assets"), Arc::new(assets))
1602            .unwrap();
1603
1604        primary
1605            .add_mount_entries_with_mode(
1606                injected.mount_entries(),
1607                super::ExactMountConflictMode::Fail,
1608            )
1609            .unwrap();
1610
1611        assert!(primary.metadata(Path::new("/opt/bin/tool")).is_ok());
1612        assert!(primary.metadata(Path::new("/opt/assets/logo.svg")).is_ok());
1613    }
1614
1615    #[tokio::test]
1616    async fn test_import_mounts_rejects_exact_mount_conflict() {
1617        let primary = MountFileSystem::new();
1618        primary
1619            .mount(
1620                Path::new("/opt/bin"),
1621                Arc::new(mem_fs::FileSystem::default()),
1622            )
1623            .unwrap();
1624
1625        let injected = MountFileSystem::new();
1626        injected
1627            .mount(
1628                Path::new("/opt/bin"),
1629                Arc::new(mem_fs::FileSystem::default()),
1630            )
1631            .unwrap();
1632
1633        assert_eq!(
1634            primary.add_mount_entries_with_mode(
1635                injected.mount_entries(),
1636                super::ExactMountConflictMode::Fail,
1637            ),
1638            Err(FsError::AlreadyExists)
1639        );
1640    }
1641
1642    #[tokio::test]
1643    async fn test_new_filesystem() {
1644        let fs = gen_filesystem();
1645        assert!(
1646            fs.read_dir(Path::new("/test_new_filesystem")).is_ok(),
1647            "hostfs can read root"
1648        );
1649        let mut file_write = fs
1650            .new_open_options()
1651            .read(true)
1652            .write(true)
1653            .create_new(true)
1654            .open(Path::new("/test_new_filesystem/foo2.txt"))
1655            .unwrap();
1656        file_write.write_all(b"hello").await.unwrap();
1657        let _ = std::fs::remove_file("/test_new_filesystem/foo2.txt");
1658    }
1659
1660    #[tokio::test]
1661    async fn test_create_dir() {
1662        let fs = gen_filesystem();
1663
1664        assert_eq!(fs.create_dir(Path::new("/")), Ok(()));
1665
1666        assert_eq!(fs.create_dir(Path::new("/test_create_dir")), Ok(()));
1667
1668        assert_eq!(
1669            fs.create_dir(Path::new("/test_create_dir/foo")),
1670            Ok(()),
1671            "creating a directory",
1672        );
1673
1674        let cur_dir = read_dir_names(&fs, "/test_create_dir");
1675
1676        if !cur_dir.contains(&"foo".to_string()) {
1677            panic!("cur_dir does not contain foo: {cur_dir:#?}");
1678        }
1679
1680        assert!(
1681            cur_dir.contains(&"foo".to_string()),
1682            "the root is updated and well-defined"
1683        );
1684
1685        assert_eq!(
1686            fs.create_dir(Path::new("/test_create_dir/foo/bar")),
1687            Ok(()),
1688            "creating a sub-directory",
1689        );
1690
1691        let foo_dir = read_dir_names(&fs, "/test_create_dir/foo");
1692
1693        assert!(
1694            foo_dir.contains(&"bar".to_string()),
1695            "the foo directory is updated and well-defined"
1696        );
1697
1698        let bar_dir = read_dir_names(&fs, "/test_create_dir/foo/bar");
1699
1700        assert!(
1701            bar_dir.is_empty(),
1702            "the foo directory is updated and well-defined"
1703        );
1704        let _ = fs_extra::remove_items(&["/test_create_dir"]);
1705    }
1706
1707    #[tokio::test]
1708    async fn test_remove_dir() {
1709        let fs = gen_filesystem();
1710
1711        assert_eq!(
1712            fs.remove_dir(Path::new("/")),
1713            Err(FsError::PermissionDenied),
1714            "cannot remove the root directory",
1715        );
1716
1717        assert_eq!(
1718            fs.remove_dir(Path::new("/foo")),
1719            Err(FsError::EntryNotFound),
1720            "cannot remove a directory that doesn't exist",
1721        );
1722
1723        assert_eq!(fs.create_dir(Path::new("/test_remove_dir")), Ok(()));
1724
1725        assert_eq!(
1726            fs.create_dir(Path::new("/test_remove_dir/foo")),
1727            Ok(()),
1728            "creating a directory",
1729        );
1730
1731        assert_eq!(
1732            fs.create_dir(Path::new("/test_remove_dir/foo/bar")),
1733            Ok(()),
1734            "creating a sub-directory",
1735        );
1736
1737        assert!(
1738            read_dir_names(&fs, "/test_remove_dir/foo").contains(&"bar".to_string()),
1739            "./foo/bar exists"
1740        );
1741
1742        assert_eq!(
1743            fs.remove_dir(Path::new("/test_remove_dir/foo")),
1744            Err(FsError::DirectoryNotEmpty),
1745            "removing a directory that has children",
1746        );
1747
1748        assert_eq!(
1749            fs.remove_dir(Path::new("/test_remove_dir/foo/bar")),
1750            Ok(()),
1751            "removing a sub-directory",
1752        );
1753
1754        assert_eq!(
1755            fs.remove_dir(Path::new("/test_remove_dir/foo")),
1756            Ok(()),
1757            "removing a directory",
1758        );
1759
1760        assert!(
1761            !read_dir_names(&fs, "/test_remove_dir").contains(&"foo".to_string()),
1762            "the foo directory still exists"
1763        );
1764    }
1765
1766    fn read_dir_names(fs: &dyn crate::FileSystem, path: &str) -> Vec<String> {
1767        fs.read_dir(Path::new(path))
1768            .unwrap()
1769            .filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
1770            .collect::<Vec<_>>()
1771    }
1772
1773    fn create_dir_all(fs: &mem_fs::FileSystem, path: &Path) {
1774        let mut current = PathBuf::from("/");
1775
1776        for component in path.iter().skip(1) {
1777            current.push(component);
1778
1779            if fs.metadata(&current).is_err() {
1780                fs.create_dir(&current).unwrap();
1781            }
1782        }
1783    }
1784
1785    #[tokio::test]
1786    async fn test_rename() {
1787        let fs = gen_filesystem();
1788
1789        assert_eq!(
1790            fs.rename(Path::new("/"), Path::new("/bar")).await,
1791            Err(FsError::PermissionDenied),
1792            "renaming a directory that has no parent",
1793        );
1794        assert_eq!(
1795            fs.rename(Path::new("/foo"), Path::new("/")).await,
1796            Err(FsError::PermissionDenied),
1797            "renaming to the synthetic root directory is rejected",
1798        );
1799
1800        assert_eq!(fs.create_dir(Path::new("/test_rename")), Ok(()));
1801        assert_eq!(fs.create_dir(Path::new("/test_rename/foo")), Ok(()));
1802        assert_eq!(fs.create_dir(Path::new("/test_rename/foo/qux")), Ok(()));
1803
1804        assert_eq!(
1805            fs.rename(
1806                Path::new("/test_rename/foo"),
1807                Path::new("/test_rename/bar/baz")
1808            )
1809            .await,
1810            Err(FsError::EntryNotFound),
1811            "renaming to a directory that has parent that doesn't exist",
1812        );
1813
1814        assert_eq!(fs.create_dir(Path::new("/test_rename/bar")), Ok(()));
1815
1816        assert_eq!(
1817            fs.rename(Path::new("/test_rename/foo"), Path::new("/test_rename/bar"))
1818                .await,
1819            Ok(()),
1820            "renaming to a directory that has parent that exists",
1821        );
1822
1823        assert!(
1824            fs.new_open_options()
1825                .write(true)
1826                .create_new(true)
1827                .open(Path::new("/test_rename/bar/hello1.txt"))
1828                .is_ok(),
1829            "creating a new file (`hello1.txt`)",
1830        );
1831        assert!(
1832            fs.new_open_options()
1833                .write(true)
1834                .create_new(true)
1835                .open(Path::new("/test_rename/bar/hello2.txt"))
1836                .is_ok(),
1837            "creating a new file (`hello2.txt`)",
1838        );
1839
1840        let cur_dir = read_dir_names(&fs, "/test_rename");
1841
1842        assert!(
1843            !cur_dir.contains(&"foo".to_string()),
1844            "the foo directory still exists"
1845        );
1846
1847        assert!(
1848            cur_dir.contains(&"bar".to_string()),
1849            "the bar directory still exists"
1850        );
1851
1852        let bar_dir = read_dir_names(&fs, "/test_rename/bar");
1853
1854        if !bar_dir.contains(&"qux".to_string()) {
1855            println!("qux does not exist: {bar_dir:?}")
1856        }
1857
1858        let qux_dir = read_dir_names(&fs, "/test_rename/bar/qux");
1859
1860        assert!(qux_dir.is_empty(), "the qux directory is empty");
1861
1862        assert!(
1863            read_dir_names(&fs, "/test_rename/bar").contains(&"hello1.txt".to_string()),
1864            "the /bar/hello1.txt file exists"
1865        );
1866
1867        assert!(
1868            read_dir_names(&fs, "/test_rename/bar").contains(&"hello2.txt".to_string()),
1869            "the /bar/hello2.txt file exists"
1870        );
1871
1872        assert_eq!(
1873            fs.create_dir(Path::new("/test_rename/foo")),
1874            Ok(()),
1875            "create ./foo again",
1876        );
1877
1878        assert_eq!(
1879            fs.rename(
1880                Path::new("/test_rename/bar/hello2.txt"),
1881                Path::new("/test_rename/foo/world2.txt")
1882            )
1883            .await,
1884            Ok(()),
1885            "renaming (and moving) a file",
1886        );
1887
1888        assert_eq!(
1889            fs.rename(
1890                Path::new("/test_rename/foo"),
1891                Path::new("/test_rename/bar/baz")
1892            )
1893            .await,
1894            Ok(()),
1895            "renaming a directory",
1896        );
1897
1898        assert_eq!(
1899            fs.rename(
1900                Path::new("/test_rename/bar/hello1.txt"),
1901                Path::new("/test_rename/bar/world1.txt")
1902            )
1903            .await,
1904            Ok(()),
1905            "renaming a file (in the same directory)",
1906        );
1907
1908        assert!(
1909            read_dir_names(&fs, "/test_rename").contains(&"bar".to_string()),
1910            "./bar exists"
1911        );
1912
1913        assert!(
1914            read_dir_names(&fs, "/test_rename/bar").contains(&"baz".to_string()),
1915            "/bar/baz exists"
1916        );
1917        assert!(
1918            !read_dir_names(&fs, "/test_rename").contains(&"foo".to_string()),
1919            "foo does not exist anymore"
1920        );
1921        assert!(
1922            read_dir_names(&fs, "/test_rename/bar/baz").contains(&"world2.txt".to_string()),
1923            "/bar/baz/world2.txt exists"
1924        );
1925        assert!(
1926            read_dir_names(&fs, "/test_rename/bar").contains(&"world1.txt".to_string()),
1927            "/bar/world1.txt (ex hello1.txt) exists"
1928        );
1929        assert!(
1930            !read_dir_names(&fs, "/test_rename/bar").contains(&"hello1.txt".to_string()),
1931            "hello1.txt was moved"
1932        );
1933        assert!(
1934            !read_dir_names(&fs, "/test_rename/bar").contains(&"hello2.txt".to_string()),
1935            "hello2.txt was moved"
1936        );
1937        assert!(
1938            read_dir_names(&fs, "/test_rename/bar/baz").contains(&"world2.txt".to_string()),
1939            "world2.txt was moved to the correct place"
1940        );
1941
1942        let _ = fs_extra::remove_items(&["/test_rename"]);
1943    }
1944
1945    #[tokio::test]
1946    async fn cross_mount_file_rename_copies_and_removes_source() {
1947        let fs = MountFileSystem::new();
1948        let left = TmpFileSystem::new();
1949        let right = TmpFileSystem::new();
1950
1951        left.new_open_options()
1952            .create(true)
1953            .write(true)
1954            .open(Path::new("/from.txt"))
1955            .unwrap();
1956
1957        fs.mount(Path::new("/left"), Arc::new(left.clone()))
1958            .unwrap();
1959        fs.mount(Path::new("/right"), Arc::new(right.clone()))
1960            .unwrap();
1961
1962        fs.rename(Path::new("/left/from.txt"), Path::new("/right/to.txt"))
1963            .await
1964            .unwrap();
1965
1966        assert_eq!(
1967            left.metadata(Path::new("/from.txt")),
1968            Err(FsError::EntryNotFound)
1969        );
1970        assert!(right.metadata(Path::new("/to.txt")).unwrap().is_file());
1971    }
1972
1973    #[tokio::test]
1974    async fn test_metadata() {
1975        use std::thread::sleep;
1976        use std::time::Duration;
1977
1978        let fs = gen_filesystem();
1979
1980        let root_metadata = fs.metadata(Path::new("/test_metadata")).unwrap();
1981
1982        assert!(root_metadata.ft.dir);
1983        assert_eq!(root_metadata.accessed, root_metadata.created);
1984        assert_eq!(root_metadata.modified, root_metadata.created);
1985        assert!(root_metadata.modified > 0);
1986
1987        assert_eq!(fs.create_dir(Path::new("/test_metadata/foo")), Ok(()));
1988
1989        let foo_metadata = fs.metadata(Path::new("/test_metadata/foo"));
1990        assert!(foo_metadata.is_ok());
1991        let foo_metadata = foo_metadata.unwrap();
1992
1993        assert!(foo_metadata.ft.dir);
1994        assert!(foo_metadata.accessed == foo_metadata.created);
1995        assert!(foo_metadata.modified == foo_metadata.created);
1996        assert!(foo_metadata.modified > 0);
1997
1998        sleep(Duration::from_secs(3));
1999
2000        assert_eq!(
2001            fs.rename(
2002                Path::new("/test_metadata/foo"),
2003                Path::new("/test_metadata/bar")
2004            )
2005            .await,
2006            Ok(())
2007        );
2008
2009        let bar_metadata = fs.metadata(Path::new("/test_metadata/bar")).unwrap();
2010        assert!(bar_metadata.ft.dir);
2011        assert!(bar_metadata.accessed == foo_metadata.accessed);
2012        assert!(bar_metadata.created == foo_metadata.created);
2013        assert!(bar_metadata.modified > foo_metadata.modified);
2014
2015        let root_metadata = fs.metadata(Path::new("/test_metadata/bar")).unwrap();
2016        assert!(
2017            root_metadata.modified > foo_metadata.modified,
2018            "the parent modified time was updated"
2019        );
2020
2021        let _ = fs_extra::remove_items(&["/test_metadata"]);
2022    }
2023
2024    #[tokio::test]
2025    async fn test_remove_file() {
2026        let fs = gen_filesystem();
2027
2028        assert!(
2029            fs.new_open_options()
2030                .write(true)
2031                .create_new(true)
2032                .open(Path::new("/test_remove_file/foo.txt"))
2033                .is_ok(),
2034            "creating a new file",
2035        );
2036
2037        assert!(read_dir_names(&fs, "/test_remove_file").contains(&"foo.txt".to_string()));
2038
2039        assert_eq!(
2040            fs.remove_file(Path::new("/test_remove_file/foo.txt")),
2041            Ok(()),
2042            "removing a file that exists",
2043        );
2044
2045        assert!(!read_dir_names(&fs, "/test_remove_file").contains(&"foo.txt".to_string()));
2046
2047        assert_eq!(
2048            fs.remove_file(Path::new("/test_remove_file/foo.txt")),
2049            Err(FsError::EntryNotFound),
2050            "removing a file that doesn't exists",
2051        );
2052
2053        let _ = fs_extra::remove_items(&["./test_remove_file"]);
2054    }
2055
2056    #[tokio::test]
2057    async fn test_readdir() {
2058        let fs = gen_filesystem();
2059
2060        assert_eq!(
2061            fs.create_dir(Path::new("/test_readdir/foo")),
2062            Ok(()),
2063            "creating `foo`"
2064        );
2065        assert_eq!(
2066            fs.create_dir(Path::new("/test_readdir/foo/sub")),
2067            Ok(()),
2068            "creating `sub`"
2069        );
2070        assert_eq!(
2071            fs.create_dir(Path::new("/test_readdir/bar")),
2072            Ok(()),
2073            "creating `bar`"
2074        );
2075        assert_eq!(
2076            fs.create_dir(Path::new("/test_readdir/baz")),
2077            Ok(()),
2078            "creating `bar`"
2079        );
2080        assert!(
2081            fs.new_open_options()
2082                .write(true)
2083                .create_new(true)
2084                .open(Path::new("/test_readdir/a.txt"))
2085                .is_ok(),
2086            "creating `a.txt`",
2087        );
2088        assert!(
2089            fs.new_open_options()
2090                .write(true)
2091                .create_new(true)
2092                .open(Path::new("/test_readdir/b.txt"))
2093                .is_ok(),
2094            "creating `b.txt`",
2095        );
2096
2097        println!("fs: {fs:?}");
2098
2099        let readdir = fs.read_dir(Path::new("/test_readdir"));
2100
2101        assert!(readdir.is_ok(), "reading the directory `/test_readdir/`");
2102
2103        let mut readdir = readdir.unwrap();
2104
2105        let next = readdir.next().unwrap().unwrap();
2106        assert!(next.path.ends_with("foo"), "checking entry #1");
2107        println!("entry 1: {next:#?}");
2108        assert!(next.file_type().unwrap().is_dir(), "checking entry #1");
2109
2110        let next = readdir.next().unwrap().unwrap();
2111        assert!(next.path.ends_with("bar"), "checking entry #2");
2112        assert!(next.file_type().unwrap().is_dir(), "checking entry #2");
2113
2114        let next = readdir.next().unwrap().unwrap();
2115        assert!(next.path.ends_with("baz"), "checking entry #3");
2116        assert!(next.file_type().unwrap().is_dir(), "checking entry #3");
2117
2118        let next = readdir.next().unwrap().unwrap();
2119        assert!(next.path.ends_with("a.txt"), "checking entry #2");
2120        assert!(next.file_type().unwrap().is_file(), "checking entry #4");
2121
2122        let next = readdir.next().unwrap().unwrap();
2123        assert!(next.path.ends_with("b.txt"), "checking entry #2");
2124        assert!(next.file_type().unwrap().is_file(), "checking entry #5");
2125
2126        if let Some(s) = readdir.next() {
2127            panic!("next: {s:?}");
2128        }
2129
2130        let _ = fs_extra::remove_items(&["./test_readdir"]);
2131    }
2132
2133    /*
2134    #[tokio::test]
2135    async fn test_canonicalize() {
2136        let fs = gen_filesystem();
2137
2138        let root_dir = env!("CARGO_MANIFEST_DIR");
2139
2140        let _ = fs_extra::remove_items(&["./test_canonicalize"]);
2141
2142        assert_eq!(
2143            fs.create_dir(Path::new("./test_canonicalize")),
2144            Ok(()),
2145            "creating `test_canonicalize`"
2146        );
2147
2148        assert_eq!(
2149            fs.create_dir(Path::new("./test_canonicalize/foo")),
2150            Ok(()),
2151            "creating `foo`"
2152        );
2153        assert_eq!(
2154            fs.create_dir(Path::new("./test_canonicalize/foo/bar")),
2155            Ok(()),
2156            "creating `bar`"
2157        );
2158        assert_eq!(
2159            fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz")),
2160            Ok(()),
2161            "creating `baz`",
2162        );
2163        assert_eq!(
2164            fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz/qux")),
2165            Ok(()),
2166            "creating `qux`",
2167        );
2168        assert!(
2169            matches!(
2170                fs.new_open_options()
2171                    .write(true)
2172                    .create_new(true)
2173                    .open(Path::new("./test_canonicalize/foo/bar/baz/qux/hello.txt")),
2174                Ok(_)
2175            ),
2176            "creating `hello.txt`",
2177        );
2178
2179        assert_eq!(
2180            fs.canonicalize(Path::new("./test_canonicalize")),
2181            Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
2182            "canonicalizing `/`",
2183        );
2184        assert_eq!(
2185            fs.canonicalize(Path::new("foo")),
2186            Err(FsError::InvalidInput),
2187            "canonicalizing `foo`",
2188        );
2189        assert_eq!(
2190            fs.canonicalize(Path::new("./test_canonicalize/././././foo/")),
2191            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo")).to_path_buf()),
2192            "canonicalizing `/././././foo/`",
2193        );
2194        assert_eq!(
2195            fs.canonicalize(Path::new("./test_canonicalize/foo/bar//")),
2196            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
2197            "canonicalizing `/foo/bar//`",
2198        );
2199        assert_eq!(
2200            fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../bar")),
2201            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
2202            "canonicalizing `/foo/bar/../bar`",
2203        );
2204        assert_eq!(
2205            fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../..")),
2206            Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
2207            "canonicalizing `/foo/bar/../..`",
2208        );
2209        assert_eq!(
2210            fs.canonicalize(Path::new("/foo/bar/../../..")),
2211            Err(FsError::InvalidInput),
2212            "canonicalizing `/foo/bar/../../..`",
2213        );
2214        assert_eq!(
2215            fs.canonicalize(Path::new("C:/foo/")),
2216            Err(FsError::InvalidInput),
2217            "canonicalizing `C:/foo/`",
2218        );
2219        assert_eq!(
2220            fs.canonicalize(Path::new(
2221                "./test_canonicalize/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
2222            )),
2223            Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar/baz/qux/hello.txt")).to_path_buf()),
2224            "canonicalizing a crazily stupid path name",
2225        );
2226
2227        let _ = fs_extra::remove_items(&["./test_canonicalize"]);
2228    }
2229    */
2230}