use std::sync::atomic::{AtomicUsize, Ordering};
use io_harness::provider::{CompletionRequest, CompletionResponse, ToolCall};
use io_harness::{run, Provider, RunOutcome, Store, TaskContract, Verification};
use serde_json::json;
struct MockScript {
steps: Vec<Vec<ToolCall>>,
at: AtomicUsize,
}
impl MockScript {
fn new(steps: Vec<Vec<ToolCall>>) -> Self {
Self { steps, at: AtomicUsize::new(0) }
}
}
impl Provider for MockScript {
async fn complete(&self, _req: CompletionRequest) -> io_harness::Result<CompletionResponse> {
let i = self.at.fetch_add(1, Ordering::SeqCst);
Ok(CompletionResponse {
tool_calls: self.steps.get(i).cloned().unwrap_or_default(),
..Default::default()
})
}
}
fn call(name: &str, args: serde_json::Value) -> ToolCall {
ToolCall { name: name.into(), arguments: args }
}
fn fixture() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
std::fs::create_dir_all(&src).unwrap();
std::fs::write(src.join("a.rs"), "pub fn a() -> u32 { 0 }\n").unwrap();
std::fs::write(src.join("b.rs"), "pub fn b() -> u32 { 0 }\n").unwrap();
dir
}
fn verify() -> Verification {
Verification::WorkspaceTestPasses {
files: vec!["src/a.rs".into(), "src/b.rs".into()],
test_src: "#[test] fn t() { assert_eq!(a() + b(), 42); }".into(),
}
}
#[tokio::test]
async fn agent_finds_then_edits_several_files_and_the_set_verifies() {
let dir = fixture();
let contract = TaskContract::workspace(
"Make a() + b() equal 42 by editing the two source files.",
dir.path(),
verify(),
);
let script = MockScript::new(vec![
vec![call("find", json!({ "name_glob": "*.rs" }))],
vec![call("grep", json!({ "pattern": "fn a", "path_glob": "src/*.rs" }))],
vec![call("read_file", json!({ "path": "src/a.rs" }))],
vec![call("write_file", json!({ "path": "src/a.rs", "content": "pub fn a() -> u32 { 40 }\n" }))],
vec![call("write_file", json!({ "path": "src/b.rs", "content": "pub fn b() -> u32 { 2 }\n" }))],
]);
let store = Store::memory().unwrap();
let result = run(&contract, &script, &store).await.unwrap();
assert_eq!(result.outcome, RunOutcome::Success { steps: 5 });
let a = std::fs::read_to_string(dir.path().join("src/a.rs")).unwrap();
let b = std::fs::read_to_string(dir.path().join("src/b.rs")).unwrap();
assert!(a.contains("40"));
assert!(b.contains("2 }"));
let steps = store.steps(result.run_id).unwrap();
let calls: String = steps.iter().map(|s| s.tool_call.clone()).collect();
assert!(calls.contains("find"));
assert!(calls.contains("grep"));
assert!(calls.contains("write_file"));
assert!(calls.contains("src/a.rs"));
assert!(calls.contains("src/b.rs"));
}
#[tokio::test]
async fn run_fails_when_one_of_the_edited_files_is_wrong() {
let dir = fixture();
let contract = TaskContract::workspace(
"Make a() + b() equal 42 by editing the two source files.",
dir.path(),
verify(),
)
.with_max_steps(4);
let script = MockScript::new(vec![
vec![call("write_file", json!({ "path": "src/a.rs", "content": "pub fn a() -> u32 { 40 }\n" }))],
vec![call("write_file", json!({ "path": "src/b.rs", "content": "pub fn b() -> u32 { 99 }\n" }))],
]);
let store = Store::memory().unwrap();
let result = run(&contract, &script, &store).await.unwrap();
assert_eq!(result.outcome, RunOutcome::StepCapReached { steps: 4 });
}
#[tokio::test]
async fn write_outside_the_workspace_is_refused_not_crashing_the_run() {
let dir = fixture();
let contract = TaskContract::workspace("noop", dir.path(), verify()).with_max_steps(1);
let script = MockScript::new(vec![vec![call(
"write_file",
json!({ "path": "../escaped.rs", "content": "pwned" }),
)]]);
let store = Store::memory().unwrap();
let result = run(&contract, &script, &store).await.unwrap();
assert_eq!(result.outcome, RunOutcome::StepCapReached { steps: 1 });
assert!(!dir.path().parent().unwrap().join("escaped.rs").exists());
}