use std::collections::BTreeSet;
use std::path::PathBuf;
use std::process::Command;
fn pv_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_pv"))
}
fn pv(args: &[&str]) -> (i32, String) {
pv_in(std::env::current_dir().expect("cwd").as_path(), args)
}
fn pv_in(dir: &std::path::Path, args: &[&str]) -> (i32, String) {
let out = Command::new(pv_bin())
.current_dir(dir)
.args(args)
.output()
.expect("failed to spawn pv");
let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&out.stderr));
(out.status.code().unwrap_or(-1), text)
}
fn panicked(rc: i32, out: &str) -> bool {
rc == 101 || rc == -1 || out.contains("panicked at")
}
struct Fixture {
metadata: String,
equations: String,
obligations: String,
ftests: String,
harnesses: String,
qa_gate: String,
extra: String,
}
const METADATA_OK: &str = "metadata:\n version: \"1.0.0\"\n kind: kernel\n \
description: \"pv surface gate fixture\"\n references:\n - \"#2589\"\n";
const EQUATIONS_OK: &str = "equations:\n probe_eq:\n formula: \"y = x\"\n";
const OBLIGATION_OK: &str =
" - type: invariant\n property: \"P\"\n formal: \"F-OK\"\n applies_to: all\n";
const FTEST_OK: &str = " - id: FALSIFY-PVGATE-001\n prediction: \"p\"\n if_fails: \"f\"\n";
const HARNESS_OK: &str =
" - id: KANI-PVGATE-001\n obligation: OB-1\n property: \"P\"\n bound: 8\n";
const QA_GATE_OK: &str = "qa_gate:\n id: F-PVGATE-001\n name: \"pv gate\"\n \
checks:\n - \"c\"\n pass_criteria: \"all\"\n";
impl Default for Fixture {
fn default() -> Self {
Self {
metadata: METADATA_OK.to_string(),
equations: EQUATIONS_OK.to_string(),
obligations: OBLIGATION_OK.to_string(),
ftests: FTEST_OK.to_string(),
harnesses: HARNESS_OK.to_string(),
qa_gate: QA_GATE_OK.to_string(),
extra: String::new(),
}
}
}
impl Fixture {
fn render(&self) -> String {
format!(
"{}\n{}\nproof_obligations:\n{}\nfalsification_tests:\n{}\n\
kani_harnesses:\n{}\n{}{}",
self.metadata,
self.equations,
self.obligations,
self.ftests,
self.harnesses,
self.qa_gate,
self.extra,
)
}
fn validate(&self) -> (i32, String) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("fixture-v1.yaml");
std::fs::write(&path, self.render()).expect("write fixture");
pv(&["validate", path.to_str().expect("utf8 path")])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Sev {
Error,
Warn,
}
impl Sev {
fn tag(self) -> &'static str {
match self {
Sev::Error => "[ERROR]",
Sev::Warn => "[WARN]",
}
}
fn expected_rc(self) -> i32 {
match self {
Sev::Error => 1,
Sev::Warn => 0,
}
}
}
struct Case {
rule: &'static str,
sev: Sev,
build: fn(&mut Fixture),
}
const CASES: &[Case] = &[
Case {
rule: "SCHEMA-001",
sev: Sev::Error,
build: |f| {
f.metadata = "metadata:\n version: \"1.0.0\"\n kind: kernel\n \
description: \"d\"\n references: []\n"
.to_string();
},
},
Case {
rule: "SCHEMA-002",
sev: Sev::Error,
build: |f| {
f.metadata = "metadata:\n version: \"\"\n kind: kernel\n \
description: \"d\"\n references:\n - \"r\"\n"
.to_string();
},
},
Case {
rule: "SCHEMA-003",
sev: Sev::Error,
build: |f| f.equations = "equations: {}\n".to_string(),
},
Case {
rule: "SCHEMA-004",
sev: Sev::Error,
build: |f| {
f.equations = "equations:\n probe_eq:\n formula: \"\"\n".to_string();
},
},
Case {
rule: "SCHEMA-005",
sev: Sev::Error,
build: |f| {
f.obligations = " - type: invariant\n property: \"\"\n \
formal: \"F-OK\"\n applies_to: all\n"
.to_string();
},
},
Case {
rule: "SCHEMA-007",
sev: Sev::Error,
build: |f| {
f.ftests = format!(
"{FTEST_OK} - id: FALSIFY-PVGATE-001\n prediction: \"q\"\n \
if_fails: \"f\"\n"
);
},
},
Case {
rule: "SCHEMA-008",
sev: Sev::Error,
build: |f| {
f.ftests = " - id: FALSIFY-PVGATE-001\n prediction: \"\"\n \
if_fails: \"f\"\n"
.to_string();
},
},
Case {
rule: "SCHEMA-009",
sev: Sev::Warn,
build: |f| {
f.ftests = " - id: FALSIFY-PVGATE-001\n prediction: \"p\"\n \
if_fails: \"\"\n"
.to_string();
},
},
Case {
rule: "SCHEMA-010",
sev: Sev::Error,
build: |f| {
f.harnesses = format!(
"{HARNESS_OK} - id: KANI-PVGATE-001\n obligation: OB-2\n \
property: \"Q\"\n bound: 8\n"
);
},
},
Case {
rule: "SCHEMA-011",
sev: Sev::Error,
build: |f| {
f.harnesses = " - id: KANI-PVGATE-001\n obligation: \"\"\n \
property: \"P\"\n bound: 8\n"
.to_string();
},
},
Case {
rule: "SCHEMA-012",
sev: Sev::Warn,
build: |f| {
f.harnesses =
" - id: KANI-PVGATE-001\n obligation: OB-1\n property: \"P\"\n".to_string();
},
},
Case {
rule: "SCHEMA-013",
sev: Sev::Warn,
build: |f| f.qa_gate = String::new(),
},
Case {
rule: "SCHEMA-014",
sev: Sev::Error,
build: |f| {
f.obligations = format!("{OBLIGATION_OK} requires: \"x > 0\"\n");
},
},
Case {
rule: "SCHEMA-015",
sev: Sev::Error,
build: |f| {
f.obligations = format!("{OBLIGATION_OK} applies_to_phase: \"phase1\"\n");
},
},
Case {
rule: "SCHEMA-016",
sev: Sev::Error,
build: |f| {
f.obligations = format!("{OBLIGATION_OK} parent_contract: \"other-v1\"\n");
},
},
Case {
rule: "SCHEMA-017",
sev: Sev::Error,
build: |f| {
f.obligations = " - type: subcontract\n property: \"P\"\n \
formal: \"F-OK\"\n applies_to: all\n \
parent_contract: \"not-listed-v1\"\n"
.to_string();
},
},
Case {
rule: "SCHEMA-018",
sev: Sev::Error,
build: |f| f.extra = "kind: KernelContract\n".to_string(),
},
Case {
rule: "SCHEMA-019",
sev: Sev::Error,
build: |f| {
f.extra = "falsification_test:\n - id: FALSIFY-PVGATE-002\n \
prediction: \"invisible to every pv gate\"\n if_fails: \"f\"\n"
.to_string();
},
},
Case {
rule: "SCHEMA-020",
sev: Sev::Error,
build: |f| {
f.extra = "commands:\n - name: probe\n subcommands: [parse]\n \
subcommands: [parse, render]\n"
.to_string();
},
},
Case {
rule: "PROVABILITY-001",
sev: Sev::Error,
build: |f| {
f.obligations = format!(
"{OBLIGATION_OK} - type: bound\n property: \"Q\"\n \
formal: \"F-2\"\n applies_to: all\n"
);
},
},
Case {
rule: "CRUX-001",
sev: Sev::Error,
build: |f| {
f.metadata = format!("{METADATA_OK} demand_score: 99999\n");
},
},
Case {
rule: "CRUX-002",
sev: Sev::Error,
build: |f| {
f.metadata = format!("{METADATA_OK} competitor: \"THIS-COMPETITOR-DOES-NOT-EXIST\"\n");
},
},
];
const BEAT_BLOCK_OK: &str = "beat:\n pillar: 1\n incumbent: scikit-learn\n \
metric: wall_clock_ratio\n direction: lower_is_better\n \
beat_threshold: 0.9\n approved_compute: CPU\n \
ci_gate_name: \"beat_pv_surface_gate\"\n";
fn beat_fixture(beat_block: &str) -> String {
format!(
"metadata:\n kind: beat-benchmark\n version: \"1.0.0\"\n \
description: \"pv surface gate beat fixture\"\n references:\n - \"#2589\"\n\
\n{EQUATIONS_OK}\n{beat_block}"
)
}
fn validate_beat(beat_block: &str) -> (i32, String) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("beat-fixture-v1.yaml");
std::fs::write(&path, beat_fixture(beat_block)).expect("write beat fixture");
pv(&["validate", path.to_str().expect("utf8 path")])
}
struct BeatCase {
rule: &'static str,
block: &'static str,
}
const BEAT_CASES: &[BeatCase] = &[
BeatCase {
rule: "BEAT-001",
block: "",
},
BeatCase {
rule: "BEAT-002",
block: "beat:\n pillar: 1\n incumbent: \"\"\n metric: m\n \
direction: lower_is_better\n beat_threshold: 0.9\n \
approved_compute: CPU\n ci_gate_name: \"g\"\n",
},
BeatCase {
rule: "BEAT-002",
block: "beat:\n pillar: 1\n incumbent: THIS-INCUMBENT-DOES-NOT-EXIST\n \
metric: m\n direction: lower_is_better\n beat_threshold: 0.9\n \
approved_compute: CPU\n ci_gate_name: \"g\"\n",
},
BeatCase {
rule: "BEAT-003",
block: "beat:\n pillar: 1\n incumbent: scikit-learn\n metric: \" \"\n \
direction: lower_is_better\n beat_threshold: 0.9\n \
approved_compute: CPU\n ci_gate_name: \"g\"\n",
},
BeatCase {
rule: "BEAT-004",
block: "beat:\n pillar: 1\n incumbent: scikit-learn\n metric: m\n \
direction: sideways_is_better\n beat_threshold: 0.9\n \
approved_compute: CPU\n ci_gate_name: \"g\"\n",
},
BeatCase {
rule: "BEAT-005",
block: "beat:\n pillar: 1\n incumbent: scikit-learn\n metric: m\n \
direction: lower_is_better\n approved_compute: CPU\n \
ci_gate_name: \"g\"\n",
},
BeatCase {
rule: "BEAT-006",
block: "beat:\n pillar: 1\n incumbent: scikit-learn\n metric: m\n \
direction: lower_is_better\n beat_threshold: 0.9\n \
approved_compute: CPU\n ci_gate_name: \"\"\n",
},
BeatCase {
rule: "BEAT-007",
block: "beat:\n pillar: 1\n incumbent: scikit-learn\n metric: m\n \
direction: lower_is_better\n beat_threshold: 0.9\n \
approved_compute: TPU\n ci_gate_name: \"g\"\n",
},
];
const ALSO_ABSENT_FROM_BASELINE: &[&str] = &["SCHEMA-006"];
fn all_rule_ids() -> BTreeSet<&'static str> {
CASES
.iter()
.map(|c| c.rule)
.chain(BEAT_CASES.iter().map(|c| c.rule))
.chain(ALSO_ABSENT_FROM_BASELINE.iter().copied())
.collect()
}
#[test]
fn validate_baseline_is_silent_and_exits_zero() {
let (rc, out) = Fixture::default().validate();
assert_eq!(
rc, 0,
"clean fixture must exit 0.\n--- pv output ---\n{out}"
);
assert!(
out.contains("0 error(s), 0 warning(s)"),
"clean fixture must report zero of both.\n--- pv output ---\n{out}"
);
for rule in all_rule_ids() {
assert!(
!out.contains(rule),
"clean fixture tripped {rule} — either the fixture is not clean or \
pv reports rules unconditionally.\n--- pv output ---\n{out}"
);
}
}
#[test]
fn validate_reaches_every_rule_at_the_right_severity() {
let mut failures = Vec::new();
for case in CASES {
let mut fixture = Fixture::default();
(case.build)(&mut fixture);
let (rc, out) = fixture.validate();
let expected_line = format!("{} {}:", case.sev.tag(), case.rule);
if !out.contains(&expected_line) {
failures.push(format!(
"{}: expected a `{}` line, got:\n{}",
case.rule,
expected_line.trim_end_matches(':'),
indent(&out)
));
continue;
}
if rc != case.sev.expected_rc() {
failures.push(format!(
"{}: reported at {:?} so pv must exit {}, got rc={rc}:\n{}",
case.rule,
case.sev,
case.sev.expected_rc(),
indent(&out)
));
}
}
assert!(
failures.is_empty(),
"{} of {} validate rules are not reachable through the CLI:\n\n{}",
failures.len(),
CASES.len(),
failures.join("\n")
);
}
fn declared_rule_ids() -> BTreeSet<String> {
let src = include_str!("../../aprender-contracts/src/schema/validator.rs");
let mut ids = BTreeSet::new();
for chunk in src.split('"').skip(1).step_by(2) {
if is_rule_id_shaped(chunk) {
ids.insert(chunk.to_string());
}
}
ids
}
fn is_rule_id_shaped(s: &str) -> bool {
let Some((family, num)) = s.rsplit_once('-') else {
return false;
};
num.len() == 3
&& num.chars().all(|c| c.is_ascii_digit())
&& !family.is_empty()
&& family
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '-')
}
#[test]
fn validate_reaches_every_beat_rule() {
let (rc, out) = validate_beat(BEAT_BLOCK_OK);
assert_eq!(
rc,
0,
"the clean beat-benchmark fixture must validate.\n{}",
indent(&out)
);
for id in all_rule_ids() {
assert!(
!out.contains(id),
"clean beat fixture tripped {id}:\n{}",
indent(&out)
);
}
let mut failures = Vec::new();
for case in BEAT_CASES {
let (rc, out) = validate_beat(case.block);
let expected = format!("[ERROR] {}:", case.rule);
if !out.contains(&expected) {
failures.push(format!(
"{}: expected `{}` line, got:\n{}",
case.rule,
expected.trim_end_matches(':'),
indent(&out)
));
} else if rc == 0 {
failures.push(format!(
"{}: reported as an ERROR but pv still exited 0:\n{}",
case.rule,
indent(&out)
));
}
}
assert!(
failures.is_empty(),
"{} of {} BEAT rules are not reachable through the CLI:\n\n{}",
failures.len(),
BEAT_CASES.len(),
failures.join("\n")
);
}
#[test]
fn every_rule_in_the_validator_source_appears_in_the_table() {
let declared = declared_rule_ids();
assert!(
declared.len() >= 25,
"parsed only {} rule ids out of validator.rs — the extractor broke, \
which would make this guard vacuously green. Found: {declared:?}",
declared.len()
);
let covered = all_rule_ids();
let missing: Vec<_> = declared
.iter()
.filter(|d| !covered.contains(d.as_str()))
.collect();
assert!(
missing.is_empty(),
"validator.rs declares rules with no row in the pv-surface decision \
table: {missing:?}. Add a Case (or ALSO_ABSENT_FROM_BASELINE) so the \
new rule is gated through the CLI, not only in library unit tests."
);
}
fn advertised_subcommands() -> Vec<String> {
let (rc, out) = pv(&["--help"]);
assert_eq!(rc, 0, "`pv --help` must exit 0, got {rc}:\n{out}");
let cmds: Vec<String> = commands_section(&out)
.filter_map(subcommand_name)
.map(str::to_string)
.collect();
assert!(
cmds.len() >= 30,
"parsed only {} subcommands out of `pv --help` — the parser broke and \
every test built on it would be vacuously green:\n{out}",
cmds.len()
);
cmds
}
fn commands_section(help: &str) -> impl Iterator<Item = &str> {
help.lines()
.skip_while(|l| !l.starts_with("Commands:"))
.skip(1)
.take_while(|l| !l.starts_with("Options:"))
}
fn subcommand_name(line: &str) -> Option<&str> {
if !line.starts_with(" ") || line.starts_with(" ") {
return None;
}
let word = line.split_whitespace().next()?;
let plausible = word
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-');
(word != "help" && plausible).then_some(word)
}
fn contract_taking_subcommands() -> Vec<String> {
let mut found = Vec::new();
for cmd in advertised_subcommands() {
let (_, help) = pv(&[&cmd, "--help"]);
let Some(usage) = help.lines().find(|l| l.starts_with("Usage:")) else {
continue;
};
if usage.contains("<CONTRACT>") || usage.contains("<PATH>") || usage.contains("<FILE>") {
found.push(cmd);
}
}
found
}
fn valid_fixture() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("fixture-v1.yaml");
std::fs::write(&path, Fixture::default().render()).expect("write fixture");
(dir, path)
}
const VALID_INPUT_NONZERO: &[(&str, i32, &str)] = &[
(
"check-parity",
1,
"needs a parity-matrix contract; a kernel contract has no cross_check_command \
rows to execute, and refusing is the correct answer",
),
(
"unlock",
2,
"clap requires `--reason`, so a bare `<CONTRACT>` is a usage error (exit 2), \
reported by clap before the command body runs",
),
];
#[test]
fn every_advertised_subcommand_is_reachable() {
let (scratch, fixture) = valid_fixture();
let fixture = fixture.to_str().expect("utf8 path").to_string();
let mut broken = Vec::new();
let mut invoked = Vec::new();
for cmd in advertised_subcommands() {
let (rc, out) = pv(&[&cmd, "--help"]);
if rc != 0 {
broken.push(format!("pv {cmd} --help -> rc={rc}\n{}", indent(&out)));
}
}
for cmd in contract_taking_subcommands() {
invoked.push(cmd.clone());
let (rc, out) = pv_in(scratch.path(), &[&cmd, &fixture]);
if panicked(rc, &out) {
broken.push(format!(
"pv {cmd} <valid contract> PANICKED (rc={rc}) -- it never returned a \
decision:\n{}",
indent(&out)
));
continue;
}
let expected = VALID_INPUT_NONZERO
.iter()
.find(|(name, _, _)| *name == cmd)
.map_or(0, |(_, code, _)| *code);
if rc != expected {
broken.push(format!(
"pv {cmd} <valid contract> -> rc={rc}, expected {expected}:\n{}",
indent(&out)
));
}
}
assert!(
invoked.len() >= 15,
"only {} contract-taking subcommand(s) were INVOKED for real; the \
usage-line parser probably broke, which would silently return this \
test to the vacuous form it was written to replace. Found: {invoked:?}",
invoked.len()
);
for (name, _, why) in VALID_INPUT_NONZERO {
assert!(
invoked.iter().any(|c| c == name),
"VALID_INPUT_NONZERO names `{name}` ({why}) but no such contract-taking \
subcommand was discovered -- the exception is dead and excuses nothing"
);
}
assert!(
broken.is_empty(),
"{} advertised subcommand(s) are not usable:\n{}",
broken.len(),
broken.join("\n")
);
}
#[test]
fn contract_taking_subcommands_reject_unusable_input() {
let dir = tempfile::tempdir().expect("tempdir");
let missing = dir.path().join("does-not-exist.yaml");
let garbage = dir.path().join("garbage.yaml");
std::fs::write(&garbage, "{{{ not yaml at all: [\n").expect("write");
let empty = dir.path().join("empty.yaml");
std::fs::write(&empty, "").expect("write");
let wrong_shape = dir.path().join("wrong-shape.yaml");
std::fs::write(&wrong_shape, "hello: world\nlist:\n - 1\n").expect("write");
let inputs = [
("nonexistent", &missing),
("malformed-yaml", &garbage),
("empty", &empty),
("well-formed-but-not-a-contract", &wrong_shape),
];
let mut checked = 0usize;
let mut bad = Vec::new();
for cmd in contract_taking_subcommands() {
checked += 1;
for (label, path) in &inputs {
let (rc, out) = pv_in(dir.path(), &[&cmd, path.to_str().expect("utf8 path")]);
if rc == 0 {
bad.push(format!(
"pv {cmd} <{label}> exited 0 -- it accepted unusable input:\n{}",
indent(&out)
));
} else if panicked(rc, &out) {
bad.push(format!(
"pv {cmd} <{label}> PANICKED (rc={rc}) -- a crash is not a \
rejection, it is the absence of a decision:\n{}",
indent(&out)
));
} else if rc != 1 && rc != 2 {
bad.push(format!(
"pv {cmd} <{label}> -> rc={rc}; a clean refusal is 1 (the \
command's own) or 2 (clap usage):\n{}",
indent(&out)
));
}
}
}
assert!(
checked >= 15,
"only {checked} contract-taking subcommands were found; the usage-line \
parser probably broke, which would make this guard vacuously green"
);
assert!(
bad.is_empty(),
"{} subcommand/input pair(s) did not cleanly reject input they cannot \
possibly process:\n{}",
bad.len(),
bad.join("\n")
);
}
#[test]
fn version_reports_this_crates_version() {
let (rc, out) = pv(&["--version"]);
assert_eq!(rc, 0, "`pv --version` must exit 0, got {rc}:\n{out}");
assert!(
out.contains(env!("CARGO_PKG_VERSION")),
"`pv --version` printed {out:?} which does not contain this crate's \
version {:?}",
env!("CARGO_PKG_VERSION")
);
}
#[test]
fn gate_self_test_rule_output_depends_on_input() {
let (_, baseline) = Fixture::default().validate();
let baseline_rules: BTreeSet<&str> = all_rule_ids()
.into_iter()
.filter(|r| baseline.contains(r))
.collect();
assert!(
baseline_rules.is_empty(),
"a `pv` that prints rules unconditionally would pass every trip case; \
the baseline must print none, saw {baseline_rules:?}"
);
let mut tripped: BTreeSet<&str> = BTreeSet::new();
for case in CASES {
let mut fixture = Fixture::default();
(case.build)(&mut fixture);
let (_, out) = fixture.validate();
if out.contains(case.rule) {
tripped.insert(case.rule);
}
}
assert_eq!(
tripped.len(),
CASES.len(),
"a `pv` that prints no rules would pass the baseline case; {} of {} \
trip cases produced no rule id. Reached: {tripped:?}",
CASES.len() - tripped.len(),
CASES.len()
);
}
fn indent(s: &str) -> String {
s.lines()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
}