LCOV - code coverage report
Current view: top level - libs/utils/src - fs_ext.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 96.8 % 93 90
Test Date: 2023-09-06 10:18:01 Functions: 76.7 % 30 23

            Line data    Source code
       1              : /// Extensions to `std::fs` types.
       2              : use std::{fs, io, path::Path};
       3              : 
       4              : use anyhow::Context;
       5              : 
       6              : pub trait PathExt {
       7              :     /// Returns an error if `self` is not a directory.
       8              :     fn is_empty_dir(&self) -> io::Result<bool>;
       9              : }
      10              : 
      11              : impl<P> PathExt for P
      12              : where
      13              :     P: AsRef<Path>,
      14              : {
      15          962 :     fn is_empty_dir(&self) -> io::Result<bool> {
      16          962 :         Ok(fs::read_dir(self)?.next().is_none())
      17          962 :     }
      18              : }
      19              : 
      20          125 : pub async fn is_directory_empty(path: impl AsRef<Path>) -> anyhow::Result<bool> {
      21          125 :     let mut dir = tokio::fs::read_dir(&path)
      22          125 :         .await
      23          125 :         .context(format!("read_dir({})", path.as_ref().display()))?;
      24          123 :     Ok(dir.next_entry().await?.is_none())
      25          125 : }
      26              : 
      27            3 : pub async fn list_dir(path: impl AsRef<Path>) -> anyhow::Result<Vec<String>> {
      28            3 :     let mut dir = tokio::fs::read_dir(&path)
      29            3 :         .await
      30            3 :         .context(format!("read_dir({})", path.as_ref().display()))?;
      31              : 
      32            3 :     let mut content = vec![];
      33            6 :     while let Some(next) = dir.next_entry().await? {
      34            3 :         let file_name = next.file_name();
      35            3 :         content.push(file_name.to_string_lossy().to_string());
      36            3 :     }
      37              : 
      38            3 :     Ok(content)
      39            3 : }
      40              : 
      41           72 : pub fn ignore_not_found(e: io::Error) -> io::Result<()> {
      42           72 :     if e.kind() == io::ErrorKind::NotFound {
      43           72 :         Ok(())
      44              :     } else {
      45            0 :         Err(e)
      46              :     }
      47           72 : }
      48              : 
      49         1056 : pub fn ignore_absent_files<F>(fs_operation: F) -> io::Result<()>
      50         1056 : where
      51         1056 :     F: Fn() -> io::Result<()>,
      52         1056 : {
      53         1056 :     fs_operation().or_else(ignore_not_found)
      54         1056 : }
      55              : 
      56              : #[cfg(test)]
      57              : mod test {
      58              :     use std::path::PathBuf;
      59              : 
      60              :     use crate::fs_ext::{is_directory_empty, list_dir};
      61              : 
      62              :     use super::ignore_absent_files;
      63              : 
      64            1 :     #[test]
      65            1 :     fn is_empty_dir() {
      66            1 :         use super::PathExt;
      67            1 : 
      68            1 :         let dir = tempfile::tempdir().unwrap();
      69            1 :         let dir_path = dir.path();
      70            1 : 
      71            1 :         // test positive case
      72            1 :         assert!(
      73            1 :             dir_path.is_empty_dir().expect("test failure"),
      74            0 :             "new tempdir should be empty"
      75              :         );
      76              : 
      77              :         // invoke on a file to ensure it returns an error
      78            1 :         let file_path: PathBuf = dir_path.join("testfile");
      79            1 :         let f = std::fs::File::create(&file_path).unwrap();
      80            1 :         drop(f);
      81            1 :         assert!(file_path.is_empty_dir().is_err());
      82              : 
      83              :         // do it again on a path, we know to be nonexistent
      84            1 :         std::fs::remove_file(&file_path).unwrap();
      85            1 :         assert!(file_path.is_empty_dir().is_err());
      86            1 :     }
      87              : 
      88            1 :     #[tokio::test]
      89            1 :     async fn is_empty_dir_async() {
      90            1 :         let dir = tempfile::tempdir().unwrap();
      91            1 :         let dir_path = dir.path();
      92              : 
      93              :         // test positive case
      94            1 :         assert!(
      95            2 :             is_directory_empty(dir_path).await.expect("test failure"),
      96            0 :             "new tempdir should be empty"
      97              :         );
      98              : 
      99              :         // invoke on a file to ensure it returns an error
     100            1 :         let file_path: PathBuf = dir_path.join("testfile");
     101            1 :         let f = std::fs::File::create(&file_path).unwrap();
     102            1 :         drop(f);
     103            1 :         assert!(is_directory_empty(&file_path).await.is_err());
     104              : 
     105              :         // do it again on a path, we know to be nonexistent
     106            1 :         std::fs::remove_file(&file_path).unwrap();
     107            1 :         assert!(is_directory_empty(file_path).await.is_err());
     108              :     }
     109              : 
     110            1 :     #[test]
     111            1 :     fn ignore_absent_files_works() {
     112            1 :         let dir = tempfile::tempdir().unwrap();
     113            1 :         let dir_path = dir.path();
     114            1 : 
     115            1 :         let file_path: PathBuf = dir_path.join("testfile");
     116            1 : 
     117            1 :         ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
     118            1 : 
     119            1 :         let f = std::fs::File::create(&file_path).unwrap();
     120            1 :         drop(f);
     121            1 : 
     122            1 :         ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
     123            1 : 
     124            1 :         assert!(!file_path.exists());
     125            1 :     }
     126              : 
     127            1 :     #[tokio::test]
     128            1 :     async fn list_dir_works() {
     129            1 :         let dir = tempfile::tempdir().unwrap();
     130            1 :         let dir_path = dir.path();
     131              : 
     132            2 :         assert!(list_dir(dir_path).await.unwrap().is_empty());
     133              : 
     134            1 :         let file_path: PathBuf = dir_path.join("testfile");
     135            1 :         let _ = std::fs::File::create(&file_path).unwrap();
     136              : 
     137            2 :         assert_eq!(&list_dir(dir_path).await.unwrap(), &["testfile"]);
     138              : 
     139            1 :         let another_dir_path: PathBuf = dir_path.join("testdir");
     140            1 :         std::fs::create_dir(another_dir_path).unwrap();
     141            1 : 
     142            1 :         let expected = &["testdir", "testfile"];
     143            2 :         let mut actual = list_dir(dir_path).await.unwrap();
     144            1 :         actual.sort();
     145            1 :         assert_eq!(actual, expected);
     146              :     }
     147              : }
        

Generated by: LCOV version 2.1-beta