1 use std::{
2     fs, io,
3     path::{Path, PathBuf},
4     sync::atomic::{AtomicUsize, Ordering},
5 };
6 
7 pub(crate) struct TestDir {
8     path: PathBuf,
9     keep: bool,
10 }
11 
12 impl TestDir {
new() -> TestDir13     pub(crate) fn new() -> TestDir {
14         let temp_dir = std::env::temp_dir();
15         // On MacOS builders on GitHub actions, the temp dir is a symlink, and
16         // that causes problems down the line. Specifically:
17         // * Cargo may emit different PackageId depending on the working directory
18         // * rust-analyzer may fail to map LSP URIs to correct paths.
19         //
20         // Work-around this by canonicalizing. Note that we don't want to do this
21         // on *every* OS, as on windows `canonicalize` itself creates problems.
22         #[cfg(target_os = "macos")]
23         let temp_dir = temp_dir.canonicalize().unwrap();
24 
25         let base = temp_dir.join("testdir");
26         let pid = std::process::id();
27 
28         static CNT: AtomicUsize = AtomicUsize::new(0);
29         for _ in 0..100 {
30             let cnt = CNT.fetch_add(1, Ordering::Relaxed);
31             let path = base.join(format!("{}_{}", pid, cnt));
32             if path.is_dir() {
33                 continue;
34             }
35             fs::create_dir_all(&path).unwrap();
36             return TestDir { path, keep: false };
37         }
38         panic!("Failed to create a temporary directory")
39     }
40     #[allow(unused)]
keep(mut self) -> TestDir41     pub(crate) fn keep(mut self) -> TestDir {
42         self.keep = true;
43         self
44     }
path(&self) -> &Path45     pub(crate) fn path(&self) -> &Path {
46         &self.path
47     }
48 }
49 
50 impl Drop for TestDir {
drop(&mut self)51     fn drop(&mut self) {
52         if self.keep {
53             return;
54         }
55         remove_dir_all(&self.path).unwrap()
56     }
57 }
58 
59 #[cfg(not(windows))]
remove_dir_all(path: &Path) -> io::Result<()>60 fn remove_dir_all(path: &Path) -> io::Result<()> {
61     fs::remove_dir_all(path)
62 }
63 
64 #[cfg(windows)]
remove_dir_all(path: &Path) -> io::Result<()>65 fn remove_dir_all(path: &Path) -> io::Result<()> {
66     for _ in 0..99 {
67         if fs::remove_dir_all(path).is_ok() {
68             return Ok(());
69         }
70         std::thread::sleep(std::time::Duration::from_millis(10))
71     }
72     fs::remove_dir_all(path)
73 }
74