use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use syn::spanned::Spanned;
use syn::visit::Visit;
const LIMIT: usize = 400;
const TEST_LIMIT: usize = 500;
fn pinned() -> HashMap<&'static str, usize> {
HashMap::from(PINS)
}
include!("fixtures/file_length_pins.rs");
struct TestItems(Vec<(usize, usize)>);
impl TestItems {
fn add(&mut self, attrs: &[syn::Attribute], item: &impl Spanned) -> bool {
if !test_only(attrs) {
return false;
}
let span = item.span();
self.0.push((span.start().line, span.end().line));
true
}
}
impl<'a> Visit<'a> for TestItems {
fn visit_item(&mut self, i: &'a syn::Item) {
let attrs = match i {
syn::Item::Const(x) => &x.attrs,
syn::Item::Enum(x) => &x.attrs,
syn::Item::Fn(x) => &x.attrs,
syn::Item::Impl(x) => &x.attrs,
syn::Item::Macro(x) => &x.attrs,
syn::Item::Mod(x) => &x.attrs,
syn::Item::Static(x) => &x.attrs,
syn::Item::Struct(x) => &x.attrs,
syn::Item::Trait(x) => &x.attrs,
syn::Item::Type(x) => &x.attrs,
syn::Item::Use(x) => &x.attrs,
_ => return syn::visit::visit_item(self, i),
};
if !self.add(attrs, i) {
syn::visit::visit_item(self, i);
}
}
fn visit_impl_item_fn(&mut self, f: &'a syn::ImplItemFn) {
if !self.add(&f.attrs, f) {
syn::visit::visit_impl_item_fn(self, f);
}
}
}
fn test_only(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|a| {
a.path().is_ident("test") || (a.path().is_ident("cfg") && a.parse_args::<syn::Meta>().is_ok_and(|m| m.path().is_ident("test")))
})
}
fn production_lines(source: &str) -> usize {
let file = syn::parse_file(source).unwrap_or_else(|e| panic!("does not parse: {e}"));
let mut tests = TestItems(Vec::new());
tests.visit_file(&file);
let lines: Vec<&str> = source.lines().collect();
let total = lines.len();
let mut is_test = vec![false; total + 1];
for (a, b) in &tests.0 {
is_test[*a..=(*b).min(total)].fill(true);
}
let blank = |line: usize| lines.get(line - 1).is_some_and(|l| l.trim().is_empty());
for start in 1..=total {
if !is_test[start] {
continue;
}
for line in (1..start).rev().take_while(|l| blank(*l)) {
is_test[line] = true;
}
for line in (start + 1..=total).take_while(|l| blank(*l)) {
is_test[line] = true;
}
}
(1..=total).filter(|line| !is_test[*line]).count()
}
fn mod_decls(source: &str) -> Vec<(String, Option<String>, bool)> {
let Ok(file) = syn::parse_file(source) else { return Vec::new() };
file.items
.iter()
.filter_map(|item| match item {
syn::Item::Mod(m) if m.content.is_none() => {
let path = m.attrs.iter().find(|a| a.path().is_ident("path")).and_then(|a| match &a.meta {
syn::Meta::NameValue(nv) => match &nv.value {
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => Some(s.value()),
_ => None,
},
_ => None,
});
Some((m.ident.to_string(), path, test_only(&m.attrs)))
}
_ => None,
})
.collect()
}
fn resolve_mod(declaring_file: &Path, name: &str, path_attr: Option<&str>) -> Option<PathBuf> {
let parent = declaring_file.parent()?;
let stem = declaring_file.file_stem()?.to_str()?;
let dir = if matches!(stem, "lib" | "main" | "mod") { parent.to_path_buf() } else { parent.join(stem) };
if let Some(rel) = path_attr {
let candidate = dir.join(rel);
return candidate.is_file().then_some(candidate);
}
[dir.join(format!("{name}.rs")), dir.join(name).join("mod.rs")].into_iter().find(|p| p.is_file())
}
fn test_only_files(files: &[PathBuf]) -> HashSet<PathBuf> {
let sources: HashMap<&PathBuf, String> = files.iter().map(|p| (p, std::fs::read_to_string(p).unwrap_or_default())).collect();
let mut found = HashSet::new();
let mut queue: Vec<PathBuf> = Vec::new();
for file in files {
for (name, path_attr, is_test) in mod_decls(&sources[file]) {
if is_test && let Some(target) = resolve_mod(file, &name, path_attr.as_deref()) {
queue.push(target);
}
}
}
while let Some(file) = queue.pop() {
if !found.insert(file.clone()) {
continue;
}
let Some(source) = sources.get(&file) else { continue };
for (name, path_attr, _) in mod_decls(source) {
if let Some(target) = resolve_mod(&file, &name, path_attr.as_deref()) {
queue.push(target);
}
}
}
found
}
fn crate_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf()
}
fn sources(dir: &Path, found: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else { return };
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
sources(&path, found);
} else if path.extension().is_some_and(|e| e == "rs") {
found.push(path);
}
}
}
fn verdict(relative: &str, lines: usize, limit: usize, pinned: &HashMap<&'static str, usize>) -> Option<String> {
match pinned.get(relative) {
Some(&ceiling) if lines > ceiling => Some(format!(
"{relative}: {lines} lines, up from its pinned {ceiling}. It is already over the \
{limit}-line limit; split it rather than growing it further. New code goes in a new \
module, never into a pinned file."
)),
Some(_) if lines <= limit => Some(format!(
"{relative}: down to {lines} lines — under the {limit} limit, so remove its entry from \
the pin list and let the real limit hold it there."
)),
Some(&ceiling) if lines < ceiling => Some(format!(
"{relative}: down to {lines} lines from its pinned {ceiling}. Lower its pin to {lines} \
so it cannot grow back."
)),
Some(_) => None,
None if lines > limit => Some(format!(
"{relative}: {lines} lines, over the {limit} limit. Split it into pieces that each do \
one thing (test items are not counted, so they are not the cause)."
)),
None => None,
}
}
fn measured() -> Vec<(String, usize, usize)> {
let root = crate_root();
let mut files = Vec::new();
sources(&root.join("src"), &mut files);
let test_only = test_only_files(&files);
sources(&root.join("tests"), &mut files);
files.push(root.join("build.rs"));
files
.iter()
.filter(|p| p.is_file())
.map(|path| {
let relative = path.strip_prefix(&root).unwrap_or(path).to_string_lossy().replace('\\', "/");
let source = std::fs::read_to_string(path).unwrap_or_default();
let is_test = test_only.contains(path) || relative.starts_with("tests/");
let (limit, lines) = if is_test { (TEST_LIMIT, source.lines().count()) } else { (LIMIT, production_lines(&source)) };
(relative, lines, limit)
})
.collect()
}
#[test]
fn no_file_outgrows_its_limit() {
let pinned = pinned();
let files = measured();
assert!(files.len() > 50, "only {} files measured — the walk is broken, not the tree", files.len());
let mut failures: Vec<String> = files
.iter()
.filter_map(|(relative, lines, limit)| verdict(relative, *lines, *limit, &pinned))
.collect();
failures.sort();
assert!(failures.is_empty(), "\n{}\n", failures.join("\n"));
}
#[test]
fn the_ratchet_holds_a_pinned_file_to_its_size() {
let pinned = HashMap::from([("a.rs", 500)]);
assert!(verdict("a.rs", 500, 400, &pinned).is_none(), "at its ceiling is fine");
assert!(
verdict("a.rs", 450, 400, &pinned).is_some_and(|m| m.contains("Lower its pin to 450")),
"a shrink must lower the pin, or the file could grow back"
);
assert!(verdict("a.rs", 501, 400, &pinned).is_some_and(|m| m.contains("up from its pinned")), "a pinned file may not grow");
assert!(verdict("a.rs", 400, 400, &pinned).is_some_and(|m| m.contains("remove its entry")), "once under the limit the pin must go");
assert!(verdict("b.rs", 400, 400, &pinned).is_none(), "unpinned, at the limit");
assert!(verdict("b.rs", 401, 400, &pinned).is_some_and(|m| m.contains("over the")), "unpinned, over the limit");
}
#[test]
fn every_pinned_file_still_exists() {
let measured: HashSet<String> = measured().into_iter().map(|(r, _, _)| r).collect();
let missing: Vec<&str> = pinned().keys().copied().filter(|r| !measured.contains(*r)).collect();
assert!(missing.is_empty(), "pinned files are no longer measured at these paths: {missing:?}");
}
#[test]
fn whole_file_test_modules_are_read_as_tests() {
let by_path: HashMap<String, usize> = measured().into_iter().map(|(r, _, limit)| (r, limit)).collect();
for relative in [
"src/registry/tests.rs", "src/handler_property_tests.rs", "src/tests.rs", "src/composition.rs", "src/cst/proptests.rs",
"src/engine/testgen.rs", "src/engine/resolve/scenarios.rs", "src/suggest/tests.rs", "src/decisionlog/tests.rs",
] {
assert_eq!(
by_path.get(relative),
Some(&TEST_LIMIT),
"{relative} is declared `#[cfg(test)] mod …;` and must be held to the test limit"
);
}
for relative in ["src/pathgate.rs", "src/main.rs", "src/registry/mod.rs"] {
assert_eq!(by_path.get(relative), Some(&LIMIT), "{relative} is production");
}
}
#[test]
fn only_test_items_are_left_out_of_the_count() {
assert_eq!(production_lines("fn a() {}\nfn b() {}\n#[cfg(test)]\nmod tests {\n // lots\n}\n"), 2);
assert_eq!(production_lines("fn a() {}\n"), 1, "a file with no tests counts whole");
assert_eq!(production_lines(""), 0);
let cases: &[(&str, &str, usize)] = &[
(
"a test-only mod declaration is its own line, not the rest of the file",
"mod real;\n#[cfg(test)]\nmod test_support;\n\nfn a() {}\nfn b() {}\n",
3,
),
(
"a test-only helper is the helper, not the rest of the file",
"fn a() {}\n#[cfg(test)]\nfn helper() {}\nfn b() {}\nfn c() {}\n",
3,
),
(
"an attribute between the guard and the item goes with the item",
"#[cfg(test)]\n#[path = \"t.rs\"]\nmod tests;\n\nfn a() {}\nfn b() {}\n",
2,
),
("a same-line test module body is still skipped", "fn a() {}\n#[cfg(test)] mod tests {\n fn t() {}\n}\n", 1),
("a one-line test module", "fn a() {}\n#[cfg(test)] mod tests { fn t() {} }\nfn b() {}\n", 2),
(
"production code after a test module still counts (files here keep them between functions)",
"fn a() {}\n#[cfg(test)]\nmod a_tests {\n #[test]\n fn t() {\n }\n}\n\nfn b() {}\nfn c() {}\n",
3,
),
(
"a `}` in column 0 inside a test's string does not end the module",
"#[cfg(test)]\nmod tests {\n const FIX: &str = \"\n}\n\";\n fn t() {}\n}\nfn b() {}\n",
1,
),
(
"`#[cfg(test)]` in a string or comment is not an attribute",
"// #[cfg(test)]\nconst A: &str = \"\n#[cfg(test)]\nmod x {\";\nfn b() {}\n",
5,
),
("a test-only method in a production impl", "impl A {\n fn a() {}\n #[cfg(test)]\n fn t() {}\n}\n", 3),
("doc comments go with their item", "/// tests\n#[cfg(test)]\nmod t {}\nfn b() {}\n", 1),
("a blank line between two test items belongs to them", "fn a() {}\n#[cfg(test)]\nfn t() {}\n\n#[cfg(test)]\nfn u() {}\n", 1),
("a blank line before an appended test item belongs to it", "fn a() {}\nfn b() {}\n\n#[cfg(test)]\nmod t {\n}\n", 2),
("a blank line after a test item belongs to it", "#[cfg(test)]\nmod t {\n}\n\nfn a() {}\n", 1),
("a run of blank lines is absorbed whole", "fn a() {}\n\n\n#[cfg(test)]\nfn t() {}\n\n\nfn b() {}\n", 2),
("blank lines between two PRODUCTION items still count", "fn a() {}\n\nfn b() {}\n", 3),
];
for (why, source, expected) in cases {
assert_eq!(production_lines(source), *expected, "{why}");
}
}
#[test]
fn a_test_only_module_declaration_is_recognized_wherever_it_points() {
let decls = mod_decls("#[cfg(test)]\nmod tests;\nmod real;\n#[cfg(test)]\nmod inline { }\n");
assert_eq!(
decls,
vec![("tests".to_string(), None, true), ("real".to_string(), None, false),],
"bodyless declarations only, each tagged with whether it is test-only"
);
let with_path = mod_decls("#[cfg(test)]\n#[path = \"support/x.rs\"]\nmod x;\n");
assert_eq!(with_path, vec![("x".to_string(), Some("support/x.rs".to_string()), true)]);
}