fix(TASK-046): engine polish + wizard bypass + ETXTBSY retry
- unify hook base-env assembly on hooks::base_envs_parts (drop the hand-copied hook_base_envs in function/agents.rs; graph executor and spawn-result hooks now share the single construction site, test-pinned) - factor the duplicated job.started dispatch in jobs.rs handle_start into one fire_job_started helper - COYOTE_LLM_ERROR now carries the immediate error (to_string()), matching COYOTE_TOOL_ERROR; test pins the new format - remove all stale #[allow(dead_code)] markers in src/hooks.rs - test infra: rename install_warn_collector -> install_log_collector, add poison-symmetric warn_snapshot/debug_snapshot helpers, hoist a shared EnvVarGuard (TestConfigDirGuard now builds on it), add a same-fire payload-uniqueness test, and give global_tools env overrides the same malformed-JSON debug diagnostic as global_hooks - inspection-only flags (--info et al) bypass the first-run wizard: missing config + info_flag returns Config::default() without writing anything, in both the sandbox and interactive arms (tests included) - bound the shim-relocation test's exec in an ETXTBSY retry (10x50ms) - document the intended 'roles' reserved-name asymmetry at resolve_bundle_name's gate
This commit is contained in:
12 files changed
+282
-191
No files matched your search
+66
-6
@@ -1,6 +1,7 @@
|
||||
//! Test-only helpers shared across modules' unit tests.
|
||||
|
||||
use log::{Level, LevelFilter, Log, Metadata, Record};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::sync::{Mutex, Once, OnceLock};
|
||||
|
||||
struct TestLogCollector;
|
||||
@@ -44,6 +45,25 @@ pub(crate) fn debug_messages() -> &'static Mutex<Vec<String>> {
|
||||
DEBUG_MESSAGES.get_or_init(Mutex::default)
|
||||
}
|
||||
|
||||
/// A copy of everything in [`warn_messages`], recovering from poisoning the
|
||||
/// same way the collector itself does.
|
||||
pub(crate) fn warn_snapshot() -> Vec<String> {
|
||||
snapshot_of(warn_messages())
|
||||
}
|
||||
|
||||
/// A copy of everything in [`debug_messages`], recovering from poisoning the
|
||||
/// same way the collector itself does.
|
||||
pub(crate) fn debug_snapshot() -> Vec<String> {
|
||||
snapshot_of(debug_messages())
|
||||
}
|
||||
|
||||
fn snapshot_of(buffer: &Mutex<Vec<String>>) -> Vec<String> {
|
||||
buffer
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.clone()
|
||||
}
|
||||
|
||||
impl Log for TestLogCollector {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
captures_warn(metadata) || captures_hooks_debug(metadata)
|
||||
@@ -68,15 +88,55 @@ impl Log for TestLogCollector {
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
/// Installs the process-wide log collector. `log::set_logger` accepts one
|
||||
/// logger per process, so every test that captures warns or hooks debug
|
||||
/// output must install through this shared entry point. The max level is
|
||||
/// Debug so the hooks debug capture sees its records; warn capture is
|
||||
/// unaffected.
|
||||
pub(crate) fn install_warn_collector() {
|
||||
/// Installs the process-wide log collector capturing warn-level messages and
|
||||
/// hooks-module debug messages. `log::set_logger` accepts one logger per
|
||||
/// process, so every test that captures either stream must install through
|
||||
/// this shared entry point. The max level is Debug so the hooks debug capture
|
||||
/// sees its records; warn capture is unaffected.
|
||||
pub(crate) fn install_log_collector() {
|
||||
static INSTALL: Once = Once::new();
|
||||
INSTALL.call_once(|| {
|
||||
log::set_logger(&TestLogCollector).expect("no other logger should be installed");
|
||||
log::set_max_level(LevelFilter::Debug);
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets (or removes) an environment variable for the guard's lifetime and
|
||||
/// restores the previous value on drop, including on panic, so a failing
|
||||
/// assertion cannot leak the override into subsequent tests. Tests using it
|
||||
/// must serialize (`#[serial]`) — the process environment is global.
|
||||
pub(crate) struct EnvVarGuard {
|
||||
key: String,
|
||||
previous: Option<OsString>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
pub(crate) fn set(key: impl Into<String>, value: impl AsRef<OsStr>) -> Self {
|
||||
let key = key.into();
|
||||
let previous = std::env::var_os(&key);
|
||||
unsafe {
|
||||
std::env::set_var(&key, value);
|
||||
}
|
||||
Self { key, previous }
|
||||
}
|
||||
|
||||
pub(crate) fn unset(key: impl Into<String>) -> Self {
|
||||
let key = key.into();
|
||||
let previous = std::env::var_os(&key);
|
||||
unsafe {
|
||||
std::env::remove_var(&key);
|
||||
}
|
||||
Self { key, previous }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.previous {
|
||||
Some(previous) => std::env::set_var(&self.key, previous),
|
||||
None => std::env::remove_var(&self.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user