use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
const ALLOWLIST: &[&str] = &[
"STO-1", "STO-3", "STO-10", "STO-12", "STO-20", "STO-21", "STO-22", "STO-23", "STO-30",
"DSC-90",
"LIFE-3", "LIFE-6", "LIFE-21",
"NS-3",
"INIT-8",
"DSC-67",
"CLI-143",
"WF-11", "WF-20",
"STO-45", "STO-57",
"STO-61", "STO-62",
"STO-46",
"TUI-1",
"TUI-3",
"HARN-19",
"VER-45",
];
#[test]
fn every_spec_id_is_cited_or_allowlisted() {
let defined = defined_ids();
assert!(
defined.len() > 50,
"found only {} spec IDs; the parser or spec layout likely changed",
defined.len()
);
let cited = cited_ids();
let allow: BTreeSet<String> = ALLOWLIST.iter().map(|s| s.to_string()).collect();
let undefined: Vec<_> = cited.difference(&defined).cloned().collect();
assert!(
undefined.is_empty(),
"tests cite spec IDs not defined in spec/ (document them): {undefined:?}"
);
let stale: Vec<_> = allow.difference(&defined).cloned().collect();
assert!(
stale.is_empty(),
"ALLOWLIST references unknown spec IDs: {stale:?}"
);
let redundant: Vec<_> = allow.intersection(&cited).cloned().collect();
assert!(
redundant.is_empty(),
"these IDs are now cited by tests; remove them from ALLOWLIST: {redundant:?}"
);
let uncovered: Vec<_> = defined
.iter()
.filter(|id| !cited.contains(*id) && !allow.contains(*id))
.cloned()
.collect();
assert!(
uncovered.is_empty(),
"spec IDs with no test citation (add a test that cites them, or ALLOWLIST them): {uncovered:?}"
);
}
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn is_id(tok: &str) -> bool {
match tok.split_once('-') {
Some((alpha, num)) => {
(2..=4).contains(&alpha.len())
&& alpha.bytes().all(|b| b.is_ascii_uppercase())
&& !num.is_empty()
&& num.bytes().all(|b| b.is_ascii_digit())
}
None => false,
}
}
fn defined_ids() -> BTreeSet<String> {
let mut out = BTreeSet::new();
for md in files_with_ext(&root().join("spec"), "md") {
let text = std::fs::read_to_string(&md).unwrap();
for line in text.lines() {
if let Some(rest) = line.trim_start().strip_prefix("- `")
&& let Some(end) = rest.find('`')
{
let tok = &rest[..end];
if is_id(tok) {
out.insert(tok.to_string());
}
}
}
}
out
}
fn cited_ids() -> BTreeSet<String> {
const MARKER: &str = "// spec:";
let mut out = BTreeSet::new();
let mut sources: Vec<(std::path::PathBuf, bool)> = files_with_ext(&root().join("src"), "rs")
.into_iter()
.map(|f| (f, true))
.collect();
sources.extend(
files_with_ext(&root().join("tests"), "rs")
.into_iter()
.map(|f| (f, false)),
);
for (f, tests_only) in sources {
if f.file_name().is_some_and(|n| n == "spec_coverage.rs") {
continue; }
let text = std::fs::read_to_string(&f).unwrap();
let scanned = if tests_only {
match find_test_module(&text) {
Some(i) => &text[i..],
None => continue,
}
} else {
text.as_str()
};
for line in scanned.lines() {
if let Some(idx) = line.find(MARKER) {
for tok in id_tokens(&line[idx + MARKER.len()..]) {
out.insert(tok);
}
}
}
}
out
}
fn find_test_module(text: &str) -> Option<usize> {
const ATTR: &str = "#[cfg(test)]";
fn line_start(text: &str, at: usize) -> usize {
text[..at].rfind('\n').map(|i| i + 1).unwrap_or(0)
}
fn skip_attr(s: &str) -> Option<usize> {
if !s.starts_with("#[") {
return None;
}
let bytes = s.as_bytes();
let mut depth = 0i32;
let mut j = 0;
while j < bytes.len() {
match bytes[j] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
return Some(j + 1);
}
}
_ => {}
}
j += 1;
}
None
}
fn skip_line_comment(s: &str) -> Option<usize> {
if !s.starts_with("//") {
return None;
}
Some(match s.find('\n') {
Some(nl) => nl + 1,
None => s.len(),
})
}
let mut from = 0;
'outer: while let Some(rel) = text[from..].find(ATTR) {
let at = from + rel;
let prefix = text[line_start(text, at)..at].trim_start();
if prefix.starts_with("//") {
from = at + ATTR.len();
continue;
}
let mut i = at + ATTR.len();
loop {
let rest = &text[i..];
let trimmed = rest.trim_start();
i += rest.len() - trimmed.len();
let rest = &text[i..];
if let Some(n) = skip_attr(rest) {
i += n;
continue;
}
if let Some(n) = skip_line_comment(rest) {
i += n;
continue;
}
break;
}
let mut after = text[i..].trim_start();
if let Some(rest) = after.strip_prefix("pub") {
after = match rest.strip_prefix('(') {
Some(rest) => match rest.find(')') {
Some(p) => rest[p + 1..].trim_start(),
None => {
from = at + ATTR.len();
continue 'outer;
}
},
None => rest.trim_start(),
};
}
if after
.strip_prefix("mod")
.is_some_and(|r| r.starts_with(char::is_whitespace))
{
return Some(at);
}
from = at + ATTR.len();
}
None
}
#[test]
fn find_test_module_recognizes_every_mod_form() {
let cases: &[(&str, &str, Option<&str>)] = &[
(
"plain: #[cfg(test)] mod tests",
"// spec: FOO-1\nfn helper() {}\n#[cfg(test)]\nmod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\nmod tests {\n // spec: BAR-1\n}\n"),
),
(
"pub mod tests",
"fn helper() {}\n#[cfg(test)]\npub mod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\npub mod tests {\n // spec: BAR-1\n}\n"),
),
(
"pub(crate) mod tests",
"fn helper() {}\n#[cfg(test)]\npub(crate) mod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\npub(crate) mod tests {\n // spec: BAR-1\n}\n"),
),
(
"pub(in crate::foo) mod tests",
"fn helper() {}\n#[cfg(test)]\npub(in crate::foo) mod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\npub(in crate::foo) mod tests {\n // spec: BAR-1\n}\n"),
),
(
"an intervening attribute: #[cfg(test)] #[allow(dead_code)] mod tests",
"fn helper() {}\n#[cfg(test)]\n#[allow(dead_code)]\nmod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\n#[allow(dead_code)]\nmod tests {\n // spec: BAR-1\n}\n"),
),
(
"an intervening comment between the attribute and mod",
"fn helper() {}\n#[cfg(test)]\n// why this module exists\nmod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\n// why this module exists\nmod tests {\n // spec: BAR-1\n}\n"),
),
(
"mod on its own line (newline form)",
"fn helper() {}\n#[cfg(test)]\nmod\ntests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\nmod\ntests {\n // spec: BAR-1\n}\n"),
),
(
"a #[cfg(test)] gating a plain fn, not a module -- must be skipped",
"fn helper() {}\n#[cfg(test)]\nfn only_in_tests() {}\n// spec: FOO-2\n",
None,
),
(
"a #[cfg(test)] gating a pub fn, not a module -- must be skipped",
"fn helper() {}\n#[cfg(test)]\npub fn only_in_tests() {}\n// spec: FOO-2\n",
None,
),
(
"a non-module #[cfg(test)] followed later by a real test module",
"#[cfg(test)]\npub fn accessor_for_tests_only() {}\n\n#[cfg(test)]\nmod tests {\n // spec: BAR-1\n}\n",
Some("#[cfg(test)]\nmod tests {\n // spec: BAR-1\n}\n"),
),
(
"no #[cfg(test)] at all",
"fn helper() {}\n// spec: FOO-3\n",
None,
),
(
"the literal text appears only inside a doc comment, never gating real code",
"/// Test-only (`#[cfg(test)]`): it must never be reachable from prod.\nfn helper() {}\n// spec: FOO-4\n",
None,
),
];
for (label, src, expected_tail) in cases {
let got = find_test_module(src);
match expected_tail {
Some(tail) => {
let idx = got.unwrap_or_else(|| {
panic!("case {label:?}: expected a match, got None for input:\n{src}")
});
assert_eq!(
&src[idx..],
*tail,
"case {label:?}: matched at the wrong occurrence"
);
}
None => {
assert_eq!(
got, None,
"case {label:?}: expected no match, got {got:?} for input:\n{src}"
);
}
}
}
}
#[test]
fn find_test_module_region_includes_citations_after_the_match() {
let src = "fn helper() {}\n#[cfg(test)]\npub(crate) mod tests {\n // spec: QUX-9\n}\n";
let at = find_test_module(src).expect("expected a test module to be found");
assert!(
src[at..].contains("QUX-9"),
"the sliced region must still contain the citation after the match point"
);
}
fn id_tokens(text: &str) -> Vec<String> {
let mut out = Vec::new();
for raw in text.split_whitespace() {
let tok = raw.trim_end_matches([',', ';']);
if is_id(tok) {
out.push(tok.to_string());
} else {
break;
}
}
out
}
#[test]
fn id_tokens_takes_the_leading_run_only() {
let ids = |s: &str| id_tokens(s);
assert_eq!(ids(" CLI-10"), ["CLI-10"]);
assert_eq!(ids(" CLI-10 CLI-72"), ["CLI-10", "CLI-72"]);
assert_eq!(ids(" CLI-10, CLI-72"), ["CLI-10", "CLI-72"]);
assert_eq!(
ids(" HOOK-51 (HOOK-3 generalized): blank run entries are skipped"),
["HOOK-51"]
);
assert_eq!(
ids(" CLI-11 (repo spec parsing), CLI-61 (compare url)"),
["CLI-11"]
);
assert_eq!(ids(" see CLI-10").len(), 0);
assert_eq!(ids("").len(), 0);
}
fn files_with_ext(dir: &Path, ext: &str) -> Vec<PathBuf> {
let mut out = Vec::new();
let Ok(rd) = std::fs::read_dir(dir) else {
return out;
};
for entry in rd.flatten() {
let path = entry.path();
if path.is_dir() {
out.extend(files_with_ext(&path, ext));
} else if path.extension().is_some_and(|e| e == ext) {
out.push(path);
}
}
out
}