use std::path::Path;
use std::process::Command;
#[derive(Debug, Clone)]
pub struct ContractVerifyResult {
pub exit_code: i32,
pub formatted: String,
pub body: String,
}
pub fn looks_like_echo_gaming(body: &str, needle: &str) -> bool {
let needle_l = needle.trim().to_ascii_lowercase();
if needle_l.is_empty() {
return false;
}
let body_l = body.to_ascii_lowercase();
for line in body_l.lines() {
let t = line.trim();
let rest = t
.strip_prefix("echo ")
.or_else(|| t.strip_prefix("printf "));
if let Some(rest) = rest {
let rest = rest
.trim()
.trim_matches(|c| c == '\'' || c == '"' || c == '`');
if rest.contains(&needle_l) || needle_l.contains(rest) {
return true;
}
}
}
let compact: String = body_l.chars().filter(|c| !c.is_whitespace()).collect();
let needle_compact: String = needle_l.chars().filter(|c| !c.is_whitespace()).collect();
!needle_compact.is_empty() && compact == needle_compact
}
pub fn run_contract_verification(command: &str, cwd: &Path) -> ContractVerifyResult {
let cmd = command.trim();
if cmd.is_empty() {
return ContractVerifyResult {
exit_code: 1,
formatted: "[terminal_result status=error backend=harness cwd=. exit_code=1]\nempty verification command\n".into(),
body: "empty verification command".into(),
};
}
let cwd_display = cwd.to_string_lossy();
let output = Command::new("sh")
.arg("-c")
.arg(cmd)
.current_dir(cwd)
.output();
match output {
Ok(out) => {
let exit_code = out.status.code().unwrap_or(1);
let mut body = String::from_utf8_lossy(&out.stdout).into_owned();
if !out.stderr.is_empty() {
if !body.is_empty() {
body.push('\n');
}
body.push_str("[stderr]\n");
body.push_str(&String::from_utf8_lossy(&out.stderr));
}
let status = if exit_code == 0 { "success" } else { "error" };
let formatted = format!(
"[terminal_result status={status} backend=harness cwd={cwd_display} exit_code={exit_code}]\n{body}"
);
ContractVerifyResult {
exit_code,
formatted,
body,
}
}
Err(e) => {
let body = format!("harness verify spawn failed: {e}");
let formatted = format!(
"[terminal_result status=error backend=harness cwd={cwd_display} exit_code=127]\n{body}\n"
);
ContractVerifyResult {
exit_code: 127,
formatted,
body,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn echo_gaming_detected() {
assert!(looks_like_echo_gaming("echo cargo test\n", "cargo test"));
assert!(looks_like_echo_gaming("cargo test", "cargo test"));
assert!(!looks_like_echo_gaming(
"running 2 tests\ntest foo ... ok\n",
"cargo test"
));
}
#[test]
fn harness_true_exits_zero() {
let dir = TempDir::new().expect("tmp");
let r = run_contract_verification("true", dir.path());
assert_eq!(r.exit_code, 0);
assert!(r.formatted.contains("exit_code=0"));
assert!(r.formatted.contains("backend=harness"));
}
#[test]
fn harness_false_exits_nonzero() {
let dir = TempDir::new().expect("tmp");
let r = run_contract_verification("false", dir.path());
assert_ne!(r.exit_code, 0);
}
}