use std::collections::{BTreeMap, BTreeSet};
const GAP_PREFIX: &str = "GAP-SG-";
const ENTRY_HEADING: &str = "## GAP-SG-";
const STATUS_MARKER: &str = "- Status:";
const RELEASE_HEADING: &str = "## [";
fn read_repo_file(relative: &str) -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()))
}
fn gap_ids_in_order(text: &str) -> Vec<String> {
let mut found = Vec::new();
let bytes = text.as_bytes();
let mut cursor = 0usize;
while let Some(hit) = text[cursor..].find(GAP_PREFIX) {
let start = cursor + hit;
let digits_at = start + GAP_PREFIX.len();
let mut end = digits_at;
while end < bytes.len() && bytes[end].is_ascii_digit() {
end += 1;
}
if end > digits_at {
let id = text[start..end].to_string();
if !found.contains(&id) {
found.push(id);
}
}
cursor = end.max(start + GAP_PREFIX.len());
}
found
}
fn gap_ids(text: &str) -> BTreeSet<String> {
gap_ids_in_order(text).into_iter().collect()
}
fn heading_id(line: &str) -> Option<String> {
gap_ids_in_order(line).into_iter().next()
}
#[test]
fn a_heading_declares_the_gap_it_names_first() {
assert_eq!(
heading_id("## GAP-SG-159 — a regra derivada do GAP-SG-149 não tem gate").as_deref(),
Some("GAP-SG-159")
);
}
fn newest_release_section(changelog: &str) -> &str {
let start = changelog
.find(RELEASE_HEADING)
.expect("CHANGELOG.md carries no release section");
let rest = &changelog[start..];
match rest[RELEASE_HEADING.len()..].find(RELEASE_HEADING) {
Some(offset) => &rest[..RELEASE_HEADING.len() + offset],
None => rest,
}
}
fn declared_status(gaps: &str) -> BTreeMap<String, String> {
let mut statuses = BTreeMap::new();
let mut current: Option<String> = None;
for line in gaps.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with(ENTRY_HEADING) {
current = heading_id(trimmed);
continue;
}
if let (Some(id), Some(rest)) = (current.as_ref(), trimmed.strip_prefix(STATUS_MARKER)) {
statuses
.entry(id.clone())
.or_insert_with(|| rest.trim().to_string());
}
}
statuses
}
fn is_outstanding(status: &str) -> bool {
let upper = status.to_uppercase();
let verdict = upper.trim_start();
verdict.starts_with("ABERTO") || verdict.starts_with("PARCIAL")
}
#[test]
fn the_outstanding_verdict_reads_only_the_leading_word() {
assert!(is_outstanding("ABERTO"));
assert!(is_outstanding("PARCIAL — resíduo aberto em GAP-SG-157"));
assert!(!is_outstanding("RESOLVIDO na v1.2.2 com resíduo aberto"));
assert!(!is_outstanding("RESOLVIDO na v1.2.2 e verificado"));
assert!(!is_outstanding("FECHADO COMO NÃO APLICÁVEL"));
}
#[test]
fn every_entry_declares_a_status() {
let gaps = read_repo_file("gaps.md");
let statuses = declared_status(&gaps);
let headings: BTreeSet<String> = gaps
.lines()
.filter(|l| l.trim_start().starts_with(ENTRY_HEADING))
.filter_map(heading_id)
.collect();
let missing: Vec<&String> = headings
.iter()
.filter(|id| !statuses.contains_key(*id))
.collect();
assert!(
missing.is_empty(),
"these gaps.md entries carry no `- Status:` line: {missing:?}"
);
}
#[test]
fn the_newest_release_never_claims_an_outstanding_gap() {
let changelog = read_repo_file("CHANGELOG.md");
let gaps = read_repo_file("gaps.md");
let statuses = declared_status(&gaps);
let claimed = gap_ids(newest_release_section(&changelog));
let contradictions: Vec<String> = claimed
.iter()
.filter_map(|id| {
let status = statuses.get(id)?;
is_outstanding(status).then(|| format!("{id} is `{status}` in gaps.md"))
})
.collect();
assert!(
contradictions.is_empty(),
"the newest CHANGELOG section credits gaps that gaps.md still reports as outstanding.\n\
Either flip the status in gaps.md or stop crediting the gap here.\n{}",
contradictions.join("\n")
);
}
#[test]
fn every_gap_credited_in_the_newest_release_exists_in_gaps_md() {
let changelog = read_repo_file("CHANGELOG.md");
let gaps = read_repo_file("gaps.md");
let known: BTreeSet<String> = declared_status(&gaps).into_keys().collect();
let claimed = gap_ids(newest_release_section(&changelog));
let unknown: Vec<&String> = claimed.iter().filter(|id| !known.contains(*id)).collect();
assert!(
unknown.is_empty(),
"the newest CHANGELOG section cites identifiers absent from gaps.md: {unknown:?}"
);
}
fn source_files() -> Vec<std::path::PathBuf> {
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let mut entries: Vec<_> = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display()))
.filter_map(Result::ok)
.map(|e| e.path())
.collect();
entries.sort();
for path in entries {
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut out = Vec::new();
walk(&root, &mut out);
out
}
fn relative(path: &std::path::Path) -> String {
path.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(path)
.display()
.to_string()
}
fn without_string_literals(line: &str) -> String {
let mut out = String::with_capacity(line.len());
let mut in_string = false;
let mut escaped = false;
for ch in line.chars() {
match ch {
_ if escaped => {
escaped = false;
out.push(if in_string { ' ' } else { ch });
}
'\\' => {
escaped = true;
out.push(if in_string { ' ' } else { ch });
}
'"' => {
in_string = !in_string;
out.push('"');
}
_ => out.push(if in_string { ' ' } else { ch }),
}
}
out
}
fn comment_body(line: &str) -> Option<&str> {
let trimmed = line.trim_start();
for marker in ["///", "//!", "//"] {
if let Some(rest) = trimmed.strip_prefix(marker) {
return Some(rest);
}
}
None
}
fn looks_mutilated(line: &str) -> bool {
let Some(body) = comment_body(line) else {
return false;
};
let cleaned = without_string_literals(body);
let chars: Vec<char> = cleaned.chars().collect();
chars
.windows(4)
.any(|w| w[0].is_alphanumeric() && w[1] == ' ' && w[2] == ' ' && w[3].is_alphanumeric())
}
#[test]
fn the_mutilation_gate_separates_a_scar_from_deliberate_spacing() {
assert!(looks_mutilated(
"//! Provides trait with concrete implementations for"
));
assert!(looks_mutilated(
" // empty vector would propagate to which"
));
assert!(looks_mutilated("/// so and can route to FTS5-puro via"));
assert!(!looks_mutilated(
r#"/// assert_eq!(normalize_entity_name(" hello world "), "hello-world");"#
));
assert!(!looks_mutilated(
" // cargo run --release -- remember --name h1-test (with the stdin body flag)"
));
assert!(!looks_mutilated(r#" let s = "a b";"#));
assert!(!looks_mutilated("/// One space between every word here."));
}
#[test]
fn no_comment_in_src_carries_the_scar_of_a_deleted_identifier() {
let mut scars = Vec::new();
for path in source_files() {
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
for (n, line) in text.lines().enumerate() {
if looks_mutilated(line) {
scars.push(format!("{}:{}: {}", relative(&path), n + 1, line.trim()));
}
}
}
assert!(
scars.is_empty(),
"a comment carries a double space between two words, which is what an \
erased identifier leaves behind. Restore the missing name, or move the \
deliberate spacing inside a string literal.\n{}",
scars.join("\n")
);
}
fn cfg_test_spans(text: &str) -> Vec<(usize, usize)> {
let lines: Vec<&str> = text.lines().collect();
let mut spans = Vec::new();
let mut i = 0;
while i < lines.len() {
if lines[i].trim_start().starts_with("#[cfg(test)]") {
let mut depth: i32 = 0;
let mut opened = false;
let mut j = i;
while j < lines.len() {
depth += lines[j].matches('{').count() as i32;
depth -= lines[j].matches('}').count() as i32;
if lines[j].contains('{') {
opened = true;
}
if opened && depth <= 0 {
break;
}
j += 1;
}
spans.push((i, j.min(lines.len().saturating_sub(1))));
i = j;
}
i += 1;
}
spans
}
fn is_test_only_file(path: &std::path::Path) -> bool {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
name == "tests.rs" || name.ends_with("_tests.rs")
}
fn is_inline_timing_policy(line: &str) -> bool {
let code = without_string_literals(line);
if code.trim_start().starts_with("//") {
return false;
}
for (marker, marker_ends_at_paren) in [
("Duration::from_", false),
("sleep(", true),
("timeout(", true),
] {
let mut from = 0;
while let Some(rel) = code[from..].find(marker) {
let after_marker = from + rel + marker.len();
let arg_start = if marker_ends_at_paren {
Some(after_marker)
} else {
code[after_marker..].find('(').map(|o| after_marker + o + 1)
};
if let Some(arg_start) = arg_start {
let arg: String = code[arg_start..]
.chars()
.take_while(|c| *c != ')')
.filter(|c| !c.is_whitespace() && *c != '_')
.collect();
if !arg.is_empty() && arg.chars().all(|c| c.is_ascii_digit()) {
return true;
}
}
from = after_marker;
if from >= code.len() {
break;
}
}
}
false
}
#[test]
fn the_timing_policy_gate_reads_the_argument_not_the_call() {
assert!(is_inline_timing_policy(
" std::thread::sleep(Duration::from_secs(300));"
));
assert!(is_inline_timing_policy(" sleep(500);"));
assert!(!is_inline_timing_policy(
" std::thread::sleep(Duration::from_secs(crate::constants::SHUTDOWN_GRACE_SECS));"
));
assert!(!is_inline_timing_policy(" Duration::from_secs(budget)"));
assert!(!is_inline_timing_policy(
" // was Duration::from_secs(300) before v1.2.0"
));
}
#[test]
fn no_timing_policy_in_src_is_written_as_a_bare_literal() {
let mut hits = Vec::new();
for path in source_files() {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
if name == "constants.rs" || is_test_only_file(&path) {
continue;
}
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let spans = cfg_test_spans(&text);
for (n, line) in text.lines().enumerate() {
if spans.iter().any(|(a, b)| n >= *a && n <= *b) {
continue;
}
if is_inline_timing_policy(line) {
hits.push(format!("{}:{}: {}", relative(&path), n + 1, line.trim()));
}
}
}
assert!(
hits.is_empty(),
"a timeout, sleep or duration is written as a bare literal. Name it in \
`src/constants.rs` with its units, its reason and its XDG key, and read \
it through the resolver.\n{}",
hits.join("\n")
);
}