use super::super::{
OUTPUT_FILE, STEP_TOOLS_SUBDIR, SpawnTool, ToolCall, ToolExecutor, ToolOutputRecord,
};
use super::fixtures::{FixedClock, HarnessRoot, StepDir, driver_target};
use serde_json::json;
use std::sync::atomic::AtomicBool;
#[test]
fn execute_all_actually_overlaps_the_calls() {
let root = HarnessRoot::new();
for (me, them) in [("a", "b"), ("b", "a")] {
root.install(
&format!("rendezvous_{me}"),
&format!(
r#"
touch mark_{me}
for _ in $(seq 1 400); do
[ -f mark_{them} ] && exit 0
sleep 0.05
done
echo "never saw mark_{them}" >&2
exit 1
"#
),
);
}
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let input = json!({});
let results = exec.execute_all(
&[
ToolCall {
id: "toolu_01-1",
name: "rendezvous_a",
input: &input,
},
ToolCall {
id: "toolu_01-2",
name: "rendezvous_b",
input: &input,
},
],
&step.path,
&AtomicBool::new(false),
None,
);
assert_eq!(results.len(), 2);
for (idx, result) in results.iter().enumerate() {
let outcome = result.as_ref().expect("no harness fault");
assert!(
!outcome.is_error,
"entry {idx} did not meet its partner: {}",
String::from_utf8_lossy(&outcome.content)
);
}
}
#[test]
fn execute_all_returns_results_in_call_order_and_lands_every_record() {
let root = HarnessRoot::new();
root.install("slow", r#"sleep 0.3; printf "slow""#);
root.install("quick", r#"printf "quick""#);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let input = json!({});
let results = exec.execute_all(
&[
ToolCall {
id: "toolu_01-1",
name: "slow",
input: &input,
},
ToolCall {
id: "toolu_01-2",
name: "quick",
input: &input,
},
],
&step.path,
&AtomicBool::new(false),
None,
);
assert_eq!(results[0].as_ref().unwrap().content, b"Exit code: 0\nslow");
assert_eq!(results[1].as_ref().unwrap().content, b"Exit code: 0\nquick");
for (id, want) in [("toolu_01-1", "slow"), ("toolu_01-2", "quick")] {
let dir = step.path.join(STEP_TOOLS_SUBDIR).join(id);
let record: ToolOutputRecord =
serde_json::from_slice(&std::fs::read(dir.join(OUTPUT_FILE)).unwrap()).unwrap();
assert_eq!(record.stdout, want);
assert_eq!(record.exit_code, 0);
assert_eq!(record.started_at, "iso-1");
assert_eq!(record.ended_at, "iso-2");
}
}
#[test]
fn a_prepare_failure_becomes_that_call_s_result() {
let root = HarnessRoot::new();
root.install("greet", r#"printf "hi""#);
let clock = FixedClock::default();
let nowhere = tempfile::TempDir::new().unwrap();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let input = json!({});
let results = exec.execute_all(
&[ToolCall {
id: "toolu_01-1",
name: "greet",
input: &input,
}],
nowhere.path(),
&AtomicBool::new(false),
None,
);
assert_eq!(results.len(), 1);
assert!(matches!(
results[0],
Err(super::super::ExecError::NoWorktree { .. })
));
}
#[test]
fn the_default_execute_all_is_serial() {
struct Counting(std::cell::RefCell<Vec<String>>);
impl ToolExecutor for Counting {
fn execute(
&self,
call: ToolCall<'_>,
_step_dir: &std::path::Path,
_stop: &AtomicBool,
_bound: Option<crate::config::ToolOutputBound>,
) -> Result<super::super::ToolOutcome, super::super::ExecError> {
self.0.borrow_mut().push(call.name.to_string());
Ok(super::super::ToolOutcome {
content: call.name.as_bytes().to_vec(),
is_error: false,
})
}
}
let exec = Counting(std::cell::RefCell::new(Vec::new()));
let input = json!({});
let results = exec.execute_all(
&[
ToolCall {
id: "1",
name: "alpha",
input: &input,
},
ToolCall {
id: "2",
name: "beta",
input: &input,
},
],
std::path::Path::new("/unused"),
&AtomicBool::new(false),
None,
);
assert_eq!(*exec.0.borrow(), vec!["alpha", "beta"]);
assert_eq!(results.len(), 2);
assert_eq!(results[0].as_ref().unwrap().content, b"alpha");
}