Skip to main content

over_there/core/server/fs/
mod.rs

1mod dir;
2mod file;
3
4pub use dir::LocalDirEntry;
5pub use file::{
6    LocalFile, LocalFileError, LocalFileHandle, LocalFilePermissions,
7};
8
9use std::collections::{hash_map::Entry, HashMap};
10use std::io;
11use std::path::{Path, PathBuf};
12
13#[derive(Debug)]
14pub struct FileSystemManager {
15    files: HashMap<u32, LocalFile>,
16}
17
18impl Default for FileSystemManager {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl FileSystemManager {
25    pub fn new() -> Self {
26        Self {
27            files: HashMap::new(),
28        }
29    }
30
31    /// Creates a new directory
32    pub async fn create_dir(
33        &self,
34        path: impl AsRef<Path>,
35        create_components: bool,
36    ) -> io::Result<()> {
37        let path = clean_path(path.as_ref()).await;
38        dir::create(path, create_components).await
39    }
40
41    /// Attempts to rename an entire directory.
42    ///
43    /// Will fail if there is an open file within the directory on any level.
44    pub async fn rename_dir(
45        &mut self,
46        from: impl AsRef<Path>,
47        to: impl AsRef<Path>,
48    ) -> io::Result<()> {
49        let from = clean_path(from.as_ref()).await;
50        let to = clean_path(to.as_ref()).await;
51
52        self.check_no_open_files(from.as_path())?;
53
54        // No open file is within this directory, so good to attempt to rename
55        dir::rename(from.as_path(), to.as_path()).await?;
56
57        Ok(())
58    }
59
60    /// Attempts to remove an entire directory, failing if any file is
61    /// currently open within the directory.
62    pub async fn remove_dir(
63        &mut self,
64        path: impl AsRef<Path>,
65        non_empty: bool,
66    ) -> io::Result<()> {
67        let path = clean_path(path.as_ref()).await;
68
69        self.check_no_open_files(path.as_path())?;
70
71        // No open file is within this directory, so good to attempt to remove
72        dir::remove(path, non_empty).await
73    }
74
75    /// Retrieves all entries within the directory `path`.
76    ///
77    /// This is a non-recursive operation, meaning that it will only yield
78    /// the immediate directory entires and not walk through subdirectories
79    /// or follow symlinks.
80    ///
81    /// Will yield an error if there are complications with reading the
82    /// directory and its entries.
83    pub async fn dir_entries(
84        &self,
85        path: impl AsRef<Path>,
86    ) -> io::Result<Vec<LocalDirEntry>> {
87        let path = clean_path(path.as_ref()).await;
88
89        dir::entries(path).await
90    }
91
92    /// Opens a file, creating it if `create` true, using `write` and `read`
93    /// for permissions.
94    ///
95    /// If the file is already open, will return the existing instance. If
96    /// permissions differ where the returned file does not have read/write
97    /// access and the request asks for it, the current instance of the file
98    /// will be closed and a new instance with the same id will be opened with
99    /// the new permissions where existing and requested permissions align.
100    pub async fn open_file(
101        &mut self,
102        path: impl AsRef<Path>,
103        create: bool,
104        write: bool,
105        read: bool,
106    ) -> io::Result<LocalFileHandle> {
107        let path = clean_path(path.as_ref()).await;
108
109        let mut new_permissions = LocalFilePermissions { read, write };
110        let mut maybe_id_and_sig = None;
111
112        // TODO: Perform more optimal lookup by filtering down open files
113        //       using a path tree?
114        let search =
115            self.files.values_mut().find(|f| f.path() == path.as_path());
116
117        // If we found a match, check the permissions to see if we can return
118        // it or if we need to open a new copy with the proper merged
119        // permissions
120        if let Some(file) = search {
121            let id = file.id();
122            let sig = file.sig();
123            let permissions = file.permissions();
124
125            // We already have read permission or are not asking for it and
126            // we already have write permission or are not asking for it
127            if (permissions.read || !read) && (permissions.write || !write) {
128                return Ok(file.handle());
129            } else {
130                // Otherwise, we now need to open a new file pointer with the
131                // proper permissions to support both cases and, if successful,
132                // close the existing file
133                new_permissions.read = permissions.read || read;
134                new_permissions.write = permissions.write || write;
135                maybe_id_and_sig = Some((id, sig));
136            }
137        }
138
139        // Open the file with the specified path
140        let mut new_file = LocalFile::open(
141            path,
142            create,
143            new_permissions.write,
144            new_permissions.read,
145        )
146        .await?;
147
148        // If we already had a file open with this path, we want to assign
149        // the previously-used id and sig
150        if let Some((id, sig)) = maybe_id_and_sig {
151            new_file.id = id;
152            new_file.sig = sig;
153        }
154
155        // Insert the file & permissions, overwriting the
156        // existing file/permissions
157        let handle = new_file.handle();
158        self.files.insert(new_file.id(), new_file);
159
160        Ok(handle)
161    }
162
163    /// Closes an open file by `handle`.
164    ///
165    /// Will fail if no file with `handle` id is open, or if the signature
166    /// on the file is different than that of `handle`.
167    pub fn close_file(
168        &mut self,
169        handle: LocalFileHandle,
170    ) -> io::Result<LocalFile> {
171        match self.files.entry(handle.id) {
172            Entry::Occupied(x) if x.get().sig == handle.sig => Ok(x.remove()),
173            Entry::Occupied(_) => Err(io::Error::new(
174                io::ErrorKind::InvalidInput,
175                format!("Signature invalid for file with id {}", handle.id),
176            )),
177            Entry::Vacant(_) => Err(io::Error::new(
178                io::ErrorKind::NotFound,
179                format!("No open file with id {}", handle.id),
180            )),
181        }
182    }
183
184    /// Attempts to rename a file at `from` into `to`.
185    ///
186    /// Will fail if file is open at `from`.
187    pub async fn rename_file(
188        &mut self,
189        from: impl AsRef<Path>,
190        to: impl AsRef<Path>,
191    ) -> io::Result<()> {
192        let from = clean_path(from.as_ref()).await;
193        let to = clean_path(to.as_ref()).await;
194
195        self.check_no_open_files(from.as_path())?;
196
197        file::rename(from.as_path(), to.as_path()).await
198    }
199
200    /// Attempts to remove a file, failing if the file is currently open.
201    pub async fn remove_file(
202        &mut self,
203        path: impl AsRef<Path>,
204    ) -> io::Result<()> {
205        let path = clean_path(path.as_ref()).await;
206
207        self.check_no_open_files(path.as_path())?;
208
209        file::remove(path).await
210    }
211
212    /// Represents the total files that are open within the manager
213    pub fn file_cnt(&self) -> usize {
214        self.files.len()
215    }
216
217    /// Looks up an open file by its associated `id`
218    pub fn get_mut(&mut self, id: impl Into<u32>) -> Option<&mut LocalFile> {
219        match self.files.get_mut(&id.into()) {
220            Some(file) => Some(file),
221            None => None,
222        }
223    }
224
225    /// Looks up an open file by its associated `id`
226    pub fn get(&self, id: impl Into<u32>) -> Option<&LocalFile> {
227        match self.files.get(&id.into()) {
228            Some(file) => Some(file),
229            None => None,
230        }
231    }
232
233    /// Determines if a file is open with the specified `id`
234    pub fn exists(&self, id: impl Into<u32>) -> bool {
235        self.get(id).is_some()
236    }
237
238    /// Checks that `path` is not an open file or (if dir) does not contain any
239    /// open files managed by the file system manager
240    fn check_no_open_files(&self, path: impl AsRef<Path>) -> io::Result<()> {
241        for f in self.files.values() {
242            if f.path().starts_with(path.as_ref()) {
243                return Err(io::Error::new(
244                    io::ErrorKind::InvalidData,
245                    format!(
246                        "File at {:?} is open and must be closed",
247                        f.path()
248                    ),
249                ));
250            }
251        }
252
253        Ok(())
254    }
255}
256
257/// Attempts to canonicalize the path, returning the canonicalized form
258/// or the original form if failed.
259async fn clean_path(path: impl AsRef<Path>) -> PathBuf {
260    tokio::fs::canonicalize(path.as_ref())
261        .await
262        .ok()
263        .unwrap_or_else(|| path.as_ref().to_path_buf())
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use tokio::fs;
270
271    #[tokio::test]
272    async fn create_dir_should_yield_error_if_parent_dirs_missing_and_flag_not_set(
273    ) {
274        let root = tempfile::tempdir().unwrap();
275        let fsm = FileSystemManager::new();
276
277        let result =
278            fsm.create_dir(root.as_ref().join("some/dir"), false).await;
279        assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
280    }
281
282    #[tokio::test]
283    async fn create_dir_should_return_success_if_created_the_path() {
284        let root = tempfile::tempdir().unwrap();
285        let fsm = FileSystemManager::new();
286
287        let path = root.as_ref().join("test-dir");
288        let result = fsm.create_dir(path.as_path(), false).await;
289        assert!(
290            result.is_ok(),
291            "Unexpectedly failed to create dir: {:?}",
292            result
293        );
294        assert!(fs::metadata(path).await.is_ok(), "Directory  missing");
295
296        let path = root.as_ref().join("some/test-dir");
297        let result = fsm.create_dir(path.as_path(), true).await;
298        assert!(
299            result.is_ok(),
300            "Unexpectedly failed to create nested dir: {:?}",
301            result
302        );
303        assert!(fs::metadata(path).await.is_ok(), "Directory missing");
304    }
305
306    #[tokio::test]
307    async fn rename_dir_should_yield_error_if_origin_path_does_not_exist() {
308        let root = tempfile::tempdir().unwrap();
309        let mut fsm = FileSystemManager::new();
310
311        let origin = root.as_ref().join("origin");
312        let destination = root.as_ref().join("destination");
313
314        match fsm.rename_dir(origin, destination).await {
315            Err(x) => assert_eq!(x.kind(), io::ErrorKind::NotFound),
316            x => panic!("Unexpected result: {:?}", x),
317        }
318    }
319
320    #[tokio::test]
321    async fn rename_dir_should_yield_error_if_origin_path_is_not_a_directory() {
322        let root = tempfile::tempdir().unwrap();
323        let mut fsm = FileSystemManager::new();
324
325        // Make origin a file instead of directory
326        let origin_file =
327            tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
328
329        let destination = root.as_ref().join("destination");
330
331        match fsm.rename_dir(origin_file.as_ref(), destination).await {
332            Err(_) => (),
333            x => panic!("Unexpected result: {:?}", x),
334        }
335    }
336
337    #[tokio::test]
338    async fn rename_dir_should_yield_error_if_contains_open_files() {
339        let root = tempfile::tempdir().unwrap();
340        let mut fsm = FileSystemManager::new();
341
342        let origin = root.as_ref().join("origin");
343        fs::create_dir(origin.as_path()).await.unwrap();
344
345        // Create a file in origin
346        let _file1 = fsm
347            .open_file(origin.as_path().join("file1"), true, true, true)
348            .await
349            .unwrap();
350
351        let destination = root.as_ref().join("destination");
352
353        match fsm
354            .rename_dir(origin.as_path(), destination.as_path())
355            .await
356        {
357            Err(x) => assert_eq!(x.kind(), io::ErrorKind::InvalidData),
358            x => panic!("Unexpected result: {:?}", x),
359        }
360    }
361
362    #[tokio::test]
363    async fn rename_dir_should_return_success_if_renamed_directory() {
364        let root = tempfile::tempdir().unwrap();
365        let mut fsm = FileSystemManager::new();
366
367        let origin = root.as_ref().join("origin");
368        fs::create_dir(origin.as_path()).await.unwrap();
369
370        let destination = root.as_ref().join("destination");
371
372        match fsm.rename_dir(origin, destination).await {
373            Ok(_) => (),
374            x => panic!("Unexpected result: {:?}", x),
375        }
376    }
377
378    #[tokio::test]
379    async fn dir_entries_should_yield_error_if_path_not_a_directory() {
380        let root = tempfile::tempdir().unwrap();
381        let fsm = FileSystemManager::new();
382
383        let file = tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
384
385        match fsm.dir_entries(file.path()).await {
386            Err(x) if x.kind() == io::ErrorKind::Other => (),
387            x => panic!("Unexpected result: {:?}", x),
388        }
389    }
390
391    #[tokio::test]
392    async fn dir_entries_should_return_a_list_of_immediate_entries_in_a_directory(
393    ) {
394        let root = tempfile::tempdir().unwrap();
395        let fsm = FileSystemManager::new();
396
397        let file = tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
398        let dir = tempfile::tempdir_in(root.as_ref()).unwrap();
399        let inner_file = tempfile::NamedTempFile::new_in(dir.as_ref()).unwrap();
400
401        match fsm.dir_entries(root.as_ref()).await {
402            Ok(entries) => {
403                assert_eq!(
404                    entries.len(),
405                    2,
406                    "Unexpected entry count: {}",
407                    entries.len()
408                );
409                assert!(
410                    entries.contains(&LocalDirEntry {
411                        path: clean_path(file.as_ref()).await,
412                        is_file: true,
413                        is_dir: false,
414                        is_symlink: false,
415                    }),
416                    "Missing file"
417                );
418                assert!(
419                    entries.contains(&LocalDirEntry {
420                        path: clean_path(dir.as_ref()).await,
421                        is_file: false,
422                        is_dir: true,
423                        is_symlink: false,
424                    }),
425                    "Missing dir"
426                );
427                assert!(
428                    !entries.contains(&LocalDirEntry {
429                        path: clean_path(inner_file.as_ref()).await,
430                        is_file: true,
431                        is_dir: false,
432                        is_symlink: false,
433                    }),
434                    "Unexpectedly found nested file"
435                );
436            }
437            x => panic!("Unexpected result: {:?}", x),
438        }
439    }
440
441    #[tokio::test]
442    async fn remove_dir_should_yield_error_if_directory_not_empty_and_flag_not_set(
443    ) {
444        let root = tempfile::tempdir().unwrap();
445        let mut fsm = FileSystemManager::new();
446
447        // NOTE: Must be kept around so that the file exists when removing dir
448        let _file = tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
449
450        match fsm.remove_dir(root.as_ref(), false).await {
451            Err(x) if x.kind() == io::ErrorKind::Other => (),
452            x => panic!("Unexpected result: {:?}", x),
453        }
454    }
455
456    #[tokio::test]
457    async fn remove_dir_should_yield_error_if_open_files_exist_in_directory() {
458        let root = tempfile::tempdir().unwrap();
459        let mut fsm = FileSystemManager::new();
460
461        fsm.open_file(root.as_ref().join("test-file"), true, true, true)
462            .await
463            .expect("Failed to open file with manager");
464
465        // Even though we want to remove everything, still cannot do it because
466        // a local file is open
467        match fsm.remove_dir(root.as_ref(), true).await {
468            Err(x) if x.kind() == io::ErrorKind::InvalidData => (),
469            x => panic!("Unexpected result: {:?}", x),
470        }
471    }
472
473    #[tokio::test]
474    async fn remove_dir_should_return_success_if_removed_directory() {
475        let root = tempfile::tempdir().unwrap();
476        let mut fsm = FileSystemManager::new();
477
478        let _ = tempfile::tempfile_in(root.as_ref()).unwrap();
479
480        match fsm.remove_dir(root.as_ref(), true).await {
481            Ok(_) => (),
482            x => panic!("Unexpected result: {:?}", x),
483        }
484    }
485
486    #[tokio::test]
487    async fn open_file_should_yield_error_if_underlying_open_fails() {
488        let root = tempfile::tempdir().unwrap();
489        let mut fsm = FileSystemManager::new();
490
491        let not_a_file = tempfile::tempdir_in(root.as_ref()).unwrap();
492
493        match fsm.open_file(not_a_file.as_ref(), true, true, true).await {
494            Err(x) if x.kind() == io::ErrorKind::Other => (),
495            x => panic!("Unexpected result: {:?}", x),
496        }
497    }
498
499    #[tokio::test]
500    async fn open_file_should_return_existing_open_file_if_permissions_allow() {
501        let root = tempfile::tempdir().unwrap();
502        let mut fsm = FileSystemManager::new();
503
504        // Open with absolute path
505        let handle = fsm
506            .open_file(root.as_ref().join("test-file"), true, true, true)
507            .await
508            .expect("Failed to create file");
509
510        assert_eq!(
511            fsm.file_cnt(),
512            1,
513            "Unexpected number of open files: {}",
514            fsm.file_cnt()
515        );
516        assert_eq!(
517            fsm.get(handle).map(|f| f.permissions()),
518            Some(LocalFilePermissions {
519                read: true,
520                write: true
521            })
522        );
523
524        // Open with unresolved path (read-only)
525        let handle_2 = fsm
526            .open_file(
527                root.as_ref().join(".").join("test-file"),
528                false,
529                false,
530                true,
531            )
532            .await
533            .expect("Failed to open file for read");
534
535        assert_eq!(
536            fsm.file_cnt(),
537            1,
538            "Unexpected number of open files: {}",
539            fsm.file_cnt()
540        );
541
542        assert_eq!(handle, handle_2);
543
544        assert_eq!(
545            fsm.get(handle_2).map(|f| f.permissions()),
546            Some(LocalFilePermissions {
547                read: true,
548                write: true
549            })
550        );
551
552        // Open with absolute path (write-only)
553        let handle_3 = fsm
554            .open_file(root.as_ref().join("test-file"), false, true, false)
555            .await
556            .expect("Failed to open file for write");
557
558        assert_eq!(
559            fsm.file_cnt(),
560            1,
561            "Unexpected number of open files: {}",
562            fsm.file_cnt()
563        );
564
565        assert_eq!(handle, handle_3);
566
567        assert_eq!(
568            fsm.get(handle_3).map(|f| f.permissions()),
569            Some(LocalFilePermissions {
570                read: true,
571                write: true
572            })
573        );
574    }
575
576    #[tokio::test]
577    async fn open_file_should_reopen_an_open_file_if_permissions_need_merging()
578    {
579        let root = tempfile::tempdir().unwrap();
580        let mut fsm = FileSystemManager::new();
581
582        // Open write-only
583        let handle = fsm
584            .open_file(root.as_ref().join("test-file"), true, true, false)
585            .await
586            .expect("Failed to create file");
587
588        assert_eq!(
589            fsm.file_cnt(),
590            1,
591            "Unexpected number of open files: {}",
592            fsm.file_cnt()
593        );
594
595        assert_eq!(
596            fsm.get(handle).map(|f| f.permissions()),
597            Some(LocalFilePermissions {
598                read: false,
599                write: true
600            })
601        );
602
603        // Open read-only
604        let handle_2 = fsm
605            .open_file(root.as_ref().join("test-file"), false, false, true)
606            .await
607            .expect("Failed to open file");
608
609        assert_eq!(
610            fsm.file_cnt(),
611            1,
612            "Unexpected number of open files: {}",
613            fsm.file_cnt()
614        );
615
616        assert_eq!(handle, handle_2);
617
618        assert_eq!(
619            fsm.get(handle_2).map(|f| f.permissions()),
620            Some(LocalFilePermissions {
621                read: true,
622                write: true
623            })
624        );
625    }
626
627    #[tokio::test]
628    async fn open_file_should_return_a_newly_opened_file_if_none_already_open()
629    {
630        let root = tempfile::tempdir().unwrap();
631        let mut fsm = FileSystemManager::new();
632
633        let handle = fsm
634            .open_file(root.as_ref().join("test-file-1"), true, true, true)
635            .await
636            .expect("Failed to create file 1");
637
638        let handle_2 = fsm
639            .open_file(root.as_ref().join("test-file-2"), true, true, true)
640            .await
641            .expect("Failed to create file 2");
642
643        assert_eq!(
644            fsm.file_cnt(),
645            2,
646            "Unexpected number of open files: {}",
647            fsm.file_cnt()
648        );
649
650        assert_ne!(handle, handle_2, "Two open files have same handle");
651    }
652
653    #[tokio::test]
654    async fn close_file_should_yield_error_if_no_file_open_with_id() {
655        let root = tempfile::tempdir().unwrap();
656        let mut fsm = FileSystemManager::new();
657
658        let handle = fsm
659            .open_file(root.as_ref().join("test-file"), true, true, true)
660            .await
661            .expect("Failed to create file");
662
663        match fsm.close_file(LocalFileHandle {
664            id: handle.id + 1,
665            sig: handle.sig,
666        }) {
667            Err(x) if x.kind() == io::ErrorKind::NotFound => (),
668            x => panic!("Unexpected result: {:?}", x),
669        }
670    }
671
672    #[tokio::test]
673    async fn close_file_should_yield_error_if_file_has_different_signature() {
674        let root = tempfile::tempdir().unwrap();
675        let mut fsm = FileSystemManager::new();
676
677        let handle = fsm
678            .open_file(root.as_ref().join("test-file"), true, true, true)
679            .await
680            .expect("Failed to create file");
681
682        match fsm.close_file(LocalFileHandle {
683            id: handle.id,
684            sig: handle.sig + 1,
685        }) {
686            Err(x) if x.kind() == io::ErrorKind::InvalidInput => (),
687            x => panic!("Unexpected result: {:?}", x),
688        }
689    }
690
691    #[tokio::test]
692    async fn close_fould_should_remove_file_from_manager_if_successful() {
693        let root = tempfile::tempdir().unwrap();
694        let mut fsm = FileSystemManager::new();
695
696        let handle = fsm
697            .open_file(root.as_ref().join("test-file"), true, true, true)
698            .await
699            .expect("Failed to create file");
700
701        match fsm.close_file(handle) {
702            Ok(_) => (),
703            x => panic!("Unexpected result: {:?}", x),
704        }
705    }
706
707    #[tokio::test]
708    async fn rename_file_should_yield_error_if_origin_path_does_not_exist() {
709        let root = tempfile::tempdir().unwrap();
710        let mut fsm = FileSystemManager::new();
711
712        let origin = root.as_ref().join("origin");
713        let destination = root.as_ref().join("destination");
714
715        match fsm.rename_file(origin, destination).await {
716            Err(x) => assert_eq!(x.kind(), io::ErrorKind::NotFound),
717            x => panic!("Unexpected result: {:?}", x),
718        }
719    }
720
721    #[tokio::test]
722    async fn rename_file_should_yield_error_if_origin_path_is_not_a_file() {
723        let root = tempfile::tempdir().unwrap();
724        let mut fsm = FileSystemManager::new();
725
726        // Make origin a directory instead of file
727        let origin_dir = tempfile::tempdir_in(root.as_ref()).unwrap();
728
729        let destination = root.as_ref().join("destination");
730
731        match fsm.rename_file(origin_dir.as_ref(), destination).await {
732            Err(x) => assert_eq!(x.kind(), io::ErrorKind::Other),
733            x => panic!("Unexpected result: {:?}", x),
734        }
735    }
736
737    #[tokio::test]
738    async fn rename_file_should_yield_error_if_file_is_open() {
739        let root = tempfile::tempdir().unwrap();
740        let mut fsm = FileSystemManager::new();
741
742        let origin = root.as_ref().join("file");
743        let _file = fsm
744            .open_file(origin.as_path(), true, true, true)
745            .await
746            .unwrap();
747
748        let destination = root.as_ref().join("destination");
749
750        match fsm
751            .rename_file(origin.as_path(), destination.as_path())
752            .await
753        {
754            Err(x) => assert_eq!(x.kind(), io::ErrorKind::InvalidData),
755            x => panic!("Unexpected result: {:?}", x),
756        }
757    }
758
759    #[tokio::test]
760    async fn rename_file_should_return_success_if_renamed_file() {
761        let root = tempfile::tempdir().unwrap();
762        let mut fsm = FileSystemManager::new();
763
764        let origin = tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
765
766        let destination = root.as_ref().join("destination");
767
768        match fsm.rename_file(origin, destination).await {
769            Ok(_) => (),
770            x => panic!("Unexpected result: {:?}", x),
771        }
772    }
773
774    #[tokio::test]
775    async fn remove_file_should_yield_error_if_file_open() {
776        let root = tempfile::tempdir().unwrap();
777        let mut fsm = FileSystemManager::new();
778
779        let path = root.as_ref().join("test-file");
780
781        fsm.open_file(path.as_path(), true, true, true)
782            .await
783            .expect("Failed to open file with manager");
784
785        // Even though we want to remove everything, still cannot do it because
786        // a local file is open
787        match fsm.remove_file(path.as_path()).await {
788            Err(x) if x.kind() == io::ErrorKind::InvalidData => (),
789            x => panic!("Unexpected result: {:?}", x),
790        }
791    }
792
793    #[tokio::test]
794    async fn remove_file_should_return_success_if_removed_file() {
795        let root = tempfile::tempdir().unwrap();
796        let mut fsm = FileSystemManager::new();
797
798        let file = tempfile::NamedTempFile::new_in(root.as_ref()).unwrap();
799
800        match fsm.remove_file(file.as_ref()).await {
801            Ok(_) => (),
802            x => panic!("Unexpected result: {:?}", x),
803        }
804    }
805}