LCOV - code coverage report
Current view: top level - libs/utils/src - fs_ext.rs (source / functions) Coverage Total Hit
Test: f081ec316c96fa98335efd15ef501745aa4f015d.info Lines: 98.0 % 101 99
Test Date: 2024-06-25 15:11:17 Functions: 78.6 % 28 22

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

Generated by: LCOV version 2.1-beta