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:
Dark-Alex-17 committed 2026-09-16 14:02:42 -06:00
1 parent 4087f3b3d0
commit 6514588e05
12 files changed
+282 -191

No files matched your search

+66 -6
View File
@@ -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),
}
}
}
}