#![cfg(unix)]
#[allow(dead_code)]
mod common;
#[path = "common/settlement.rs"]
mod settlement;
use settlement::{Harness, TERMINAL};
use std::process::Stdio;
use std::thread;
use std::time::Duration;
const SETTLE: Duration = Duration::from_secs(60);
fn spawn_bg(harness: &Harness, id: &str, extra: &[&str]) {
let mut args = vec!["--bg"];
args.extend_from_slice(extra);
let output = harness.run(id, &args).output().unwrap();
assert!(
output.status.success(),
"--bg dispatch failed: stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn foreground_run_exits_one_when_verify_fails() {
let harness = Harness::new();
let child = harness
.run("t-settle-fg-fail", &[])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
harness.release(1);
let output = child.wait_with_output().unwrap();
assert_eq!(
output.status.code(),
Some(1),
"stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(harness.status("t-settle-fg-fail").as_deref(), Some("failed"));
}
#[test]
fn wait_exit_code_matches_outcome_after_failing_verify() {
let harness = Harness::new();
let id = "t-settle-wait-fail";
spawn_bg(&harness, id, &[]);
let waiter = harness
.aid()
.args(["wait", id, "--timeout", "110"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
harness.release(1);
let output = waiter.wait_with_output().unwrap();
assert_eq!(harness.wait_terminal(id), "failed");
assert_eq!(
output.status.code(),
Some(1),
"stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn verify_failure_with_retry_ends_with_child_exit_code() {
let harness = Harness::new();
let parent = "t-settle-retry";
let attach = harness
.run(parent, &["--retry", "1"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
harness.release(1);
let child = harness.wait_for(SETTLE, |h| h.child_of(parent));
harness.release(0);
let output = attach.wait_with_output().unwrap();
assert_eq!(
output.status.code(),
Some(0),
"stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(harness.wait_terminal(&child), "done");
assert_eq!(harness.status(parent).as_deref(), Some("failed"));
}
#[test]
#[ignore = "task status is terminal before verification settles"]
fn status_is_not_terminal_while_verify_is_gated() {
let harness = Harness::new();
let id = "t-settle-gated-status";
spawn_bg(&harness, id, &[]);
harness.wait_verifier_holding();
let status = harness.status(id).unwrap_or_default();
assert!(!TERMINAL.contains(&status.as_str()), "status {status} is terminal while verify is gated");
harness.release(0);
assert_eq!(harness.wait_terminal(id), "done");
}
#[test]
#[ignore = "accept is allowed before verification settles"]
fn accept_is_refused_while_verify_is_gated() {
let harness = Harness::with_git_project();
let id = "t-settle-gated-accept";
spawn_bg(&harness, id, &["-w", "test/settle-accept"]);
harness.wait_verifier_holding();
let refused = harness.aid().args(["accept", id]).output().unwrap();
assert!(
!refused.status.success(),
"accept succeeded while verify was gated: stdout={}",
String::from_utf8_lossy(&refused.stdout)
);
harness.release(0);
assert_eq!(harness.wait_terminal(id), "done");
let accepted = harness.aid().args(["accept", id]).output().unwrap();
assert!(
accepted.status.success(),
"accept failed after verify passed: stderr={}",
String::from_utf8_lossy(&accepted.stderr)
);
}
#[test]
#[ignore = "batch resolves dependencies before parent verification settles"]
fn batch_dependent_waits_for_parent_verify() {
let harness = Harness::new();
let (parent, dependent) = ("t-settle-batch-parent", "t-settle-batch-dependent");
let batch = harness.project().join("batch.toml");
std::fs::write(
&batch,
format!(
"[defaults]\nagent = \"{}\"\ndir = \"{}\"\nverify = \"{}\"\n\n\
[[task]]\nid = \"{parent}\"\nname = \"parent\"\nprompt = \"parent\"\n\n\
[[task]]\nid = \"{dependent}\"\nname = \"dependent\"\nprompt = \"dependent\"\n\
depends_on = [\"parent\"]\n",
settlement::AGENT,
harness.project().display(),
harness.verifier().display(),
),
)
.unwrap();
let mut batch_run = harness
.aid()
.arg("batch")
.arg(&batch)
.args(["--parallel", "--yes", "--force"])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
harness.wait_verifier_holding();
thread::sleep(Duration::from_secs(3));
let early = harness.status(dependent);
assert!(
matches!(early.as_deref(), None | Some("waiting") | Some("pending")),
"dependent left its dependency wait before parent verify passed: {early:?}"
);
let batch_status = batch_run.try_wait().unwrap();
assert!(batch_status.is_none(), "batch exited before parent verification settled: {batch_status:?}");
harness.release(0);
harness.release(0);
assert_eq!(harness.wait_terminal(dependent), "done");
let _ = batch_run.kill();
let _ = batch_run.wait();
}