repositories / smith
smith
There are many coding harnesses - but this one is fast
owned by admin
smith-cli/tests/e2e_eval.rs
Raw//! End-to-end `smith eval` workflows through the built binary
//! (`SMH-SPEC-SPEC0001`, Interface modes: Eval).
use assert_cmd::Command;
use smith_core::trace::build_trace;
use smith_core::{Session, TraceRecord, load_session};
use std::path::{Path, PathBuf};
const MOCK_REPLY: &str = "mock provider: no model configured";
/// A fresh data home per test; stale runs never leak into assertions.
fn scratch(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("smith-eval-test-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
dir
}
fn smith(data_home: &Path) -> Command {
let mut command = Command::cargo_bin("smith").unwrap();
command
.env("XDG_DATA_HOME", data_home)
.env_remove("SMITH_PROVIDER")
.env_remove("SMITH_MODEL")
.env_remove("SMITH_BASE_URL")
.env_remove("ANTHROPIC_API_KEY")
.env_remove("OPENAI_API_KEY")
.env_remove("GEMINI_API_KEY");
command
}
fn eval_session(data_home: &Path) -> PathBuf {
data_home.join("smith/sessions/run/eval.smh")
}
/// Decode the session file the binary left behind into its trace.
fn trace_of(path: &Path) -> Vec<TraceRecord> {
let recovery =
load_session(path, smith::config::Config::default_valid().max_frame_bytes).unwrap();
assert!(recovery.damage.is_empty(), "{:?}", recovery.damage);
assert_eq!(recovery.torn_tail_at, None);
build_trace(&Session::from_frames(&recovery.frames).unwrap())
}
#[test]
fn eval_mock_turn_prints_the_reply() {
let home = scratch("text");
smith(&home)
.args(["eval", "--mock", "hello"])
.assert()
.success()
.stdout(format!("{MOCK_REPLY}\n"))
.stderr("");
}
#[test]
fn eval_json_reports_text_reason_and_cost() {
let home = scratch("json");
let output = smith(&home)
.args(["eval", "--mock", "--json", "hello"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.lines().count(), 1, "{stdout}");
// The mock reply and its zero cost are fixed; the line is pinned byte for byte.
insta::assert_snapshot!("eval_mock_json", stdout);
}
#[test]
fn second_eval_appends_to_the_same_session_and_keeps_the_first_trace() {
let home = scratch("resume");
let session = eval_session(&home);
smith(&home)
.args(["eval", "--mock", "first"])
.assert()
.success();
let first_len = std::fs::metadata(&session).unwrap().len();
let first_trace = trace_of(&session);
assert!(first_trace.len() > 1, "{first_trace:?}");
smith(&home)
.args(["eval", "--mock", "second"])
.assert()
.success()
.stdout(format!("{MOCK_REPLY}\n"));
let second_len = std::fs::metadata(&session).unwrap().len();
let second_trace = trace_of(&session);
assert!(second_len > first_len, "{second_len} <= {first_len}");
assert!(second_trace.len() > first_trace.len());
assert_eq!(second_trace[..first_trace.len()], first_trace[..]);
}