mod common;
use common::{Repo, STANDARDS};
const CLEAN: &str = "VERDICT: reviewer=fake session=s-01 major=0 moderate=1 minor=2";
const AIM: &str = "raise the staged file's line count";
#[test]
fn a_reviewer_that_stops_answering_is_killed_at_the_ceiling() {
let repo = Repo::new();
repo.declare_agent("echo 'still thinking' >&2; sleep 60", &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest_within(AIM, "2s");
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("without answering"), "{}", run.err);
assert!(run.err.contains("2s ceiling"), "{}", run.err);
assert!(run.err.contains("standards: reviewing"), "{}", run.err);
assert!(run.err.contains("session "), "{}", run.err);
assert!(run.err.contains("still reviewing"), "{}", run.err);
assert!(run.err.contains("--timeout"), "{}", run.err);
assert!(run.err.contains("still thinking"), "{}", run.err);
}
#[test]
fn an_agent_that_leaves_its_pipe_held_open_does_not_hang_the_run() {
let repo = Repo::new();
let leaves_a_holder = concat!(
"sleep 120 &\n",
r#"python3 -c 'import json; print(json.dumps({"is_error": False, "result": "VERDICT: reviewer=fake session=s-1 major=0 moderate=0 minor=0", "session_id": "s-1", "modelUsage": {"fake-model": {}}}))'"#,
"\nexit 0"
);
repo.declare_agent(leaves_a_holder, &[STANDARDS]);
repo.stage(&["src.rs"]);
let started = std::time::Instant::now();
let run = repo.attest(AIM);
assert!(
started.elapsed() < std::time::Duration::from_secs(60),
"the run waited on a pipe its agent had already finished with: {:?}",
started.elapsed()
);
assert_eq!(run.code, 0, "{}", run.err);
}
#[test]
fn a_crash_on_stderr_reaches_the_author_though_the_agent_exited_clean() {
let repo = Repo::new();
repo.declare_agent(
"echo 'UnhandledPromiseRejection: boom' >&2; exit 0",
&[STANDARDS],
);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("UnhandledPromiseRejection"), "{}", run.err);
}
#[test]
fn an_answer_with_no_verdict_names_why_the_reviewer_stopped() {
let repo = Repo::new();
let answer = r#"python3 -c 'import json; print(json.dumps({"is_error": False, "result": "I read the diff and then", "stop_reason": "max_tokens", "session_id": "s-cut", "modelUsage": {"fake-model": {}}}))'"#;
repo.declare_agent(answer, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("stopped on max_tokens"), "{}", run.err);
}
#[test]
fn the_ceiling_is_read_as_minutes_and_says_so_when_it_cannot_be() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest_within(AIM, "soon");
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("45m"), "{}", run.err);
let zero = repo.attest_within(AIM, "0");
assert_eq!(zero.code, 2, "{}", zero.err);
assert!(zero.err.contains("above zero"), "{}", zero.err);
}
#[test]
fn a_round_cut_short_is_resumed_where_it_stopped() {
let repo = Repo::new();
let hangs_once = concat!(
r#"n=$(cat rounds 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > rounds; "#,
r#"cat >> prompts; echo "[$AGENT_VERDICT_PRIOR_SESSION]" >> handed; "#,
r#"if [ "$n" = 1 ]; then sleep 60; fi; "#,
r#"echo "VERDICT: reviewer=fake session=s-1 major=0 moderate=0 minor=0""#
);
repo.declare_runner(hangs_once, &[STANDARDS]);
repo.stage(&["src.rs"]);
let killed = repo.attest_within(AIM, "2s");
assert_eq!(killed.code, 2, "{}", killed.err);
let cut_short = repo.last_assigned();
repo.transcript_for(&cut_short);
let resumed = repo.again();
assert_eq!(resumed.code, 0, "{}", resumed.err);
assert!(resumed.err.contains("died mid-review"), "{}", resumed.err);
assert!(resumed.err.contains(&cut_short), "{}", resumed.err);
assert!(
resumed.err.contains("transcript was last written"),
"{}",
resumed.err
);
assert!(
repo.read("handed").contains(&format!("[{cut_short}]")),
"{}",
repo.read("handed")
);
assert!(repo.prompts().contains("interrupted"), "{}", repo.prompts());
assert!(
!repo.prompts().contains("Fixes incorporated"),
"{}",
repo.prompts()
);
}
#[test]
fn a_round_that_left_no_transcript_opens_a_fresh_reviewer() {
let repo = Repo::new();
let hangs_once = concat!(
r#"n=$(cat rounds 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > rounds; "#,
r#"cat >> prompts; "#,
r#"if [ "$n" = 1 ]; then sleep 60; fi; "#,
r#"echo "VERDICT: reviewer=fake session=s-1 major=0 moderate=0 minor=0""#
);
repo.declare_runner(hangs_once, &[STANDARDS]);
repo.stage(&["src.rs"]);
let killed = repo.attest_within(AIM, "2s");
assert_eq!(killed.code, 2, "{}", killed.err);
let cut_short = repo.last_assigned();
let fresh = repo.again();
assert_eq!(fresh.code, 0, "{}", fresh.err);
assert!(!fresh.err.contains("died mid-review"), "{}", fresh.err);
assert_ne!(
repo.last_assigned(),
cut_short,
"the dead session was reused"
);
assert!(
!repo.prompts().contains("interrupted"),
"{}",
repo.prompts()
);
}
#[test]
fn a_recorded_round_leaves_no_marker_for_the_next_one_to_resume() {
let repo = Repo::new();
let escalating = concat!(
r#"n=$(cat rounds 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > rounds; "#,
r#"cat >> prompts; m=0; if [ "$n" = 1 ]; then m=1; fi; "#,
r#"echo "VERDICT: reviewer=fake session=s-1 major=$m moderate=0 minor=0""#
);
repo.declare_runner(escalating, &[STANDARDS]);
repo.stage(&["src.rs"]);
let blocked = repo.attest(AIM);
assert_eq!(blocked.code, 1, "{}", blocked.err);
repo.write("src.rs", "reworked");
repo.stage(&["src.rs"]);
let again = repo.again();
assert_eq!(again.code, 0, "{}", again.err);
assert!(!again.err.contains("died mid-review"), "{}", again.err);
assert!(
repo.prompts().contains("Fixes incorporated"),
"{}",
repo.prompts()
);
assert!(
!repo.prompts().contains("interrupted"),
"{}",
repo.prompts()
);
}
#[test]
fn a_resume_that_fails_is_not_resumed_again() {
let repo = Repo::new();
let dies_then_refuses = concat!(
r#"n=$(cat rounds 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > rounds; "#,
r#"echo "[$AGENT_VERDICT_PRIOR_SESSION]" >> handed; "#,
r#"if [ "$n" = 1 ]; then sleep 60; fi; "#,
r#"echo "nothing this tool can record""#
);
repo.declare_runner(dies_then_refuses, &[STANDARDS]);
repo.stage(&["src.rs"]);
let killed = repo.attest_within(AIM, "2s");
assert_eq!(killed.code, 2, "{}", killed.err);
let cut_short = repo.last_assigned();
repo.transcript_for(&cut_short);
let resumed = repo.again();
assert_eq!(resumed.code, 2, "{}", resumed.err);
assert!(
repo.read("handed").contains(&format!("[{cut_short}]")),
"the killed round was not the one taken up: {}",
repo.read("handed")
);
let fresh = repo.again();
assert_eq!(fresh.code, 2, "{}", fresh.err);
assert_ne!(
repo.last_assigned(),
cut_short,
"a session that failed on resume was resumed a second time"
);
}
#[test]
fn a_second_attest_is_refused_while_one_is_running() {
let repo = Repo::new();
repo.declare_agent("echo $$ > reviewing\nsleep 30", &[STANDARDS]);
repo.stage(&["src.rs"]);
let root = repo.root();
let reviewing = common::pipe(&repo.dir.join("reviewing"));
let mut running = repo.running(&[
"attest",
"--repo",
&root,
"--intent",
AIM,
common::BACKGROUND,
]);
let reviewer =
common::arrived_at(&reviewing).expect("the first run never reached its reviewer");
let beside = repo.attest(AIM);
assert_eq!(beside.code, 2, "{}", beside.err);
assert!(
beside.err.contains("another review is already running"),
"{}",
beside.err
);
assert!(
beside.err.contains(&format!("kill {reviewer}")),
"the refusal did not name the reviewer {reviewer}: {}",
beside.err
);
let _ = running.kill();
let _ = running.wait();
}
#[test]
fn a_run_killed_by_a_signal_says_so_and_leaves_its_reviewer_holding_the_claim() {
let repo = Repo::new();
repo.declare_agent("echo $$ > reviewing\nsleep 30", &[STANDARDS]);
repo.stage(&["src.rs"]);
let root = repo.root();
let running_now = common::pipe(&repo.dir.join("reviewing"));
let running = repo.running(&[
"attest",
"--repo",
&root,
"--intent",
AIM,
common::BACKGROUND,
]);
let reviewer =
common::arrived_at(&running_now).expect("the reviewer never started, so nothing held");
let killed = std::process::Command::new("kill")
.args(["-TERM", &running.id().to_string()])
.status()
.expect("kill runs");
assert!(killed.success(), "could not signal the run");
let out = running.wait_with_output().expect("it exits");
let said = String::from_utf8_lossy(&out.stderr);
assert!(said.contains("killed by SIGTERM"), "{said}");
assert!(said.contains("standards"), "{said}");
assert_eq!(
std::os::unix::process::ExitStatusExt::signal(&out.status),
Some(libc::SIGTERM),
"{said}"
);
let beside = repo.attest(AIM);
assert_eq!(beside.code, 2, "{}", beside.err);
assert!(
beside.err.contains("another review is already running"),
"{}",
beside.err
);
assert!(
beside.err.contains(&format!("kill {reviewer}")),
"the refusal did not name the reviewer {reviewer}: {}",
beside.err
);
}
#[test]
fn a_claim_file_nobody_holds_is_simply_taken() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
repo.write(".git/agent-verdict.lock", "0");
let run = repo.attest(AIM);
assert_eq!(run.code, 0, "{}", run.err);
assert!(run.out.contains("standards:"), "{}", run.out);
}
#[test]
fn a_helper_the_reviewer_leaves_behind_does_not_hold_the_repo() {
let repo = Repo::new();
let leaves_a_helper = concat!(
"sleep 120 &\n",
r#"python3 -c 'import json; print(json.dumps({"is_error": False, "result": "VERDICT: reviewer=fake session=s-1 major=0 moderate=0 minor=0", "session_id": "s-1", "modelUsage": {"fake-model": {}}}))'"#
);
repo.declare_agent(leaves_a_helper, &[STANDARDS]);
repo.stage(&["src.rs"]);
let first = repo.attest(AIM);
assert_eq!(first.code, 0, "{}", first.err);
let again = repo.again();
assert!(
!again.err.contains("another review is already running"),
"a helper outlived the review and kept the repo: {}",
again.err
);
assert_eq!(again.code, 0, "{}", again.err);
}
#[test]
fn a_run_killed_before_a_round_opens_names_no_review() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
repo.write("slow-judge", "");
let judging = common::pipe(&repo.dir.join("judging"));
let running = repo.running(&[
"attest",
"--repo",
&repo.root(),
"--intent",
AIM,
common::BACKGROUND,
]);
common::arrived_at(&judging).expect("the run never reached the judge");
let _ = std::process::Command::new("kill")
.args(["-TERM", &running.id().to_string()])
.status();
let out = running.wait_with_output().expect("it exits");
let said = String::from_utf8_lossy(&out.stderr);
assert!(said.contains("killed by SIGTERM"), "{said}");
assert!(
!said.contains("while reviewing"),
"a run killed before any round opened named one: {said}"
);
}