paceflow 0.2.2

Local-first CLI that turns AI coding session history and git metadata into engineering analytics.
Documentation
//! Test-only helpers shared across the crate's `#[cfg(test)]` modules.
//!
//! Several test modules need to mutate process environment variables
//! (most often `PACEFLOW_HOME`) and rely on a mutex to serialize those
//! mutations. Previously each module had its own local `OnceLock<Mutex<()>>`,
//! which meant a test in one module could change a shared env var while a
//! test in another module was mid-flight. This module exposes a single
//! process-wide lock plus a `ScopedEnvVar` RAII guard so every test that
//! touches the environment goes through the same critical section.

use std::ffi::{OsStr, OsString};
use std::sync::{Mutex, MutexGuard, OnceLock};

pub fn env_lock() -> &'static Mutex<()> {
    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
    LOCK.get_or_init(|| Mutex::new(()))
}

pub fn lock_env() -> MutexGuard<'static, ()> {
    env_lock()
        .lock()
        .unwrap_or_else(|poisoned| poisoned.into_inner())
}

pub struct ScopedEnvVar {
    key: &'static str,
    original: Option<OsString>,
}

impl ScopedEnvVar {
    pub fn set(key: &'static str, value: impl AsRef<OsStr>) -> Self {
        let original = std::env::var_os(key);
        unsafe {
            std::env::set_var(key, value);
        }
        Self { key, original }
    }

    pub fn unset(key: &'static str) -> Self {
        let original = std::env::var_os(key);
        unsafe {
            std::env::remove_var(key);
        }
        Self { key, original }
    }
}

impl Drop for ScopedEnvVar {
    fn drop(&mut self) {
        match &self.original {
            Some(value) => unsafe {
                std::env::set_var(self.key, value);
            },
            None => unsafe {
                std::env::remove_var(self.key);
            },
        }
    }
}