//! 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 { 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[..]); }