use super::super::{
ENV_CONV_BRANCH, ENV_CONV_REPO, INPUT_FILE, OUTPUT_FILE, STEP_TOOLS_SUBDIR, SpawnTool,
ToolCall, ToolExecutor, ToolInputRecord, ToolOutputRecord,
};
use super::fixtures::{FixedClock, HarnessRoot, StepDir, after_header, driver_target};
use serde_json::json;
use std::sync::atomic::AtomicBool;
#[test]
fn happy_path_writes_input_and_output_records() {
let root = HarnessRoot::new();
root.install("greet", r#"printf "hello %s" "$1""#);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let outcome = exec
.execute(
ToolCall {
id: "toolu_01",
name: "greet",
input: &json!({"who": "world"}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
assert!(!outcome.is_error);
assert_eq!(outcome.content, b"Exit code: 0\nhello ");
let dir = step.path.join(STEP_TOOLS_SUBDIR).join("toolu_01");
let input: ToolInputRecord =
serde_json::from_slice(&std::fs::read(dir.join(INPUT_FILE)).unwrap()).unwrap();
assert_eq!(input.id, "toolu_01");
assert_eq!(input.name, "greet");
assert_eq!(input.input, json!({"who": "world"}));
let output: ToolOutputRecord =
serde_json::from_slice(&std::fs::read(dir.join(OUTPUT_FILE)).unwrap()).unwrap();
assert_eq!(output.exit_code, 0);
assert_eq!(output.stdout, "hello ");
assert_eq!(output.stderr, "");
assert_eq!(output.started_at, "iso-1");
assert_eq!(output.ended_at, "iso-2");
}
#[test]
fn non_zero_exit_states_its_code_and_marks_the_stderr_block() {
let root = HarnessRoot::new();
root.install(
"fail",
r#"
echo "out-line"
echo "err-line" 1>&2
exit 7
"#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let outcome = exec
.execute(
ToolCall {
id: "toolu_2",
name: "fail",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
assert!(outcome.is_error);
assert_eq!(
outcome.content,
b"Exit code: 7\nout-line\n--- stderr ---\nerr-line\n"
);
let dir = step.path.join(STEP_TOOLS_SUBDIR).join("toolu_2");
let output: ToolOutputRecord =
serde_json::from_slice(&std::fs::read(dir.join(OUTPUT_FILE)).unwrap()).unwrap();
assert_eq!(output.exit_code, 7);
assert_eq!(output.stdout, "out-line\n");
assert_eq!(output.stderr, "err-line\n");
}
#[test]
fn exit_zero_still_surfaces_stderr() {
let root = HarnessRoot::new();
root.install(
"noisy",
r#"
echo "loud diagnostic" 1>&2
echo "quiet result"
"#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let outcome = exec
.execute(
ToolCall {
id: "tu_n",
name: "noisy",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
assert!(!outcome.is_error);
assert_eq!(
outcome.content,
b"Exit code: 0\nquiet result\n--- stderr ---\nloud diagnostic\n"
);
let dir = step.path.join(STEP_TOOLS_SUBDIR).join("tu_n");
let output: ToolOutputRecord =
serde_json::from_slice(&std::fs::read(dir.join(OUTPUT_FILE)).unwrap()).unwrap();
assert_eq!(output.stderr, "loud diagnostic\n");
}
#[test]
fn stdin_payload_is_offered_verbatim_to_the_tool() {
let root = HarnessRoot::new();
root.install("echo-stdin", "cat");
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let input = json!({"command": "ls -la", "n": 42});
let outcome = exec
.execute(
ToolCall {
id: "tu_s",
name: "echo-stdin",
input: &input,
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
let echoed: serde_json::Value = serde_json::from_slice(after_header(&outcome.content)).unwrap();
assert_eq!(echoed, input);
}
#[test]
fn tool_that_ignores_stdin_still_succeeds() {
let root = HarnessRoot::new();
root.install("ignore-stdin", "echo done");
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let outcome = exec
.execute(
ToolCall {
id: "tu_i",
name: "ignore-stdin",
input: &json!("a string this time"),
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
assert!(!outcome.is_error);
assert_eq!(after_header(&outcome.content), b"done\n");
}
#[test]
fn executor_sets_conv_repo_and_conv_branch_env_vars_on_tool_subprocess() {
let root = HarnessRoot::new();
root.install(
"echoenv",
r#"printf "%s\n%s" "${LERNIE_CONV_REPO:-}" "${LERNIE_CONV_BRANCH:-}""#,
);
let clock = FixedClock::default();
let step = StepDir::new();
let exec = SpawnTool::new(root.path(), &clock, driver_target());
let outcome = exec
.execute(
ToolCall {
id: "tu_env",
name: "echoenv",
input: &json!({}),
},
&step.path,
&AtomicBool::new(false),
None,
)
.unwrap();
assert!(!outcome.is_error);
let conv_repo = step
.path
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
let expected = format!("{}\nconvid", conv_repo.display());
assert_eq!(
String::from_utf8(after_header(&outcome.content).to_vec()).unwrap(),
expected
);
assert_eq!(ENV_CONV_REPO, "LERNIE_CONV_REPO");
assert_eq!(ENV_CONV_BRANCH, "LERNIE_CONV_BRANCH");
}