Skip to main content

xet_runtime/utils/
guards.rs

1#[cfg(not(target_family = "wasm"))]
2use std::env;
3#[cfg(not(target_family = "wasm"))]
4use std::ffi::OsStr;
5#[cfg(not(target_family = "wasm"))]
6use std::path::{Path, PathBuf};
7
8/// Guard that temporarily sets an environment variable and restores the previous value on drop.
9///
10/// This guard is useful for testing or temporarily modifying environment variables in a
11/// controlled manner. When the guard is dropped, it automatically restores the environment
12/// variable to its previous state (or removes it if it didn't exist before).
13///
14/// # Safety
15///
16/// This struct uses `unsafe` blocks to modify environment variables, which is necessary
17/// because Rust's standard library marks `env::set_var` and `env::remove_var` as unsafe
18/// due to their potential for data races in multi-threaded contexts. The guard ensures
19/// that modifications are properly cleaned up.
20///
21/// # Examples
22///
23/// ```no_run
24/// use xet_runtime::utils::EnvVarGuard;
25///
26/// // Temporarily set an environment variable
27/// let _guard = EnvVarGuard::set("MY_VAR", "test_value");
28/// // Environment variable is set here
29/// // When _guard is dropped, the previous value (or absence) is restored
30/// ```
31#[cfg(not(target_family = "wasm"))]
32pub struct EnvVarGuard {
33    key: &'static str,
34    prev: Option<String>,
35}
36
37#[cfg(not(target_family = "wasm"))]
38impl EnvVarGuard {
39    pub fn set(key: &'static str, value: impl AsRef<OsStr>) -> Self {
40        let prev = env::var(key).ok();
41        unsafe {
42            env::set_var(key, value);
43        }
44        Self { key, prev }
45    }
46}
47
48#[cfg(not(target_family = "wasm"))]
49impl Drop for EnvVarGuard {
50    fn drop(&mut self) {
51        if let Some(v) = &self.prev {
52            unsafe {
53                env::set_var(self.key, v);
54            }
55        } else {
56            unsafe {
57                env::remove_var(self.key);
58            }
59        }
60    }
61}
62
63/// Guard that temporarily changes the current working directory and restores the previous one on drop.
64///
65/// This guard is useful for testing or temporarily changing the working directory in a
66/// controlled manner. When the guard is dropped, it automatically restores the previous
67/// working directory.
68///
69/// # Error Handling
70///
71/// If restoring the previous directory fails during drop, the error is silently ignored
72/// (logged via `let _ = ...`). This is intentional to avoid panicking in destructors.
73///
74/// # Examples
75///
76/// ```no_run
77/// use std::path::Path;
78///
79/// use xet_runtime::utils::CwdGuard;
80///
81/// // Temporarily change to a different directory
82/// let new_dir = Path::new("/tmp");
83/// let _guard = CwdGuard::set(new_dir)?;
84/// // Current working directory is changed here
85/// // When _guard is dropped, the previous directory is restored
86/// # Ok::<(), std::io::Error>(())
87/// ```
88#[cfg(not(target_family = "wasm"))]
89pub struct CwdGuard {
90    prev: PathBuf,
91}
92
93#[cfg(not(target_family = "wasm"))]
94impl CwdGuard {
95    pub fn set(new_dir: &Path) -> std::io::Result<Self> {
96        let prev = env::current_dir()?;
97        env::set_current_dir(new_dir)?;
98        Ok(Self { prev })
99    }
100}
101
102#[cfg(not(target_family = "wasm"))]
103impl Drop for CwdGuard {
104    fn drop(&mut self) {
105        let _ = env::set_current_dir(&self.prev);
106    }
107}
108
109/// Guard that executes a closure when dropped.
110///
111/// Useful for ensuring cleanup logic runs even on early returns or panics.
112///
113/// # Examples
114///
115/// ```no_run
116/// use xet_runtime::utils::ClosureGuard;
117///
118/// let _guard = ClosureGuard::new(|| {
119///     println!("cleanup!");
120/// });
121/// // closure runs when _guard is dropped
122/// ```
123pub struct ClosureGuard<F: FnOnce()> {
124    closure: Option<F>,
125}
126
127impl<F: FnOnce()> ClosureGuard<F> {
128    pub fn new(f: F) -> Self {
129        Self { closure: Some(f) }
130    }
131}
132
133impl<F: FnOnce()> Drop for ClosureGuard<F> {
134    fn drop(&mut self) {
135        if let Some(f) = self.closure.take() {
136            f();
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use serial_test::serial;
144    use tempfile::tempdir;
145
146    use super::*;
147
148    #[test]
149    #[serial(default_config_env)]
150    fn env_var_guard_sets_and_restores() {
151        let key = "TEST_ENV_VAR_GUARD";
152
153        // Set an initial value
154        unsafe {
155            env::set_var(key, "initial");
156        }
157
158        {
159            let _guard = EnvVarGuard::set(key, "temporary");
160            assert_eq!(env::var(key).unwrap(), "temporary");
161        }
162
163        // Should be restored to initial value
164        assert_eq!(env::var(key).unwrap(), "initial");
165    }
166
167    #[test]
168    #[serial(default_config_env)]
169    fn env_var_guard_restores_none_when_var_did_not_exist() {
170        let key = "TEST_ENV_VAR_GUARD_NEW";
171
172        // Ensure it doesn't exist
173        unsafe {
174            env::remove_var(key);
175        }
176        assert!(env::var(key).is_err());
177
178        {
179            let _guard = EnvVarGuard::set(key, "temporary");
180            assert_eq!(env::var(key).unwrap(), "temporary");
181        }
182
183        // Should be removed (didn't exist before)
184        assert!(env::var(key).is_err());
185    }
186
187    #[test]
188    #[serial(default_config_env)]
189    fn env_var_guard_multiple_guards_same_key() {
190        let key = "TEST_ENV_VAR_GUARD_MULTI";
191
192        unsafe {
193            env::set_var(key, "initial");
194        }
195
196        {
197            let _guard1 = EnvVarGuard::set(key, "first");
198            assert_eq!(env::var(key).unwrap(), "first");
199
200            {
201                let _guard2 = EnvVarGuard::set(key, "second");
202                assert_eq!(env::var(key).unwrap(), "second");
203            }
204
205            // Should restore to first guard's value
206            assert_eq!(env::var(key).unwrap(), "first");
207        }
208
209        // Should restore to initial value
210        assert_eq!(env::var(key).unwrap(), "initial");
211    }
212
213    #[test]
214    #[serial(default_config_env)]
215    fn cwd_guard_changes_and_restores() {
216        let cur_dir = || env::current_dir().unwrap().canonicalize().unwrap();
217        let tmp1 = tempdir().unwrap();
218        let tmp2 = tempdir().unwrap();
219
220        let original_dir = cur_dir();
221
222        {
223            let _guard = CwdGuard::set(tmp1.path()).unwrap();
224            assert_eq!(cur_dir(), tmp1.path().canonicalize().unwrap());
225
226            {
227                let _guard2 = CwdGuard::set(tmp2.path()).unwrap();
228                assert_eq!(cur_dir(), tmp2.path().canonicalize().unwrap());
229            }
230
231            // Should restore to tmp1
232            assert_eq!(cur_dir(), tmp1.path().canonicalize().unwrap());
233        }
234
235        // Should restore to original directory
236        assert_eq!(cur_dir(), original_dir);
237    }
238
239    #[test]
240    #[serial(default_config_env)]
241    fn cwd_guard_handles_nonexistent_directory_error() {
242        let nonexistent = Path::new("/nonexistent/path/that/does/not/exist");
243
244        let result = CwdGuard::set(nonexistent);
245        assert!(result.is_err());
246    }
247
248    #[test]
249    fn closure_guard_runs_on_drop() {
250        use std::sync::Arc;
251        use std::sync::atomic::{AtomicBool, Ordering};
252
253        let ran = Arc::new(AtomicBool::new(false));
254        let ran_clone = ran.clone();
255        {
256            let _guard = ClosureGuard::new(move || ran_clone.store(true, Ordering::SeqCst));
257            assert!(!ran.load(Ordering::SeqCst));
258        }
259        assert!(ran.load(Ordering::SeqCst));
260    }
261}