|
1 | 1 | use std::{
|
2 | 2 | cell::{Cell, RefCell},
|
3 |
| - fs, |
4 |
| - sync::Once, |
| 3 | + env, fs, |
| 4 | + sync::{Once, OnceLock}, |
5 | 5 | time::Duration,
|
6 | 6 | };
|
7 | 7 |
|
@@ -127,7 +127,53 @@ impl Project<'_> {
|
127 | 127 | }
|
128 | 128 |
|
129 | 129 | pub(crate) fn server(self) -> Server {
|
130 |
| - static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(()); |
| 130 | + Project::server_with_lock(self, false) |
| 131 | + } |
| 132 | + |
| 133 | + /// `prelock` : Forcefully acquire a lock that will maintain the path to the config dir throughout the whole test. |
| 134 | + /// |
| 135 | + /// When testing we set the user config dir by setting an envvar `__TEST_RA_USER_CONFIG_DIR`. |
| 136 | + /// This value must be maintained until the end of a test case. When tests run in parallel |
| 137 | + /// this value may change thus making the tests flaky. As such, we use a `MutexGuard` that locks |
| 138 | + /// the process until `Server` is dropped. To optimize parallelization we use a lock only when it is |
| 139 | + /// needed, that is when a test uses config directory to do stuff. Our naive approach is to use a lock |
| 140 | + /// if there is a path to config dir in the test fixture. However, in certain cases we create a |
| 141 | + /// file in the config dir after server is run, something where our naive approach comes short. |
| 142 | + /// Using a `prelock` allows us to force a lock when we know we need it. |
| 143 | + pub(crate) fn server_with_lock(self, config_lock: bool) -> Server { |
| 144 | + static CONFIG_DIR_LOCK: OnceLock<(Utf8PathBuf, Mutex<()>)> = OnceLock::new(); |
| 145 | + |
| 146 | + let config_dir_guard = if config_lock { |
| 147 | + Some({ |
| 148 | + let (path, mutex) = CONFIG_DIR_LOCK.get_or_init(|| { |
| 149 | + let value = TestDir::new().keep().path().to_owned(); |
| 150 | + env::set_var("__TEST_RA_USER_CONFIG_DIR", &value); |
| 151 | + (value, Mutex::new(())) |
| 152 | + }); |
| 153 | + #[allow(dyn_drop)] |
| 154 | + (mutex.lock(), { |
| 155 | + Box::new({ |
| 156 | + struct Dropper(Utf8PathBuf); |
| 157 | + impl Drop for Dropper { |
| 158 | + fn drop(&mut self) { |
| 159 | + for entry in fs::read_dir(&self.0).unwrap() { |
| 160 | + let path = entry.unwrap().path(); |
| 161 | + if path.is_file() { |
| 162 | + fs::remove_file(path).unwrap(); |
| 163 | + } else if path.is_dir() { |
| 164 | + fs::remove_dir_all(path).unwrap(); |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | + Dropper(path.clone()) |
| 170 | + }) as Box<dyn Drop> |
| 171 | + }) |
| 172 | + }) |
| 173 | + } else { |
| 174 | + None |
| 175 | + }; |
| 176 | + |
131 | 177 | let tmp_dir = self.tmp_dir.unwrap_or_else(|| {
|
132 | 178 | if self.root_dir_contains_symlink {
|
133 | 179 | TestDir::new_symlink()
|
@@ -160,13 +206,9 @@ impl Project<'_> {
|
160 | 206 | assert!(mini_core.is_none());
|
161 | 207 | assert!(toolchain.is_none());
|
162 | 208 |
|
163 |
| - let mut config_dir_guard = None; |
164 | 209 | for entry in fixture {
|
165 | 210 | if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") {
|
166 |
| - if config_dir_guard.is_none() { |
167 |
| - config_dir_guard = Some(CONFIG_DIR_LOCK.lock()); |
168 |
| - } |
169 |
| - let path = Config::user_config_path().unwrap().join(&pth['/'.len_utf8()..]); |
| 211 | + let path = Config::user_config_dir_path().unwrap().join(&pth['/'.len_utf8()..]); |
170 | 212 | fs::create_dir_all(path.parent().unwrap()).unwrap();
|
171 | 213 | fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
172 | 214 | } else {
|
@@ -269,12 +311,14 @@ pub(crate) struct Server {
|
269 | 311 | client: Connection,
|
270 | 312 | /// XXX: remove the tempdir last
|
271 | 313 | dir: TestDir,
|
272 |
| - _config_dir_guard: Option<MutexGuard<'static, ()>>, |
| 314 | + #[allow(dyn_drop)] |
| 315 | + _config_dir_guard: Option<(MutexGuard<'static, ()>, Box<dyn Drop>)>, |
273 | 316 | }
|
274 | 317 |
|
275 | 318 | impl Server {
|
| 319 | + #[allow(dyn_drop)] |
276 | 320 | fn new(
|
277 |
| - config_dir_guard: Option<MutexGuard<'static, ()>>, |
| 321 | + config_dir_guard: Option<(MutexGuard<'static, ()>, Box<dyn Drop>)>, |
278 | 322 | dir: TestDir,
|
279 | 323 | config: Config,
|
280 | 324 | ) -> Server {
|
|
0 commit comments