Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-c0re/src/test_env.rs

39 lines
2.1 KiB
Rust

//! Test-only: the crate's single lock for tests that mutate process
//! environment variables.
//!
//! Environment variables are one process-global, and every `#[test]` in this
//! crate lands in the *same* test binary, run on parallel threads. A test that
//! sets a variable, asserts, then restores it is only safe against a *concurrent*
//! test if both take the same lock — so any test here that touches the
//! environment must route through [`lock`] rather than rolling its own
//! save/restore.
//!
//! ⚠️ **The lock has to be crate-wide, not per-module.** `hive-bash-mcp`'s
//! sibling helper records why: two separate per-module mutexes serialise
//! nothing against each other, and that produced a CI-only flake. The pair that
//! motivated this one is `meta::tests` (which renders a flake from
//! `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES`) and `stats::otel_metrics::tests` (which asserts which
//! variable enables the exporter) — different modules, same process env, same
//! binary.
//!
//! ⚠️ **A green local run is weak evidence for this class.** An agent container
//! has the hyperhive variables ambient-set, so a losing race still finds a
//! plausible value; the nix sandbox strips them, so there the race can delete a
//! variable out from under another thread. Reproduce the sandbox shape with
//! `env -u OTEL_EXPORTER_OTLP_ENDPOINT cargo test -p hive-c0re`.
use std::sync::{Mutex, MutexGuard, PoisonError};
static ENV_LOCK: Mutex<()> = Mutex::new(());
/// Serialise this test against every other environment-mutating test in the
/// crate. Hold the returned guard for as long as the variables are perturbed —
/// bind it (`let _env = lock();`), never discard it with `let _ = lock();`,
/// which drops the guard immediately and serialises nothing.
///
/// Recovers from poisoning: a test that panicked mid-mutation has already
/// failed and reported, and refusing to run every later test on top of that
/// turns one failure into a cascade that hides which test actually broke.
pub fn lock() -> MutexGuard<'static, ()> {
ENV_LOCK.lock().unwrap_or_else(PoisonError::into_inner)
}