Files
optimclaw/src/config/builder.rs
T
3aa36c8f55 fix(tests): eliminate env mutex poison cascade (#1558)
* fix(tests): eliminate env mutex poison cascade and fix test flakiness

The shared ENV_MUTEX used by ~68 config tests would cascade a single
test panic into failures across every module. Replace all .unwrap() /
.expect() lock acquisitions with a poison-recovering lock_env() helper.
Consolidate rogue module-local ENV_LOCK instances (workspace, orchestrator,
bootstrap) onto the shared global mutex to prevent cross-module races.

Also fixes:
- gateway user_id fallback was hardcoded to "default" instead of owner_id
- test_ironclaw_env_path used LazyLock which is order-dependent

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* test(helpers): add regression test for lock_env poison recovery

Satisfies the regression-test-check CI gate by adding a test that
intentionally poisons ENV_MUTEX and verifies lock_env() recovers.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(ci): detect test changes inside #[cfg(test)] regions

The regression test check relied on git diff -W to expand context to
function boundaries, but git doesn't recognize Rust `mod tests {}` as a
function boundary. Changes to imports, helpers, or lock calls inside
test modules were invisible to the check.

Add a line-level fallback: for each changed .rs file, find where
#[cfg(test)] starts and check if any diff hunk targets a line at or
after that boundary. This catches edits anywhere inside test modules
regardless of git's language awareness.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: address PR review feedback

- Clear ENV_MUTEX poison after regression test so it doesn't leave
  global state dirty for subsequent tests.
- Fix CI regression-test-check to match #[cfg(test)] only when followed
  by `mod` (the test module pattern), avoiding false positives from
  standalone #[cfg(test)] items like statics or functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-22 14:36:24 -07:00

95 lines
3.1 KiB
Rust

use std::path::PathBuf;
use std::time::Duration;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Builder mode configuration.
#[derive(Debug, Clone)]
pub struct BuilderModeConfig {
/// Whether the software builder tool is enabled.
pub enabled: bool,
/// Directory for build artifacts (default: temp dir).
pub build_dir: Option<PathBuf>,
/// Maximum iterations for the build loop.
pub max_iterations: u32,
/// Build timeout in seconds.
pub timeout_secs: u64,
/// Whether to automatically register built WASM tools.
pub auto_register: bool,
}
impl Default for BuilderModeConfig {
fn default() -> Self {
Self {
enabled: true,
build_dir: None,
max_iterations: 20,
timeout_secs: 600,
auto_register: true,
}
}
}
impl BuilderModeConfig {
pub(crate) fn resolve(settings: &crate::settings::Settings) -> Result<Self, ConfigError> {
let bs = &settings.builder;
Ok(Self {
enabled: parse_bool_env("BUILDER_ENABLED", bs.enabled)?,
build_dir: optional_env("BUILDER_DIR")?
.map(PathBuf::from)
.or_else(|| bs.build_dir.clone()),
max_iterations: parse_optional_env("BUILDER_MAX_ITERATIONS", bs.max_iterations)?,
timeout_secs: parse_optional_env("BUILDER_TIMEOUT_SECS", bs.timeout_secs)?,
auto_register: parse_bool_env("BUILDER_AUTO_REGISTER", bs.auto_register)?,
})
}
/// Convert to BuilderConfig for the builder tool.
pub fn to_builder_config(&self) -> crate::tools::BuilderConfig {
crate::tools::BuilderConfig {
build_dir: self.build_dir.clone().unwrap_or_else(std::env::temp_dir),
max_iterations: self.max_iterations,
timeout: Duration::from_secs(self.timeout_secs),
cleanup_on_failure: true,
validate_wasm: true,
run_tests: true,
auto_register: self.auto_register,
wasm_output_dir: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::helpers::lock_env;
use crate::settings::Settings;
#[test]
fn resolve_falls_back_to_settings() {
let _guard = lock_env();
let mut settings = Settings::default();
settings.builder.max_iterations = 99;
settings.builder.auto_register = false;
let cfg = BuilderModeConfig::resolve(&settings).expect("resolve");
assert_eq!(cfg.max_iterations, 99);
assert!(!cfg.auto_register);
}
#[test]
fn env_overrides_settings() {
let _guard = lock_env();
let mut settings = Settings::default();
settings.builder.timeout_secs = 123;
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe { std::env::set_var("BUILDER_TIMEOUT_SECS", "3") };
let cfg = BuilderModeConfig::resolve(&settings).expect("resolve");
unsafe { std::env::remove_var("BUILDER_TIMEOUT_SECS") };
assert_eq!(cfg.timeout_secs, 3);
}
}