fix(core): cfg-gate test override + restore TEST_OVERRIDE on panic

TEST_OVERRIDE and test_get_override are now #[cfg(test)]-gated and
the read in probe_power_state is wrapped in a #[cfg(test)] block,
so test scaffolding doesn't compile into production builds.

with_override now uses a drop-guard pattern so a panic inside body
still resets TEST_OVERRIDE, preventing stale state from leaking
into subsequent tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jars
2026-05-09 11:30:33 +01:00
parent 0c761c0be6
commit 3d978e3978

View File

@@ -64,6 +64,7 @@ fn read_trimmed(path: &Path) -> Option<String> {
fs::read_to_string(path).ok().map(|s| s.trim().to_owned()) fs::read_to_string(path).ok().map(|s| s.trim().to_owned())
} }
#[cfg(test)]
use std::sync::Mutex; use std::sync::Mutex;
/// Top-level power-state probe. /// Top-level power-state probe.
@@ -80,6 +81,7 @@ use std::sync::Mutex;
/// ///
/// (TTL cache is added in Task 1.4.) /// (TTL cache is added in Task 1.4.)
pub fn probe_power_state() -> PowerState { pub fn probe_power_state() -> PowerState {
#[cfg(test)]
if let Some(state) = test_get_override() { if let Some(state) = test_get_override() {
return state; return state;
} }
@@ -111,14 +113,29 @@ fn platform_probe() -> PowerState {
// In-process override slot. Tests must use `with_override` (below) to // In-process override slot. Tests must use `with_override` (below) to
// set it; production code never writes to this. Read on every probe. // set it; production code never writes to this. Read on every probe.
#[cfg(test)]
static TEST_OVERRIDE: Mutex<Option<PowerState>> = Mutex::new(None); static TEST_OVERRIDE: Mutex<Option<PowerState>> = Mutex::new(None);
#[cfg(test)]
fn test_get_override() -> Option<PowerState> { fn test_get_override() -> Option<PowerState> {
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") *TEST_OVERRIDE.lock().expect("power test override mutex poisoned")
} }
/// Drop-guard that resets `TEST_OVERRIDE` to `None` on drop (including on unwind).
/// This prevents a panicking test body from leaking stale override state into
/// subsequent tests.
#[cfg(test)]
struct OverrideGuard;
#[cfg(test)]
impl Drop for OverrideGuard {
fn drop(&mut self) {
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = None;
}
}
/// Run `body` with the in-process override set to `state`. Restores /// Run `body` with the in-process override set to `state`. Restores
/// the previous override (always `None` in practice) on return. /// `TEST_OVERRIDE` to `None` on return, even if `body` panics.
/// ///
/// Holds a dedicated `TEST_LOCK` mutex for the duration of the body, /// Holds a dedicated `TEST_LOCK` mutex for the duration of the body,
/// so override-using unit tests run serially with respect to each /// so override-using unit tests run serially with respect to each
@@ -128,9 +145,8 @@ pub(crate) fn with_override<R>(state: Option<PowerState>, body: impl FnOnce() ->
static TEST_LOCK: Mutex<()> = Mutex::new(()); static TEST_LOCK: Mutex<()> = Mutex::new(());
let _lock = TEST_LOCK.lock().expect("power TEST_LOCK poisoned"); let _lock = TEST_LOCK.lock().expect("power TEST_LOCK poisoned");
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = state; *TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = state;
let result = body(); let _guard = OverrideGuard;
*TEST_OVERRIDE.lock().expect("power test override mutex poisoned") = None; body()
result
} }
#[cfg(test)] #[cfg(test)]