From 1c057a1a335684bb289b1dd5db59b117f287b45f Mon Sep 17 00:00:00 2001 From: leynos Date: Fri, 12 Jun 2026 13:59:18 +0200 Subject: [PATCH] Add safe PathGuard::set_path hiding unsafe set_var (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `Environment::set_var` API is `unsafe` under Rust 2024, which forced `unsafe` blocks into every consumer that mutated `PATH` through a `PathGuard`. Provide a safe `set_path` method that acquires the global `EnvLock`, performs the mutation, and relies on the guard's drop behaviour to restore the captured value — keeping the unsafety encapsulated within `PathGuard` itself. Update the mock-based and real-environment tests to use the safe helper instead of open-coded `unsafe` blocks. --- test_support/src/path_guard.rs | 27 ++++++++++++++++++++++++++- tests/path_guard_tests.rs | 8 +++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/test_support/src/path_guard.rs b/test_support/src/path_guard.rs index 87d434c59..2b182e664 100644 --- a/test_support/src/path_guard.rs +++ b/test_support/src/path_guard.rs @@ -3,9 +3,10 @@ //! Provides a guard that resets the environment variable on drop so tests do //! not pollute global state. -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use crate::env_guard::{EnvGuard, Environment, StdEnv}; +use crate::env_lock::EnvLock; /// Environment abstraction for setting variables. pub trait Env: Environment {} @@ -65,4 +66,28 @@ impl PathGuard { pub fn env_mut(&mut self) -> &mut E { self.inner.env_mut() } + + /// Set `PATH` to `value` through the guard's environment. + /// + /// Safe wrapper around the `unsafe` [`Environment::set_var`] operation: + /// the global [`EnvLock`] serialises the mutation and the guard restores + /// the original value on drop, so the unsafety stays encapsulated here + /// rather than leaking `unsafe` blocks into consumer code. + /// + /// # Examples + /// + /// ```rust,no_run + /// use std::ffi::OsStr; + /// use test_support::PathGuard; + /// + /// let mut guard = PathGuard::capture(); + /// guard.set_path(OsStr::new("/stub/bin")); + /// // The captured PATH is restored when `guard` drops. + /// ``` + pub fn set_path(&mut self, value: &OsStr) { + let _lock = EnvLock::acquire(); + // SAFETY: `EnvLock` serialises the mutation and the guard restores + // the captured value on drop. + unsafe { self.inner.env_mut().set_var("PATH", value) }; + } } diff --git a/tests/path_guard_tests.rs b/tests/path_guard_tests.rs index 109050ade..241c6e385 100644 --- a/tests/path_guard_tests.rs +++ b/tests/path_guard_tests.rs @@ -34,9 +34,7 @@ fn restores_path_without_touching_real_env() { .return_const(()); { let mut guard = PathGuard::with_env(Some("/orig".into()), env); - unsafe { - guard.env_mut().set_var("PATH", OsStr::new("/tmp")); - } + guard.set_path(OsStr::new("/tmp")); } } @@ -45,8 +43,8 @@ fn restores_path_without_touching_real_env() { fn capture_snapshots_and_restores_real_path() -> Result<()> { let original = std::env::var_os("PATH"); { - let _guard = PathGuard::capture(); - test_support::env::set_var("PATH", OsStr::new("/netsuke-capture-test")); + let mut guard = PathGuard::capture(); + guard.set_path(OsStr::new("/netsuke-capture-test")); let mutated = std::env::var_os("PATH").context("PATH should be set after mutation")?; ensure!( mutated == OsStr::new("/netsuke-capture-test"),