use std::process::Command;
const REGISTRY: &[(i32, &str)] = &[
(0, "All tests passed"),
(1, "One or more tests failed"),
(2, "Configuration / user error"),
(3, "Infra / judge unavailable"),
(4, "Would block (dry-run sandbox)"),
];
#[test]
fn the_numbers_are_what_the_module_defines() {
use assay_cli_exit_codes::*;
assert_eq!(EXIT_SUCCESS, 0);
assert_eq!(EXIT_TEST_FAILURE, 1);
assert_eq!(EXIT_CONFIG_ERROR, 2);
assert_eq!(EXIT_INFRA_ERROR, 3);
assert_eq!(EXIT_WOULD_BLOCK, 4);
let mut seen = std::collections::BTreeSet::new();
for (code, meaning) in REGISTRY {
assert!(
seen.insert(*code),
"exit code {code} is used twice; a consumer cannot distinguish {meaning}"
);
}
}
#[allow(non_snake_case)]
mod assay_cli_exit_codes {
pub const EXIT_SUCCESS: i32 = 0;
pub const EXIT_TEST_FAILURE: i32 = 1;
pub const EXIT_CONFIG_ERROR: i32 = 2;
pub const EXIT_INFRA_ERROR: i32 = 3;
pub const EXIT_WOULD_BLOCK: i32 = 4;
}
fn workspace_file(rel: &str) -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.canonicalize()
.expect("workspace root")
.join(rel);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}
#[test]
fn the_module_and_the_normative_table_agree() {
let spec = workspace_file("docs/architecture/SPEC-PR-Gate-Outputs-v1.md");
let section = spec
.split("## 4. Exit Code Registry")
.nth(1)
.expect("the spec still has an exit code registry")
.split("\n## ")
.next()
.expect("section body");
let mut rows = Vec::new();
for line in section.lines() {
let cells: Vec<&str> = line.trim().trim_matches('|').split('|').collect();
if cells.len() < 2 {
continue;
}
if let Ok(code) = cells[0].trim().parse::<i32>() {
rows.push((code, cells[1].trim().to_string()));
}
}
assert!(
rows.len() >= 4,
"parsed {} registry row(s); the table moved and this check stopped reading it, which is a \
pass that means nothing",
rows.len()
);
for (code, meaning) in &rows {
let (_, ours) = REGISTRY
.iter()
.find(|(c, _)| c == code)
.unwrap_or_else(|| panic!("the spec defines exit {code} ({meaning}) and we do not"));
let spec_first = meaning.split_whitespace().next().unwrap_or_default();
let ours_first = ours.split_whitespace().next().unwrap_or_default();
assert_eq!(
spec_first.to_lowercase(),
ours_first.to_lowercase(),
"exit {code}: spec says {meaning:?}, registry says {ours:?}"
);
}
}
#[test]
fn the_binary_exits_with_the_codes_it_documents() {
let ok = Command::new(env!("CARGO_BIN_EXE_assay"))
.arg("--version")
.output()
.expect("the binary runs");
assert_eq!(
ok.status.code(),
Some(0),
"--version must be exit 0; got {:?}",
ok.status.code()
);
let dir = tempfile::tempdir().expect("tempdir");
let missing = dir.path().join("nope.yaml");
let cfg = Command::new(env!("CARGO_BIN_EXE_assay"))
.args(["run", "--config"])
.arg(&missing)
.output()
.expect("the binary runs");
assert_eq!(
cfg.status.code(),
Some(2),
"a config that cannot load must be exit 2 (config/user error), not {:?}. stderr: {}",
cfg.status.code(),
String::from_utf8_lossy(&cfg.stderr)
);
}