e_utils/fs/
_fs.rs

1use async_trait::async_trait;
2
3use crate::{regex::regex2, AnyRes as _, AnyResult, Error};
4use std::{
5  borrow::Cow,
6  fs,
7  io::{self, Write as _},
8  path::{Path, PathBuf},
9};
10/// 同步树形目录,返回完整路径的字符串数组
11pub fn tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
12  let path = PathBuf::from(src.as_ref());
13
14  // 处理文件情况
15  if path.is_file() {
16    return Ok(vec![path]);
17  }
18
19  // 获取目标目录和匹配模式
20  let (target_dir, pattern) = if path.is_dir() {
21    (path, None)
22  } else if let Some(parent) = path.parent() {
23    if !parent.is_dir() {
24      return Err(format!("{} 不是一个有效的路径", parent.display()).into());
25    }
26    (parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
27  } else {
28    return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
29  };
30
31  // 读取目录内容
32  let files = if is_next {
33    tree_folder(&target_dir)?
34  } else {
35    let mut list = Vec::new();
36    for entry in std::fs::read_dir(&target_dir)? {
37      list.push(entry?.path());
38    }
39    list
40  };
41
42  // 根据pattern过滤结果
43  Ok(match pattern {
44    Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
45    None => files,
46  })
47}
48
49/// 树形目录,返回完整路径的字符串数组
50pub fn tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
51where
52  P: AsRef<Path>,
53{
54  let path = dir_path.as_ref();
55  if !path.exists() {
56    return Err(format!("Path does not exist: {}", path.display()).into());
57  }
58  let mut result = Vec::new();
59  if path.is_dir() {
60    result.extend(fs::read_dir(path)?.filter_map(|entry| entry.ok()).flat_map(|entry| {
61      let path = entry.path();
62      if path.is_dir() {
63        tree_folder(path).unwrap_or_default()
64      } else {
65        vec![path]
66      }
67    }));
68  } else {
69    result.push(path.to_path_buf());
70  }
71
72  Ok(result)
73}
74
75/// 解析字符串中的,
76pub fn tree_from_str(src: impl AsRef<str>) -> Vec<String> {
77  src.as_ref().split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
78}
79/// 正则匹配文件夹中的文件
80pub fn regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
81  Ok(
82    fs::read_dir(src.as_ref())
83      .any()?
84      .filter_map(Result::ok)
85      .filter_map(|entry| {
86        let path = entry.path();
87        path
88          .is_file()
89          .then(|| {
90            let path_str = path.to_string_lossy().to_string();
91            let (is_success, res) = regex2(&path_str, pat);
92            if is_success {
93              res.map(|v| v.to_string())
94            } else {
95              Some(path_str)
96            }
97          })
98          .flatten()
99      })
100      .collect(),
101  )
102}
103/// 重命名
104pub fn rename_file<P, P2>(src: P, dst: P2) -> AnyResult<()>
105where
106  P: AsRef<Path>,
107  P2: AsRef<Path>,
108{
109  let src = src.as_ref();
110  let dst = dst.as_ref();
111  if src.exists() && src.is_file() {
112    if dst.exists() && !dst.is_file() {
113      Err(format!("目标已存在,并非文件格式 {}", dst.display()).into())
114    } else {
115      fs::rename(src, dst)?;
116      if dst.exists() && dst.is_file() {
117        Ok(())
118      } else {
119        Err(format!("源{} 目标移动失败 {}", src.display(), dst.display()).into())
120      }
121    }
122  } else {
123    Err(format!("原始缓存文件不存在 {}", src.display()).into())
124  }
125}
126/// 自动化转换路径
127pub fn convert_path(path_str: &str) -> String {
128  if cfg!(target_os = "windows") {
129    path_str.replace('/', "\\")
130  } else {
131    String::from(path_str)
132  }
133}
134
135/// 复制目录
136pub fn auto_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> AnyResult<()> {
137  let from_path = from.as_ref();
138  let to_path = to.as_ref();
139
140  if from_path.is_file() {
141    // 如果是文件,直接复制
142    fs::copy(from_path, to_path)?;
143  } else if from_path.is_dir() {
144    // to_path.auto_create_dir()?;
145    // 遍历目录中的所有条目
146    for entry in fs::read_dir(from_path)? {
147      let entry = entry?;
148      let from_entry_path = entry.path();
149      let to_entry_path = to_path.join(from_entry_path.file_name().ok_or(Error::Str("无法解析文件名".into()))?);
150      // 递归复制每个条目
151      auto_copy(from_entry_path, to_entry_path)?;
152    }
153  } else {
154    // 如果既不是文件也不是目录,返回错误
155    return Err("Path is neither a file nor a directory".into());
156  }
157  Ok(())
158}
159
160/// 写入
161fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
162  if !is_append && path.exists() {
163    // path.auto_remove_file()?;
164  }
165  let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path)?;
166  f.write_all(&bytes)?;
167  if is_sync {
168    f.sync_data()?;
169  }
170  Ok(())
171}
172/// 写入GBK格式
173#[cfg(feature = "encode")]
174pub fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
175  let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
176  if had_errors {
177    return Err("写入GBK失败".into());
178  } else {
179    write(path, bytes, is_sync, is_append)
180  }
181}
182/// 写入UTF-8格式
183#[cfg(feature = "encode")]
184pub fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
185  let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
186  if had_errors {
187    return Err("写入UTF-8失败".into());
188  } else {
189    write(path, bytes, is_sync, is_append)
190  }
191}
192/// 异步写入
193#[cfg(feature = "tokio")]
194pub mod a_sync {
195  use crate::{regex::regex2, AnyRes as _, AnyResult};
196  use std::{
197    borrow::Cow,
198    path::{Path, PathBuf},
199  };
200  use tokio::{fs, io::AsyncWriteExt as _};
201
202  /// 异步树形目录,返回完整路径的字符串数组
203  pub async fn a_tree_from(src: impl AsRef<str>, is_next: bool) -> crate::AnyResult<Vec<PathBuf>> {
204    let path = PathBuf::from(src.as_ref());
205    // 处理文件情况
206    if path.is_file() {
207      return Ok(vec![path]);
208    }
209    // 获取目标目录和匹配模式
210    let (target_dir, pattern) = if path.is_dir() {
211      (path, None)
212    } else if let Some(parent) = path.parent() {
213      if !parent.is_dir() {
214        return Err(format!("{} 不是一个有效的路径", parent.display()).into());
215      }
216      (parent.to_path_buf(), path.file_name().map(|f| f.to_string_lossy().to_string()))
217    } else {
218      return Err(format!("{} 不是一个有效的路径", src.as_ref()).into());
219    };
220
221    // 读取目录内容
222    let files = if is_next {
223      a_tree_folder(&target_dir).await?
224    } else {
225      let mut list = Vec::new();
226      let mut dir = tokio::fs::read_dir(&target_dir).await?;
227      while let Some(entry) = dir.next_entry().await? {
228        list.push(entry.path());
229      }
230      list
231    };
232
233    // 根据pattern过滤结果
234    Ok(match pattern {
235      Some(pat) => files.into_iter().filter(|v| regex2(&v.display().to_string(), &pat).0).collect(),
236      None => files,
237    })
238  }
239
240  /// 异步树形目录,返回完整路径的字符串数组
241  pub async fn a_tree_folder<P>(dir_path: P) -> AnyResult<Vec<PathBuf>>
242  where
243    P: AsRef<Path>,
244  {
245    Box::pin(async move {
246      let path = dir_path.as_ref();
247      if !path.exists() {
248        return Err(format!("Path does not exist: {}", path.display()).into());
249      }
250      let mut result = Vec::new();
251      if path.is_dir() {
252        let mut read_dir = tokio::fs::read_dir(path).await?;
253        while let Some(entry) = read_dir.next_entry().await? {
254          let path = entry.path();
255          if path.is_dir() {
256            let sub_files = a_tree_folder(&path).await?;
257            result.extend(sub_files);
258          } else {
259            result.push(path);
260          }
261        }
262      } else {
263        result.push(path.to_path_buf());
264      }
265      Ok(result)
266    })
267    .await
268  }
269
270  /// 异步正则匹配文件夹中的文件
271  pub async fn a_regex_read_dir(src: impl AsRef<Path>, pat: &str) -> crate::Result<Vec<String>> {
272    let mut entries = tokio::fs::read_dir(src.as_ref()).await.any()?;
273    let mut results = Vec::new();
274    while let Some(entry) = entries.next_entry().await.any()? {
275      let path = entry.path();
276      if path.is_file() {
277        let path_str = path.to_string_lossy().to_string();
278        let (is_success, res) = regex2(&path_str, pat);
279        if is_success {
280          if let Some(v) = res {
281            results.push(v.to_string());
282          }
283        } else {
284          results.push(path_str);
285        }
286      }
287    }
288
289    Ok(results)
290  }
291
292  /// 异步写入
293  async fn write<'a>(path: &PathBuf, bytes: Cow<'a, [u8]>, is_sync: bool, is_append: bool) -> AnyResult<()> {
294    if !is_append && path.exists() {
295      fs::remove_file(path).await?
296    }
297    let mut f = fs::OpenOptions::new().read(true).write(true).create(true).append(is_append).open(path).await?;
298    f.write_all(&bytes).await?;
299    if is_sync {
300      f.sync_data().await?;
301    }
302    Ok(())
303  }
304  /// 异步写入GBK格式
305  #[cfg(feature = "encode")]
306  pub async fn write_gbk<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
307    let (bytes, _encode, had_errors) = encoding_rs::GBK.encode(content);
308    if had_errors {
309      return Err("异步写入GBK失败".into());
310    } else {
311      write(path, bytes, is_sync, is_append).await
312    }
313  }
314  /// 异步写入UTF-8格式
315  #[cfg(feature = "encode")]
316  pub async fn write_utf8<'a>(path: &PathBuf, content: &'a str, is_sync: bool, is_append: bool) -> AnyResult<()> {
317    let (bytes, _encode, had_errors) = encoding_rs::UTF_8.encode(content);
318    if had_errors {
319      return Err("异步写入UTF-8失败".into());
320    } else {
321      write(path, bytes, is_sync, is_append).await
322    }
323  }
324}
325
326/// 自动检查和操作文件系统路径
327#[async_trait]
328pub trait AutoPath {
329  /// 自动创建目录
330  ///
331  /// # Example
332  ///
333  /// ```
334  /// use std::path::Path;
335  /// use e_utils::fs::AutoPath;
336  ///
337  /// let path = Path::new("test_dir");
338  /// path.auto_create_dir().unwrap();
339  /// assert!(path.exists() && path.is_dir());
340  ///
341  /// // 清理
342  /// std::fs::remove_dir(path).unwrap();
343  /// ```
344  fn auto_create_dir(&self) -> AnyResult<()>;
345
346  /// 自动移除目录
347  ///
348  /// # Example
349  ///
350  /// ```
351  /// use std::path::Path;
352  /// use e_utils::fs::AutoPath;
353  ///
354  /// let path = Path::new("test_remove_dir");
355  /// std::fs::create_dir(path).unwrap();
356  /// assert!(path.exists());
357  ///
358  /// path.auto_remove_dir().unwrap();
359  /// assert!(!path.exists());
360  /// ```
361  fn auto_remove_dir(&self) -> AnyResult<()>;
362
363  /// 自动创建文件
364  ///
365  /// # Example
366  ///
367  /// ```
368  /// use std::path::Path;
369  /// use e_utils::fs::AutoPath;
370  ///
371  /// let path = Path::new("test_file.txt");
372  /// path.auto_create_file("Hello, World!").unwrap();
373  /// assert!(path.exists() && path.is_file());
374  ///
375  /// let content = std::fs::read_to_string(path).unwrap();
376  /// assert_eq!(content, "Hello, World!");
377  ///
378  /// // 清理
379  /// std::fs::remove_file(path).unwrap();
380  /// ```
381  fn auto_create_file<S: AsRef<str>>(&self, content: S) -> AnyResult<()>;
382
383  /// 自动移除文件
384  ///
385  /// # Example
386  ///
387  /// ```
388  /// use std::path::Path;
389  /// use e_utils::fs::AutoPath;
390  ///
391  /// let path = Path::new("test_remove_file.txt");
392  /// std::fs::write(path, "Test content").unwrap();
393  /// assert!(path.exists());
394  ///
395  /// path.auto_remove_file().unwrap();
396  /// assert!(!path.exists());
397  /// ```
398  fn auto_remove_file(&self) -> AnyResult<()>;
399
400  /// Asynchronously creates a directory.
401  ///
402  /// # Example
403  ///
404  /// ```
405  /// use std::path::Path;
406  /// use e_utils::fs::AutoPath;
407  ///
408  /// #[tokio::main]
409  /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
410  ///     let path = Path::new("test_async_dir");
411  ///     path.a_auto_create_dir().await?;
412  ///     assert!(path.exists() && path.is_dir());
413  ///
414  ///     // Clean up
415  ///     tokio::fs::remove_dir(path).await?;
416  ///     Ok(())
417  /// }
418  /// ```
419  #[cfg(feature = "tokio")]
420  async fn a_auto_create_dir(&self) -> AnyResult<()>;
421
422  /// Asynchronously removes a directory.
423  ///
424  /// # Example
425  ///
426  /// ```
427  /// use std::path::Path;
428  /// use e_utils::fs::AutoPath;
429  ///
430  /// #[tokio::main]
431  /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
432  ///     let path = Path::new("test_async_remove_dir");
433  ///     tokio::fs::create_dir(path).await?;
434  ///     assert!(path.exists());
435  ///
436  ///     path.a_auto_remove_dir().await?;
437  ///     assert!(!path.exists());
438  ///     Ok(())
439  /// }
440  /// ```
441  #[cfg(feature = "tokio")]
442  async fn a_auto_remove_dir(&self) -> AnyResult<()>;
443
444  /// Asynchronously creates a file with the given content.
445  ///
446  /// # Example
447  ///
448  /// ```
449  /// use std::path::Path;
450  /// use e_utils::fs::AutoPath;
451  ///
452  /// #[tokio::main]
453  /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
454  ///     let path = Path::new("test_async_file.txt");
455  ///     path.a_auto_create_file("Hello, World!").await?;
456  ///     assert!(path.exists() && path.is_file());
457  ///
458  ///     let content = tokio::fs::read_to_string(path).await?;
459  ///     assert_eq!(content, "Hello, World!");
460  ///
461  ///     // Clean up
462  ///     tokio::fs::remove_file(path).await?;
463  ///     Ok(())
464  /// }
465  /// ```
466  #[cfg(feature = "tokio")]
467  async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()>;
468
469  /// Asynchronously removes a file.
470  ///
471  /// # Example
472  ///
473  /// ```
474  /// use std::path::Path;
475  /// use e_utils::fs::AutoPath;
476  ///
477  /// #[tokio::main]
478  /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
479  ///     let path = Path::new("test_async_remove_file.txt");
480  ///     tokio::fs::write(path, "Test content").await?;
481  ///     assert!(path.exists());
482  ///
483  ///     path.a_auto_remove_file().await?;
484  ///     assert!(!path.exists());
485  ///     Ok(())
486  /// }
487  /// ```
488  #[cfg(feature = "tokio")]
489  async fn a_auto_remove_file(&self) -> AnyResult<()>;
490}
491
492#[async_trait]
493impl<T: AsRef<str> + Send + Sync> AutoPath for T {
494  fn auto_create_dir(&self) -> AnyResult<()> {
495    Path::new(self.as_ref()).auto_create_dir()
496  }
497
498  fn auto_remove_dir(&self) -> AnyResult<()> {
499    Path::new(self.as_ref()).auto_remove_dir()
500  }
501
502  fn auto_create_file<S>(&self, content: S) -> AnyResult<()>
503  where
504    S: AsRef<str>,
505  {
506    Path::new(self.as_ref()).auto_create_file(content)
507  }
508
509  fn auto_remove_file(&self) -> AnyResult<()> {
510    Path::new(self.as_ref()).auto_remove_file()
511  }
512  #[cfg(feature = "tokio")]
513  async fn a_auto_create_dir(&self) -> AnyResult<()> {
514    Path::new(self.as_ref()).a_auto_create_dir().await
515  }
516  #[cfg(feature = "tokio")]
517  async fn a_auto_remove_dir(&self) -> AnyResult<()> {
518    Path::new(self.as_ref()).a_auto_remove_dir().await
519  }
520  #[cfg(feature = "tokio")]
521  async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
522    Path::new(self.as_ref()).a_auto_create_file(content).await
523  }
524  #[cfg(feature = "tokio")]
525  async fn a_auto_remove_file(&self) -> AnyResult<()> {
526    Path::new(self.as_ref()).a_auto_remove_file().await
527  }
528}
529#[async_trait]
530impl AutoPath for Path {
531  fn auto_create_dir(&self) -> AnyResult<()> {
532    if !self.exists() {
533      fs::create_dir_all(self)?;
534      if !self.exists() {
535        return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
536      }
537    }
538    return Ok(());
539  }
540  fn auto_remove_dir(&self) -> AnyResult<()> {
541    if self.is_dir() {
542      fs::remove_dir_all(self)?;
543      if self.exists() {
544        return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
545      }
546    } else if self.exists() {
547      return Err(format!("{} is not a directory", self.display()).into());
548    }
549    Ok(())
550  }
551
552  fn auto_create_file<S: AsRef<str>>(&self, data: S) -> AnyResult<()> {
553    if !self.exists() {
554      fs::write(self, data.as_ref())?;
555    } else if !self.is_file() {
556      return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
557    }
558    Ok(())
559  }
560
561  fn auto_remove_file(&self) -> AnyResult<()> {
562    if self.exists() {
563      if self.is_file() {
564        fs::remove_file(self)?;
565      } else {
566        return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
567      }
568    }
569    Ok(())
570  }
571  #[cfg(feature = "tokio")]
572  async fn a_auto_create_dir(&self) -> AnyResult<()> {
573    if !self.exists() {
574      tokio::fs::create_dir_all(self).await?;
575      if !self.exists() {
576        return Err(format!("{} -> {}", self.display(), io::ErrorKind::NotFound).into());
577      }
578    }
579    return Ok(());
580  }
581
582  #[cfg(feature = "tokio")]
583  async fn a_auto_remove_dir(&self) -> AnyResult<()> {
584    if self.is_dir() {
585      tokio::fs::remove_dir_all(self).await?;
586      if self.exists() {
587        return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
588      }
589    } else if self.exists() {
590      return Err(format!("{} is not a directory", self.display()).into());
591    }
592    Ok(())
593  }
594
595  #[cfg(feature = "tokio")]
596  async fn a_auto_create_file(&self, content: impl AsRef<[u8]> + Send) -> AnyResult<()> {
597    if !self.exists() {
598      tokio::fs::write(self, content.as_ref()).await?;
599    } else if !self.is_file() {
600      return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
601    }
602    Ok(())
603  }
604
605  #[cfg(feature = "tokio")]
606  async fn a_auto_remove_file(&self) -> AnyResult<()> {
607    if self.exists() {
608      if self.is_file() {
609        tokio::fs::remove_file(self).await?;
610      } else {
611        return Err(format!("{} -> {}", self.display(), io::ErrorKind::AlreadyExists).into());
612      }
613    }
614    Ok(())
615  }
616}
617/// 创建临时文件
618pub fn temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
619  let path = std::env::temp_dir().join(folder.as_ref());
620  path.auto_create_dir()?;
621  let target = path.join(fname.as_ref());
622  target.auto_create_file(content.as_ref())?;
623  Ok(target)
624}
625/// 创建临时文件
626#[cfg(feature = "tokio")]
627pub async fn a_temp_file(folder: impl AsRef<str>, fname: impl AsRef<str>, content: impl AsRef<str>) -> crate::AnyResult<PathBuf> {
628  let path = std::env::temp_dir().join(folder.as_ref());
629  path.a_auto_create_dir().await?;
630  let target = path.join(fname.as_ref());
631  target.a_auto_create_file(content.as_ref()).await?;
632  Ok(target)
633}
634
635#[cfg(test)]
636mod auto_path_tests {
637  use super::*;
638  use std::fs;
639
640  #[test]
641  fn test_auto_create_dir() {
642    let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
643    assert!(path.exists() && path.parent().unwrap().is_dir());
644  }
645
646  #[test]
647  fn test_auto_remove_dir() {
648    let path = temp_file("test", "test_remove_dir", "Hello, World!").unwrap();
649    let parent = path.parent().unwrap();
650    parent.auto_remove_dir().unwrap();
651    assert!(!parent.exists());
652  }
653
654  #[test]
655  fn test_auto_create_file() {
656    let path = temp_file("test", "test_file.txt", "Hello, World!").unwrap();
657    assert!(path.exists() && path.is_file());
658    assert_eq!(fs::read_to_string(&path).unwrap(), "Hello, World!");
659  }
660
661  #[test]
662  fn test_auto_remove_file() {
663    let path = temp_file("test", "test_remove_file.txt", "Test content").unwrap();
664    let parent = path.parent().unwrap();
665    parent.auto_remove_dir().unwrap();
666    assert!(!parent.exists());
667  }
668
669  #[cfg(feature = "tokio")]
670  #[tokio::test]
671  async fn test_a_auto_create_dir() {
672    let path = temp_file("test", "test_async_dir", "Test content").unwrap();
673    path.a_auto_create_dir().await.unwrap();
674    assert!(path.exists() && path.is_dir());
675  }
676
677  #[cfg(feature = "tokio")]
678  #[tokio::test]
679  async fn test_a_auto_remove_dir() {
680    let path = temp_file("test", "test_async_remove_dir", "Test content").unwrap();
681    let parent = path.parent().unwrap();
682    parent.a_auto_remove_dir().await.unwrap();
683    assert!(!parent.exists());
684  }
685
686  #[cfg(feature = "tokio")]
687  #[tokio::test]
688  async fn test_a_auto_create_file() {
689    let path = temp_file("test", "test_async_file.txt", "Hello, Async World!").unwrap();
690    assert!(path.exists() && path.is_file());
691    assert_eq!(tokio::fs::read_to_string(&path).await.unwrap(), "Hello, Async World!");
692  }
693
694  #[cfg(feature = "tokio")]
695  #[tokio::test]
696  async fn test_a_auto_remove_file() {
697    let path = temp_file("test", "test_async_remove_file.txt", "Test async content").unwrap();
698    let parent = path.parent().unwrap();
699    parent.a_auto_remove_dir().await.unwrap();
700    assert!(!parent.exists());
701  }
702}