Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions crates/forge_config/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ static LOAD_DOT_ENV: LazyLock<()> = LazyLock::new(|| {
}
});

/// Caches base-path resolution for the process lifetime.
static BASE_PATH: LazyLock<PathBuf> = LazyLock::new(ConfigReader::resolve_base_path);

/// Merges [`ForgeConfig`] from layered sources using a builder pattern.
#[derive(Default)]
pub struct ConfigReader {
Expand Down Expand Up @@ -58,6 +61,10 @@ impl ConfigReader {
/// existing directory without disruption.
/// 3. `~/.forge` as the default path.
pub fn base_path() -> PathBuf {
BASE_PATH.clone()
}

fn resolve_base_path() -> PathBuf {
if let Ok(path) = std::env::var("FORGE_CONFIG") {
return PathBuf::from(path);
}
Expand Down Expand Up @@ -199,7 +206,7 @@ mod tests {
#[test]
fn test_base_path_uses_forge_config_env_var() {
let _guard = EnvGuard::set(&[("FORGE_CONFIG", "/custom/forge/dir")]);
let actual = ConfigReader::base_path();
let actual = ConfigReader::resolve_base_path();
let expected = PathBuf::from("/custom/forge/dir");
assert_eq!(actual, expected);
}
Expand All @@ -210,7 +217,7 @@ mod tests {
// cannot race with test_base_path_uses_forge_config_env_var.
let _guard = EnvGuard::set_and_remove(&[], &["FORGE_CONFIG"]);

let actual = ConfigReader::base_path();
let actual = ConfigReader::resolve_base_path();
// Without FORGE_CONFIG set the path must be either "forge" (legacy,
// preferred when ~/forge exists) or ".forge" (default new path).
let name = actual.file_name().unwrap();
Expand Down
52 changes: 16 additions & 36 deletions crates/forge_infra/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,43 +158,12 @@ impl EnvironmentInfra for ForgeEnvironmentInfra {
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::{Mutex, MutexGuard};

use forge_config::ForgeConfig;
use pretty_assertions::assert_eq;

use super::*;

/// Serializes tests that mutate environment variables to prevent races.
static ENV_MUTEX: Mutex<()> = Mutex::new(());

/// Holds env vars set for a test's duration and removes them on drop,
/// while holding [`ENV_MUTEX`].
struct EnvGuard {
keys: Vec<&'static str>,
_lock: MutexGuard<'static, ()>,
}

impl EnvGuard {
#[must_use]
fn set(pairs: &[(&'static str, &str)]) -> Self {
let lock = ENV_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let keys = pairs.iter().map(|(k, _)| *k).collect();
for (key, value) in pairs {
unsafe { std::env::set_var(key, value) };
}
Self { keys, _lock: lock }
}
}

impl Drop for EnvGuard {
fn drop(&mut self) {
for key in &self.keys {
unsafe { std::env::remove_var(key) };
}
}
}

#[test]
fn test_to_environment_sets_cwd() {
let fixture_cwd = PathBuf::from("/test/cwd");
Expand All @@ -203,11 +172,22 @@ mod tests {
}

#[test]
fn test_to_environment_uses_forge_config_env_var() {
let _guard = EnvGuard::set(&[("FORGE_CONFIG", "/custom/config/dir")]);
let actual = to_environment(PathBuf::from("/any/cwd"));
let expected = PathBuf::from("/custom/config/dir");
assert_eq!(actual.base_path, expected);
fn test_to_environment_base_path_is_stable_after_env_var_change() {
let fixture_cwd = PathBuf::from("/any/cwd");
let expected = to_environment(fixture_cwd.clone()).base_path;

let previous = std::env::var("FORGE_CONFIG").ok();
unsafe { std::env::set_var("FORGE_CONFIG", "/custom/config/dir") };

let actual = to_environment(fixture_cwd).base_path;

if let Some(value) = previous {
unsafe { std::env::set_var("FORGE_CONFIG", value) };
} else {
unsafe { std::env::remove_var("FORGE_CONFIG") };
}

assert_eq!(actual, expected);
}

#[test]
Expand Down
Loading