39 lines
2.1 KiB
Rust
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)
|
|
}
|