mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
* 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]>
127 lines
4.2 KiB
Rust
127 lines
4.2 KiB
Rust
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
|
|
use crate::bootstrap::ironclaw_base_dir;
|
|
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
|
|
use crate::error::ConfigError;
|
|
|
|
/// WASM sandbox configuration.
|
|
#[derive(Debug, Clone)]
|
|
pub struct WasmConfig {
|
|
/// Whether WASM tool execution is enabled.
|
|
pub enabled: bool,
|
|
/// Directory containing installed WASM tools (default: ~/.ironclaw/tools/).
|
|
pub tools_dir: PathBuf,
|
|
/// Default memory limit in bytes (default: 10 MB).
|
|
pub default_memory_limit: u64,
|
|
/// Default execution timeout in seconds (default: 60).
|
|
pub default_timeout_secs: u64,
|
|
/// Default fuel limit for CPU metering (default: 10M).
|
|
pub default_fuel_limit: u64,
|
|
/// Whether to cache compiled modules.
|
|
pub cache_compiled: bool,
|
|
/// Directory for compiled module cache.
|
|
pub cache_dir: Option<PathBuf>,
|
|
}
|
|
|
|
impl Default for WasmConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
tools_dir: default_tools_dir(),
|
|
default_memory_limit: 10 * 1024 * 1024, // 10 MB
|
|
default_timeout_secs: 60,
|
|
default_fuel_limit: 10_000_000,
|
|
cache_compiled: true,
|
|
cache_dir: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the default tools directory (~/.ironclaw/tools/).
|
|
fn default_tools_dir() -> PathBuf {
|
|
ironclaw_base_dir().join("tools")
|
|
}
|
|
|
|
impl WasmConfig {
|
|
pub(crate) fn resolve(settings: &crate::settings::Settings) -> Result<Self, ConfigError> {
|
|
let ws = &settings.wasm;
|
|
Ok(Self {
|
|
enabled: parse_bool_env("WASM_ENABLED", ws.enabled)?,
|
|
tools_dir: optional_env("WASM_TOOLS_DIR")?
|
|
.map(PathBuf::from)
|
|
.or_else(|| ws.tools_dir.clone())
|
|
.unwrap_or_else(default_tools_dir),
|
|
default_memory_limit: parse_optional_env(
|
|
"WASM_DEFAULT_MEMORY_LIMIT",
|
|
ws.default_memory_limit,
|
|
)?,
|
|
default_timeout_secs: parse_optional_env(
|
|
"WASM_DEFAULT_TIMEOUT_SECS",
|
|
ws.default_timeout_secs,
|
|
)?,
|
|
default_fuel_limit: parse_optional_env(
|
|
"WASM_DEFAULT_FUEL_LIMIT",
|
|
ws.default_fuel_limit,
|
|
)?,
|
|
cache_compiled: parse_bool_env("WASM_CACHE_COMPILED", ws.cache_compiled)?,
|
|
cache_dir: optional_env("WASM_CACHE_DIR")?
|
|
.map(PathBuf::from)
|
|
.or_else(|| ws.cache_dir.clone()),
|
|
})
|
|
}
|
|
|
|
/// Convert to WasmRuntimeConfig.
|
|
pub fn to_runtime_config(&self) -> crate::tools::wasm::WasmRuntimeConfig {
|
|
use crate::tools::wasm::{FuelConfig, ResourceLimits, WasmRuntimeConfig};
|
|
|
|
WasmRuntimeConfig {
|
|
default_limits: ResourceLimits {
|
|
memory_bytes: self.default_memory_limit,
|
|
fuel: self.default_fuel_limit,
|
|
timeout: Duration::from_secs(self.default_timeout_secs),
|
|
},
|
|
fuel_config: FuelConfig {
|
|
initial_fuel: self.default_fuel_limit,
|
|
enabled: true,
|
|
},
|
|
cache_compiled: self.cache_compiled,
|
|
cache_dir: self.cache_dir.clone(),
|
|
optimization_level: wasmtime::OptLevel::Speed,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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.wasm.default_memory_limit = 42;
|
|
settings.wasm.cache_compiled = false;
|
|
|
|
let cfg = WasmConfig::resolve(&settings).expect("resolve");
|
|
assert_eq!(cfg.default_memory_limit, 42);
|
|
assert!(!cfg.cache_compiled);
|
|
}
|
|
|
|
#[test]
|
|
fn env_overrides_settings() {
|
|
let _guard = lock_env();
|
|
let mut settings = Settings::default();
|
|
settings.wasm.default_fuel_limit = 42;
|
|
|
|
// SAFETY: Under ENV_MUTEX, no concurrent env access.
|
|
unsafe { std::env::set_var("WASM_DEFAULT_FUEL_LIMIT", "7") };
|
|
let cfg = WasmConfig::resolve(&settings).expect("resolve");
|
|
unsafe { std::env::remove_var("WASM_DEFAULT_FUEL_LIMIT") };
|
|
|
|
assert_eq!(cfg.default_fuel_limit, 7);
|
|
}
|
|
}
|