From 32f5a018a4750b030348442321d763910dac8af2 Mon Sep 17 00:00:00 2001 From: ming Date: Thu, 27 Aug 2026 22:36:44 +0800 Subject: [PATCH] test(bindings): make temporary-home cleanup panic-safe --- crates/core/src/bindings.rs | 74 +++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/crates/core/src/bindings.rs b/crates/core/src/bindings.rs index ecf433d..9e8a457 100644 --- a/crates/core/src/bindings.rs +++ b/crates/core/src/bindings.rs @@ -253,7 +253,7 @@ fn normalized_favorite_order( mod tests { use super::*; use crate::firetv::FireTvAction; - use std::{env, fs, path::PathBuf, sync::Mutex}; + use std::{env, ffi::OsString, fs, path::PathBuf, sync::Mutex}; static TEST_ENV_LOCK: Mutex<()> = Mutex::new(()); @@ -270,31 +270,54 @@ mod tests { } } - fn with_temp_home(test: impl FnOnce()) { - let _env_guard = TEST_ENV_LOCK.lock().expect("lock test environment"); - let original_home = env::var_os("HOME"); - let original_appdata = env::var_os("APPDATA"); - let temp_home = - env::temp_dir().join(format!("sendo-bindings-test-{}", generate_binding_id())); + struct TestHomeGuard { + original_home: Option, + original_appdata: Option, + temp_home: PathBuf, + } - env::set_var("HOME", &temp_home); - env::remove_var("APPDATA"); + impl TestHomeGuard { + fn new() -> Self { + let original_home = env::var_os("HOME"); + let original_appdata = env::var_os("APPDATA"); + let temp_home = + env::temp_dir().join(format!("sendo-bindings-test-{}", generate_binding_id())); - test(); + env::set_var("HOME", &temp_home); + env::remove_var("APPDATA"); - if let Some(home) = original_home { - env::set_var("HOME", home); - } else { - env::remove_var("HOME"); + Self { + original_home, + original_appdata, + temp_home, + } } + } - if let Some(appdata) = original_appdata { - env::set_var("APPDATA", appdata); - } else { - env::remove_var("APPDATA"); + impl Drop for TestHomeGuard { + fn drop(&mut self) { + if let Some(home) = self.original_home.take() { + env::set_var("HOME", home); + } else { + env::remove_var("HOME"); + } + + if let Some(appdata) = self.original_appdata.take() { + env::set_var("APPDATA", appdata); + } else { + env::remove_var("APPDATA"); + } + + let _ = fs::remove_dir_all(&self.temp_home); } + } - let _ = fs::remove_dir_all(temp_home); + fn with_temp_home(test: impl FnOnce()) { + let _env_guard = TEST_ENV_LOCK + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + let _home_guard = TestHomeGuard::new(); + test(); } fn stored_bindings_path() -> PathBuf { @@ -367,6 +390,19 @@ mod tests { }); } + #[test] + fn restores_test_environment_when_the_body_panics() { + let original_home = env::var_os("HOME"); + let original_appdata = env::var_os("APPDATA"); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + with_temp_home(|| panic!("simulated test panic")); + })); + + assert!(result.is_err()); + assert_eq!(env::var_os("HOME"), original_home); + assert_eq!(env::var_os("APPDATA"), original_appdata); + } + #[test] fn replaces_bindings_without_leaving_temporary_files() { with_temp_home(|| {