#!/usr/bin/env python3 # file: scripts/audit_rust_test_layout.py # version: 1 """Audit the Knowledge Rust unit/integration test layout.""" from __future__ import annotations import argparse import pathlib import re import sys _TEST_ATTRIBUTE = re.compile(r"(?m)^\s*#\s*\[\s*test\s*\]") _PATH_ATTRIBUTE = re.compile(r'#\s*\[\s*path\s*=\s*"([^"]+)"\s*\]') _FORBIDDEN_MODULE = re.compile(r"(?m)^\s*mod\s+unit_tests\s*;") def audit_crate(crate_root: pathlib.Path) -> list[str]: """Return layout violations for one crate.""" errors: list[str] = [] src_root = crate_root / "src" referenced_unit_tests: set[pathlib.Path] = set() if src_root.is_dir(): for source in sorted(src_root.rglob("*.rs")): text = source.read_text(encoding="utf-8") relative = source.relative_to(crate_root) if _TEST_ATTRIBUTE.search(text): errors.append(f"{crate_root.name}/{relative}: #[test] must live under unit_tests/") if source.name in {"tests.rs", "unit_tests.rs"}: errors.append(f"{crate_root.name}/{relative}: test implementation file must not live under src/") if _FORBIDDEN_MODULE.search(text): errors.append(f"{crate_root.name}/{relative}: use #[path = \"../unit_tests/...\"] instead of mod unit_tests;") for match in _PATH_ATTRIBUTE.finditer(text): candidate = (source.parent / match.group(1)).resolve() try: candidate.relative_to((crate_root / "unit_tests").resolve()) except ValueError: continue referenced_unit_tests.add(candidate) unit_root = crate_root / "unit_tests" if unit_root.is_dir(): for test_file in sorted(unit_root.rglob("*.rs")): resolved = test_file.resolve() if resolved not in referenced_unit_tests: relative = test_file.relative_to(crate_root) errors.append(f"{crate_root.name}/{relative}: unit test file is not attached by any #[path] module") return errors def main() -> int: """Run the audit.""" parser = argparse.ArgumentParser() parser.add_argument("root", nargs="?", default="crates") arguments = parser.parse_args() root = pathlib.Path(arguments.root) errors: list[str] = [] crate_count = 0 for manifest in sorted(root.glob("*/Cargo.toml")): crate_count += 1 errors.extend(audit_crate(manifest.parent)) if errors: for error in errors: print(error, file=sys.stderr) return 1 print(f"Rust test-layout audit: clean ({crate_count} crate(s))") return 0 if __name__ == "__main__": raise SystemExit(main())