From c20fbd2727d25de896587ffc94d65ea267a88f98 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 16:45:21 +0000 Subject: [PATCH] test: serialise the tests that set process environment variables Eight lib tests in six modules set or remove BOJ_URL, OVERLAY_URL, HYPATIA_URL or TYPELL_URL. The environment is process-wide and `cargo test` runs tests on parallel threads, so one test could read the value another had just set. Rust CI's parallel Cargo test failed on main at c0dc793 this way (boj_live::tests::boj_endpoint_default_and_override saw boj/commands.rs's custom URL). A test-only `crate::test_env::lock()` now guards all eight; a poisoned lock is taken anyway. Running just those tests 300 times in parallel: 44 failures on main, 0 with the lock. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SJtjqoZaSQo7uBwvwVMA9G --- src-gossamer/src/boj/commands.rs | 1 + src-gossamer/src/boj_live.rs | 1 + src-gossamer/src/governance/commands.rs | 2 ++ src-gossamer/src/hypatia/commands.rs | 2 ++ src-gossamer/src/lib.rs | 17 +++++++++++++++++ src-gossamer/src/overlay/commands.rs | 1 + src-gossamer/src/typell/commands.rs | 1 + 7 files changed, 25 insertions(+) diff --git a/src-gossamer/src/boj/commands.rs b/src-gossamer/src/boj/commands.rs index e3e40fe..a95c733 100644 --- a/src-gossamer/src/boj/commands.rs +++ b/src-gossamer/src/boj/commands.rs @@ -106,6 +106,7 @@ mod tests { #[test] fn boj_url_default_and_override() { + let _env = crate::test_env::lock(); std::env::remove_var("BOJ_URL"); assert_eq!(boj_url(), DEFAULT_BOJ_URL); let custom = "http://boj.local:9999/v2"; diff --git a/src-gossamer/src/boj_live.rs b/src-gossamer/src/boj_live.rs index 46b43c1..1127734 100644 --- a/src-gossamer/src/boj_live.rs +++ b/src-gossamer/src/boj_live.rs @@ -122,6 +122,7 @@ mod tests { /// Verify the endpoint builder respects BOJ_URL and strips trailing slashes. #[test] fn boj_endpoint_default_and_override() { + let _env = crate::test_env::lock(); std::env::remove_var("BOJ_URL"); let ep = boj_endpoint(); assert_eq!(ep.base_url, DEFAULT_BOJ_URL); diff --git a/src-gossamer/src/governance/commands.rs b/src-gossamer/src/governance/commands.rs index aa2c38a..cd49d85 100644 --- a/src-gossamer/src/governance/commands.rs +++ b/src-gossamer/src/governance/commands.rs @@ -134,12 +134,14 @@ mod tests { #[test] fn boj_url_default() { + let _env = crate::test_env::lock(); std::env::remove_var("BOJ_URL"); assert_eq!(boj_url(), DEFAULT_BOJ_URL); } #[test] fn boj_url_override() { + let _env = crate::test_env::lock(); let custom = "http://nesy.local:8800/v2"; std::env::set_var("BOJ_URL", custom); assert_eq!(boj_url(), custom); diff --git a/src-gossamer/src/hypatia/commands.rs b/src-gossamer/src/hypatia/commands.rs index bfcb72d..bf09ac0 100644 --- a/src-gossamer/src/hypatia/commands.rs +++ b/src-gossamer/src/hypatia/commands.rs @@ -117,6 +117,7 @@ mod tests { /// Verify the default Hypatia URL is the expected localhost endpoint. #[test] fn smoke_hypatia_url_default() { + let _env = crate::test_env::lock(); // Unset the env var to ensure we get the hardcoded default. std::env::remove_var("HYPATIA_URL"); let url = hypatia_url(); @@ -126,6 +127,7 @@ mod tests { /// Verify that HYPATIA_URL env var overrides the default. #[test] fn smoke_hypatia_url_override() { + let _env = crate::test_env::lock(); std::env::set_var("HYPATIA_URL", "http://custom-host:9999/api/v2"); let url = hypatia_url(); assert_eq!(url, "http://custom-host:9999/api/v2"); diff --git a/src-gossamer/src/lib.rs b/src-gossamer/src/lib.rs index 97328a3..c1c5ac0 100644 --- a/src-gossamer/src/lib.rs +++ b/src-gossamer/src/lib.rs @@ -95,6 +95,23 @@ pub mod watcher; pub mod wiring_inspector; pub mod workspace; +/// Serialises the tests that set or remove process environment variables +/// (`BOJ_URL`, `OVERLAY_URL`, `HYPATIA_URL`, `TYPELL_URL`). `cargo test` runs +/// tests on parallel threads and the environment is process-wide, so without +/// this lock one test can read the value another has just set. +#[cfg(test)] +pub(crate) mod test_env { + use std::sync::{Mutex, MutexGuard}; + + static LOCK: Mutex<()> = Mutex::new(()); + + /// Hold the guard for the whole test. A test that panics while holding it + /// poisons the lock; the next test takes it anyway. + pub(crate) fn lock() -> MutexGuard<'static, ()> { + LOCK.lock().unwrap_or_else(|e| e.into_inner()) + } +} + /// Cross-module integration tests. These exercise the public command-backed /// functions end-to-end. They live in the lib (not `tests/`) so they run via /// `cargo test --lib` without cargo also force-building the GTK-linked diff --git a/src-gossamer/src/overlay/commands.rs b/src-gossamer/src/overlay/commands.rs index 1adfe9c..33c4426 100644 --- a/src-gossamer/src/overlay/commands.rs +++ b/src-gossamer/src/overlay/commands.rs @@ -273,6 +273,7 @@ mod tests { #[test] fn overlay_url_default_and_override() { + let _env = crate::test_env::lock(); std::env::remove_var("OVERLAY_URL"); assert_eq!(overlay_url(), DEFAULT_OVERLAY_URL); let custom = "http://overlay.local:9999/v2"; diff --git a/src-gossamer/src/typell/commands.rs b/src-gossamer/src/typell/commands.rs index f4043d5..90a8369 100644 --- a/src-gossamer/src/typell/commands.rs +++ b/src-gossamer/src/typell/commands.rs @@ -104,6 +104,7 @@ mod tests { #[test] fn typell_url_default_and_override() { + let _env = crate::test_env::lock(); std::env::remove_var("TYPELL_URL"); assert_eq!(typell_url(), DEFAULT_TYPELL_URL); let custom = "http://typell.local:9999/v2";