1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Shared thread-local seam for reading process-environment variables in a way
//! that unit tests can override hermetically.
//!
//! Production code that must stay testable reads env through [`env_var_seamed`]
//! instead of [`std::env::var`]. Under `cfg(not(test))` that is exactly
//! `std::env::var(key).ok()`. Under `cfg(test)` the process environment is
//! structurally invisible: reads come from a per-thread override map instead,
//! so ambient shell configuration (a developer's exported `HARN_LLM_MODEL`, a
//! CI wrapper's provider default) can never seed state a test did not ask for,
//! and no cross-test serialization lock is needed for the seamed variables.
//!
//! Tests inject values through [`TestEnvGuard`], obtained from
//! [`test_env_guard`]. The guard clears this thread's overrides on both
//! creation and drop, giving each test hermetic edges: neither ambient
//! configuration nor a sibling test's leftovers leak in, and nothing leaks out.
//!
//! # Visibility across threads
//!
//! The override map is thread-local, so a value set on the test thread is
//! invisible to work the test spawns on other threads (e.g. a multi-threaded
//! Tokio runtime). Code exercised on a worker thread that must observe a
//! test-provided value has to keep reading real process env under a
//! process-global lock instead. The base-URL resolver
//! [`crate::llm_config::resolve_base_url`] is deliberately left on
//! [`std::env::var`] for this reason.
//!
//! # Convergence
//!
//! This module is the sole owner of the keyed-override seam pattern. Two
//! domain-local seams predated it and now converge here, reading through
//! [`env_var_seamed`] and layering any domain lifecycle over [`test_env_guard`]
//! rather than duplicating the thread-local map:
//! - `egress`'s `EgressTestEnvGuard` (the `HARN_EGRESS_*` family) owns a
//! [`TestEnvGuard`] and adds an egress-policy reset on its own lifecycle.
//! - `stdlib::sandbox::handler_env`'s `HandlerSandboxTestGuard` (the single
//! `HARN_HANDLER_SANDBOX` variable) owns a [`TestEnvGuard`] purely to keep
//! the single-variable `set(value)` ergonomics; it adds no extra behavior.
/// Reads one process-environment variable through the test seam.
///
/// - `cfg(not(test))`: `std::env::var(key).ok()`.
/// - `cfg(test)`: the calling thread's override map only; the process
/// environment is not consulted.
pub(crate) fn env_var_seamed(key: &str) -> Option<String> {
#[cfg(test)]
{
overrides::get(key)
}
#[cfg(not(test))]
{
std::env::var(key).ok()
}
}
#[cfg(test)]
mod overrides {
use std::cell::RefCell;
use std::collections::BTreeMap;
thread_local! {
static OVERRIDES: RefCell<BTreeMap<String, String>> =
const { RefCell::new(BTreeMap::new()) };
}
pub(super) fn get(key: &str) -> Option<String> {
OVERRIDES.with(|overrides| overrides.borrow().get(key).cloned())
}
pub(super) fn set(key: &str, value: &str) {
OVERRIDES.with(|overrides| {
overrides
.borrow_mut()
.insert(key.to_owned(), value.to_owned());
});
}
pub(super) fn clear() {
OVERRIDES.with(|overrides| overrides.borrow_mut().clear());
}
}
/// Gives a test a clean, hermetic env universe for the seamed variables: on
/// both creation and drop it clears this thread's overrides, so neither ambient
/// configuration nor a sibling test's leftovers can leak in, and nothing leaks
/// out. All seamed state is thread-keyed, so no cross-test serialization is
/// needed and the guard is safe to hold across `await` points on a
/// single-threaded runtime.
#[cfg(test)]
#[must_use]
pub(crate) fn test_env_guard() -> TestEnvGuard {
overrides::clear();
TestEnvGuard {}
}
/// Guard returned by [`test_env_guard`]. Injects env values for this thread via
/// [`TestEnvGuard::set`] and clears all overrides on drop.
#[cfg(test)]
pub(crate) struct TestEnvGuard {}
#[cfg(test)]
impl TestEnvGuard {
/// Sets an environment variable for this thread only, visible to
/// [`env_var_seamed`] readers on the same thread.
pub(crate) fn set(&self, key: &str, value: &str) {
overrides::set(key, value);
}
}
#[cfg(test)]
impl Drop for TestEnvGuard {
fn drop(&mut self) {
overrides::clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seam_reads_overrides_and_clears_on_drop() {
{
let env = test_env_guard();
env.set("HARN_LLM_PROVIDER", "mock");
assert_eq!(env_var_seamed("HARN_LLM_PROVIDER").as_deref(), Some("mock"));
// A key never set reads as absent regardless of ambient env.
assert_eq!(env_var_seamed("HARN_LLM_MODEL"), None);
}
// Dropping the guard clears the thread's overrides.
assert_eq!(env_var_seamed("HARN_LLM_PROVIDER"), None);
}
#[test]
fn fresh_guard_clears_prior_overrides() {
let first = test_env_guard();
first.set("HARN_DEFAULT_PROVIDER", "openai");
drop(first);
let second = test_env_guard();
assert_eq!(env_var_seamed("HARN_DEFAULT_PROVIDER"), None);
drop(second);
}
}