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    /// Removes the variable for the guard's lifetime, restoring it on drop.
48    ///
49    /// Useful for tests that assert default behavior and must not be perturbed by a value the
50    /// developer happens to have exported.
51    pub fn unset(key: &'static str) -> Self {
52        let prev = env::var(key).ok();
53        unsafe {
54            env::remove_var(key);
55        }
56        Self { key, prev }
57    }
58}
59
60#[cfg(not(target_family = "wasm"))]
61impl Drop for EnvVarGuard {
62    fn drop(&mut self) {
63        if let Some(v) = &self.prev {
64            unsafe {
65                env::set_var(self.key, v);
66            }
67        } else {
68            unsafe {
69                env::remove_var(self.key);
70            }
71        }
72    }
73}
74
75/// Guard that temporarily changes the current working directory and restores the previous one on drop.
76///
77/// This guard is useful for testing or temporarily changing the working directory in a
78/// controlled manner. When the guard is dropped, it automatically restores the previous
79/// working directory.
80///
81/// # Error Handling
82///
83/// If restoring the previous directory fails during drop, the error is silently ignored
84/// (logged via `let _ = ...`). This is intentional to avoid panicking in destructors.
85///
86/// # Examples
87///
88/// ```no_run
89/// use std::path::Path;
90///
91/// use xet_runtime::utils::CwdGuard;
92///
93/// // Temporarily change to a different directory
94/// let new_dir = Path::new("/tmp");
95/// let _guard = CwdGuard::set(new_dir)?;
96/// // Current working directory is changed here
97/// // When _guard is dropped, the previous directory is restored
98/// # Ok::<(), std::io::Error>(())
99/// ```
100#[cfg(not(target_family = "wasm"))]
101pub struct CwdGuard {
102    prev: PathBuf,
103}
104
105#[cfg(not(target_family = "wasm"))]
106impl CwdGuard {
107    pub fn set(new_dir: &Path) -> std::io::Result<Self> {
108        let prev = env::current_dir()?;
109        env::set_current_dir(new_dir)?;
110        Ok(Self { prev })
111    }
112}
113
114#[cfg(not(target_family = "wasm"))]
115impl Drop for CwdGuard {
116    fn drop(&mut self) {
117        let _ = env::set_current_dir(&self.prev);
118    }
119}
120
121/// Guard that executes a closure when dropped.
122///
123/// Useful for ensuring cleanup logic runs even on early returns or panics.
124///
125/// # Examples
126///
127/// ```no_run
128/// use xet_runtime::utils::ClosureGuard;
129///
130/// let _guard = ClosureGuard::new(|| {
131///     println!("cleanup!");
132/// });
133/// // closure runs when _guard is dropped
134/// ```
135pub struct ClosureGuard<F: FnOnce()> {
136    closure: Option<F>,
137}
138
139impl<F: FnOnce()> ClosureGuard<F> {
140    pub fn new(f: F) -> Self {
141        Self { closure: Some(f) }
142    }
143}
144
145impl<F: FnOnce()> Drop for ClosureGuard<F> {
146    fn drop(&mut self) {
147        if let Some(f) = self.closure.take() {
148            f();
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use serial_test::serial;
156    use tempfile::tempdir;
157
158    use super::*;
159
160    #[test]
161    #[serial(default_config_env)]
162    fn env_var_guard_sets_and_restores() {
163        let key = "TEST_ENV_VAR_GUARD";
164
165        // Set an initial value
166        unsafe {
167            env::set_var(key, "initial");
168        }
169
170        {
171            let _guard = EnvVarGuard::set(key, "temporary");
172            assert_eq!(env::var(key).unwrap(), "temporary");
173        }
174
175        // Should be restored to initial value
176        assert_eq!(env::var(key).unwrap(), "initial");
177    }
178
179    #[test]
180    #[serial(default_config_env)]
181    fn env_var_guard_restores_none_when_var_did_not_exist() {
182        let key = "TEST_ENV_VAR_GUARD_NEW";
183
184        // Ensure it doesn't exist
185        unsafe {
186            env::remove_var(key);
187        }
188        assert!(env::var(key).is_err());
189
190        {
191            let _guard = EnvVarGuard::set(key, "temporary");
192            assert_eq!(env::var(key).unwrap(), "temporary");
193        }
194
195        // Should be removed (didn't exist before)
196        assert!(env::var(key).is_err());
197    }
198
199    #[test]
200    #[serial(default_config_env)]
201    fn env_var_guard_multiple_guards_same_key() {
202        let key = "TEST_ENV_VAR_GUARD_MULTI";
203
204        unsafe {
205            env::set_var(key, "initial");
206        }
207
208        {
209            let _guard1 = EnvVarGuard::set(key, "first");
210            assert_eq!(env::var(key).unwrap(), "first");
211
212            {
213                let _guard2 = EnvVarGuard::set(key, "second");
214                assert_eq!(env::var(key).unwrap(), "second");
215            }
216
217            // Should restore to first guard's value
218            assert_eq!(env::var(key).unwrap(), "first");
219        }
220
221        // Should restore to initial value
222        assert_eq!(env::var(key).unwrap(), "initial");
223    }
224
225    #[test]
226    #[serial(default_config_env)]
227    fn cwd_guard_changes_and_restores() {
228        let cur_dir = || env::current_dir().unwrap().canonicalize().unwrap();
229        let tmp1 = tempdir().unwrap();
230        let tmp2 = tempdir().unwrap();
231
232        let original_dir = cur_dir();
233
234        {
235            let _guard = CwdGuard::set(tmp1.path()).unwrap();
236            assert_eq!(cur_dir(), tmp1.path().canonicalize().unwrap());
237
238            {
239                let _guard2 = CwdGuard::set(tmp2.path()).unwrap();
240                assert_eq!(cur_dir(), tmp2.path().canonicalize().unwrap());
241            }
242
243            // Should restore to tmp1
244            assert_eq!(cur_dir(), tmp1.path().canonicalize().unwrap());
245        }
246
247        // Should restore to original directory
248        assert_eq!(cur_dir(), original_dir);
249    }
250
251    #[test]
252    #[serial(default_config_env)]
253    fn cwd_guard_handles_nonexistent_directory_error() {
254        let nonexistent = Path::new("/nonexistent/path/that/does/not/exist");
255
256        let result = CwdGuard::set(nonexistent);
257        assert!(result.is_err());
258    }
259
260    #[test]
261    fn closure_guard_runs_on_drop() {
262        use std::sync::Arc;
263        use std::sync::atomic::{AtomicBool, Ordering};
264
265        let ran = Arc::new(AtomicBool::new(false));
266        let ran_clone = ran.clone();
267        {
268            let _guard = ClosureGuard::new(move || ran_clone.store(true, Ordering::SeqCst));
269            assert!(!ran.load(Ordering::SeqCst));
270        }
271        assert!(ran.load(Ordering::SeqCst));
272    }
273}