use super::super::{ExecError, SpawnTool, ToolCall, ToolExecutor};
use super::fixtures::{
FixedClock, HarnessRoot, StepDir, after_header, driver_target, write_script,
};
use serde_json::json;
use std::sync::atomic::AtomicBool;
use tempfile::TempDir;
#[test]
fn write_script_helper_is_round_tripped_by_the_fixture() {
use std::time::Duration;
let dir = TempDir::new().unwrap();
let path = dir.path().join("hi.sh");
write_script(&path, "echo hi");
let mut attempt: u32 = 1;
let out = loop {
match std::process::Command::new(&path).output() {
Ok(o) => break o,
Err(e) if e.raw_os_error() == Some(libc::ETXTBSY) && attempt < 10_000 => {
attempt += 1;
std::thread::sleep(Duration::from_millis(2));
}
Err(e) => panic!("exec failed: {e}"),
}
};
assert!(out.status.success());
assert_eq!(out.stdout, b"hi\n");
}
#[test]
fn spawn_retries_past_transient_etxtbsy() {
use std::fs::OpenOptions;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;
let root = HarnessRoot::new();
let installed = root.install("blocked", "echo unblocked");
let barrier = Arc::new(Barrier::new(2));
let path_clone = installed.clone();
let b2 = barrier.clone();
let holder = thread::spawn(move || {
let f = OpenOptions::new().write(true).open(&path_clone).unwrap();
b2.wait();
thread::sleep(Duration::from_millis(40));
drop(f);
});
barrier.wait();
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target()).with_etxtbsy_budget(1_000_000);
let outcome = exec
.execute(
ToolCall {
id: "tu_etx",
name: "blocked",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.expect("a million-attempt budget rides out the hold on any machine");
holder.join().unwrap();
assert!(!outcome.is_error);
assert_eq!(after_header(&outcome.content), b"unblocked\n");
}
#[test]
fn spawn_surfaces_etxtbsy_after_budget_exhausted() {
use std::fs::OpenOptions;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;
let root = HarnessRoot::new();
let installed = root.install("forever-busy", "echo will-not-run");
let barrier = Arc::new(Barrier::new(2));
let stop_holder = Arc::new(AtomicBool::new(false));
let path_clone = installed.clone();
let b2 = barrier.clone();
let stop_clone = stop_holder.clone();
let holder = thread::spawn(move || {
let f = OpenOptions::new().write(true).open(&path_clone).unwrap();
b2.wait();
while !stop_clone.load(std::sync::atomic::Ordering::SeqCst) {
thread::sleep(Duration::from_millis(5));
}
drop(f);
});
barrier.wait();
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target()).with_etxtbsy_budget(3);
let err = exec
.execute(
ToolCall {
id: "tu_etx_x",
name: "forever-busy",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.expect_err("the attempt budget exhausts against a permanent hold");
stop_holder.store(true, std::sync::atomic::Ordering::SeqCst);
holder.join().unwrap();
match err {
ExecError::Spawn { name, .. } => assert_eq!(name, "forever-busy"),
other => panic!("expected Spawn, got {other:?}"),
}
}