use std::fs;
use std::path::{Path, PathBuf};
fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("workspace root")
.to_path_buf()
}
fn extract_required_tests(contents: &str) -> Vec<String> {
let mut out = Vec::new();
let mut in_qa_gate = false;
let mut in_required_tests = false;
for line in contents.lines() {
let trimmed_start = line.trim_start();
if trimmed_start.is_empty() {
continue;
}
if !line.starts_with(char::is_whitespace) && trimmed_start.starts_with("qa_gate:") {
in_qa_gate = true;
in_required_tests = false;
continue;
}
if !line.starts_with(char::is_whitespace)
&& in_qa_gate
&& !trimmed_start.starts_with("qa_gate:")
{
in_qa_gate = false;
in_required_tests = false;
continue;
}
if !in_qa_gate {
continue;
}
if trimmed_start.starts_with("required_tests:") {
in_required_tests = true;
continue;
}
if in_required_tests && !trimmed_start.starts_with('-') {
in_required_tests = false;
continue;
}
if in_required_tests {
if let Some(name) = trimmed_start.strip_prefix("- ") {
let name = name.trim();
let name = name.trim_matches('"').trim_matches('\'').to_string();
if !name.is_empty() {
out.push(name);
}
}
}
}
out
}
fn collect_test_function_names(root: &Path) -> Vec<String> {
let mut names = Vec::new();
let crates_dir = root.join("crates");
let Ok(crates) = fs::read_dir(&crates_dir) else {
return names;
};
for c in crates.flatten() {
let cp = c.path();
if !cp.is_dir() {
continue;
}
let tests_dir = cp.join("tests");
if tests_dir.is_dir() {
walk_rs_for_tests(&tests_dir, &mut names);
}
let src_dir = cp.join("src");
if src_dir.is_dir() {
walk_rs_for_tests(&src_dir, &mut names);
}
}
names.sort();
names.dedup();
names
}
fn walk_rs_for_tests(dir: &Path, names: &mut Vec<String>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
walk_rs_for_tests(&p, names);
continue;
}
if p.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let Ok(src) = fs::read_to_string(&p) else {
continue;
};
let mut seen_test_attr = false;
for line in src.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with("#[test]") {
seen_test_attr = true;
continue;
}
if seen_test_attr {
let after_pub = trimmed.strip_prefix("pub ").unwrap_or(trimmed);
if let Some(rest) = after_pub.strip_prefix("fn ") {
if let Some(end) = rest.find(|c: char| !c.is_alphanumeric() && c != '_') {
names.push(rest[..end].to_string());
}
}
seen_test_attr = false;
}
}
}
}
#[test]
fn every_qa_gate_required_test_exists_as_a_real_test_fn() {
let root = workspace_root();
let contracts_dir = root.join("contracts");
let test_fns = collect_test_function_names(&root);
assert!(
test_fns.len() >= 10,
"expected at least 10 #[test] fns to be collected; \
test discovery probably broke (found {})",
test_fns.len()
);
let entries = fs::read_dir(&contracts_dir).expect("read contracts/");
let mut checked = 0;
let mut missing: Vec<(PathBuf, String)> = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("yaml") {
continue;
}
let contents = fs::read_to_string(&path).expect("read contract yaml");
let required = extract_required_tests(&contents);
for name in required {
if !test_fns.iter().any(|t| t == &name) {
missing.push((path.clone(), name));
} else {
checked += 1;
}
}
}
assert!(
missing.is_empty(),
"qa_gate required_tests reference {} test fn(s) that don't exist: {:#?}",
missing.len(),
missing
);
assert!(
checked >= 2,
"expected at least 2 qa_gate required_tests to be checked; \
the qa_gate blocks themselves may have regressed (checked {})",
checked
);
}
#[test]
fn extract_required_tests_parses_the_common_yaml_shape() {
let yaml = r#"
metadata:
id: C-FOO
version: "1.0.0"
equations: {}
qa_gate:
id: QA-FOO
name: "test contract"
min_coverage: 0.50
max_complexity: 20
required_tests:
- test_alpha
- test_beta
- "test_gamma"
"#;
let got = extract_required_tests(yaml);
assert_eq!(got, vec!["test_alpha", "test_beta", "test_gamma"]);
}
#[test]
fn extract_required_tests_returns_empty_when_no_qa_gate() {
let yaml = r#"
metadata:
id: C-FOO
equations: {}
"#;
assert!(extract_required_tests(yaml).is_empty());
}