use super::super::{ExecError, SpawnTool, ToolCall, ToolExecutor};
use super::fixtures::{FixedClock, HarnessRoot, StepDir, driver_target};
use serde_json::json;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
const MARKER_RETRIES: u32 = 12_000;
fn await_marker(marker: &Path) {
for _ in 0..MARKER_RETRIES {
if marker.exists() {
return;
}
thread::sleep(Duration::from_millis(5));
}
panic!("fixture never touched {}", marker.display());
}
#[test]
fn sigterm_inside_deadline_lets_tool_exit_with_its_own_code() {
let root = HarnessRoot::new();
root.install(
"well-behaved",
r#"
trap 'echo bye; exit 9' TERM
touch ready
while true; do sleep 0.05; done
"#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let exec =
SpawnTool::new(root.path(), &clock, driver_target()).with_deadline(Duration::from_secs(60));
let stop = AtomicBool::new(false);
let ready = step.worktree.join("ready");
thread::scope(|s| {
s.spawn(|| {
await_marker(&ready);
stop.store(true, Ordering::SeqCst);
});
let outcome = exec
.execute(
ToolCall {
id: "tu_c1",
name: "well-behaved",
input: &json!({}),
},
&step.path,
&stop,
)
.unwrap();
assert!(outcome.is_error, "exit 9 → is_error true");
assert_eq!(outcome.content, b"bye\n");
});
}
#[test]
fn sigkill_after_deadline_when_tool_ignores_sigterm() {
let root = HarnessRoot::new();
root.install(
"stubborn",
r#"
trap 'echo ignored 1>&2' TERM
touch ready
while true; do sleep 0.05; done
"#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let deadline = Duration::from_millis(250);
let exec = SpawnTool::new(root.path(), &clock, driver_target()).with_deadline(deadline);
let stop = AtomicBool::new(false);
let ready = step.worktree.join("ready");
let started = Instant::now();
thread::scope(|s| {
s.spawn(|| {
await_marker(&ready);
stop.store(true, Ordering::SeqCst);
});
let err = exec
.execute(
ToolCall {
id: "tu_c2",
name: "stubborn",
input: &json!({}),
},
&step.path,
&stop,
)
.unwrap_err();
match err {
ExecError::KilledBySignal { name, signal } => {
assert_eq!(name, "stubborn");
assert_eq!(signal, libc::SIGKILL);
}
other => panic!("expected KilledBySignal, got {other:?}"),
}
});
assert!(
started.elapsed() >= deadline,
"cascade returned before deadline elapsed"
);
}
#[test]
fn unsolicited_signal_kill_is_reported_as_harness_fault() {
let root = HarnessRoot::new();
root.install(
"self-segv",
r#"
echo about-to-segv
kill -SEGV $$
"#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let err = exec
.execute(
ToolCall {
id: "tu_c3",
name: "self-segv",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
)
.unwrap_err();
match err {
ExecError::KilledBySignal { name, signal } => {
assert_eq!(name, "self-segv");
assert_eq!(signal, libc::SIGSEGV);
}
other => panic!("expected KilledBySignal(SIGSEGV), got {other:?}"),
}
}