use ev::receipt::{append, Receipt};
use ev::store::Store;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
fn ev() -> Command {
Command::new(env!("CARGO_BIN_EXE_ev"))
}
fn repo() -> std::path::PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let p = std::env::temp_dir().join(format!(
"ev-resurface-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::Relaxed)
));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
assert!(ev()
.arg("init")
.current_dir(&p)
.output()
.unwrap()
.status
.success());
p
}
fn decide_bound(repo: &std::path::Path) -> String {
let out = ev()
.args([
"decide",
"no-Redis posture",
"--assume",
"no Redis; multi-pod via existing DB",
"--assume-test",
"pytest x",
"--counter-test",
"pytest x::flips",
"--on-platform",
"linux-ci",
"--triggered-by",
"pyproject.toml",
"--surface",
"pyproject-deps",
"--verified-at-sha",
"d308afac1b2c3d4e5f60718293a4b5c6d7e8f901",
"--blame",
"Wang Yu",
])
.current_dir(repo)
.output()
.unwrap();
assert!(
out.status.success(),
"decide failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout)
.split_whitespace()
.nth(1)
.unwrap()
.to_string()
}
fn rcpt(ran_at: &str, result: &str) -> Receipt {
Receipt {
test: "pytest x".into(),
platform: "linux-ci".into(),
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
ran_at: ran_at.into(),
result: result.into(),
falsifiable: None,
}
}
fn disable_age_staleness(repo: &std::path::Path) {
let s = Store::at(repo);
let cfg = std::fs::read_to_string(s.config_path())
.unwrap()
.replace("staleness_days = 7", "staleness_days = 3650000");
std::fs::write(s.config_path(), cfg).unwrap();
}
#[test]
fn check_should_report_not_run_and_gate_when_no_receipt_exists() {
let r = repo();
decide_bound(&r);
let plain = ev().arg("check").current_dir(&r).output().unwrap();
let gated = ev()
.args(["check", "--exit-on-red"])
.current_dir(&r)
.output()
.unwrap();
assert!(plain.status.success());
assert!(String::from_utf8_lossy(&plain.stdout).contains("not-run"));
assert!(!gated.status.success());
}
#[test]
fn check_should_report_green_and_pass_when_a_green_receipt_covers_the_platform() {
let r = repo();
decide_bound(&r);
disable_age_staleness(&r);
append(&Store::at(&r), &rcpt("2026-01-01T00:00:00Z", "green")).unwrap();
let out = ev()
.args(["check", "--exit-on-red"])
.current_dir(&r)
.output()
.unwrap();
assert!(out.status.success());
assert!(String::from_utf8_lossy(&out.stdout).contains("green"));
}
#[test]
fn check_should_go_red_and_gate_when_the_latest_receipt_is_red() {
let r = repo();
decide_bound(&r);
disable_age_staleness(&r);
let s = Store::at(&r);
append(&s, &rcpt("2026-01-01T00:00:00Z", "green")).unwrap();
append(&s, &rcpt("2026-02-01T00:00:00Z", "red")).unwrap();
let out = ev()
.args(["check", "--exit-on-red"])
.current_dir(&r)
.output()
.unwrap();
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stdout).contains("red"));
}
#[test]
fn check_should_promote_gray_to_red_when_the_receipt_is_gray() {
let r = repo();
decide_bound(&r);
disable_age_staleness(&r);
append(&Store::at(&r), &rcpt("2026-01-01T00:00:00Z", "gray")).unwrap();
let out = ev()
.args(["check", "--exit-on-red"])
.current_dir(&r)
.output()
.unwrap();
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stdout).contains("gray->red"));
}
#[test]
fn deleting_results_should_leave_the_tick_id_unchanged_when_the_chain_is_rebuilt() {
let r = repo();
let id = decide_bound(&r);
append(&Store::at(&r), &rcpt("2026-01-01T00:00:00Z", "green")).unwrap();
std::fs::remove_dir_all(r.join(".evolving/results")).unwrap();
let shown = ev().args(["show", &id]).current_dir(&r).output().unwrap();
assert!(shown.status.success());
assert!(String::from_utf8_lossy(&shown.stdout).contains(&id));
}