diff --git a/scripts/README.md b/scripts/README.md index 528acc9..8d99733 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -90,7 +90,7 @@ shell env file. - новые `.rs` файлы не больше 1000 строк; - существующие крупные файлы не должны расти сверх текущего baseline; -- крупные inline test modules подсвечиваются как debt. +- крупные inline test modules запрещены вне legacy allowlist. ```bash ./scripts/check-rust-code-health.sh diff --git a/scripts/check-rust-code-health.sh b/scripts/check-rust-code-health.sh index ca23e5b..e9edd40 100755 --- a/scripts/check-rust-code-health.sh +++ b/scripts/check-rust-code-health.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ROOT_DIR="${CRANK_RUST_HEALTH_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" -DEFAULT_MAX_LINES=1000 +DEFAULT_MAX_LINES="${CRANK_RUST_HEALTH_DEFAULT_MAX_LINES:-1000}" # Existing debt baseline. New files must stay under DEFAULT_MAX_LINES. These # files are allowed to exist, but CI fails if they grow further. @@ -46,7 +46,8 @@ while IFS=: read -r file line _; do fi lines="$(wc -l < "$file" | tr -d ' ')" if (( lines > DEFAULT_MAX_LINES / 2 )); then - echo "warning: $rel contains inline tests and has $lines lines; prefer tests/ for integration-style tests" >&2 + echo "error: $rel contains inline tests and has $lines lines; move integration-style tests to tests/" >&2 + status=1 fi done < <( grep -RIn --include='*.rs' '^[[:space:]]*mod tests[[:space:]]*{' \ @@ -61,7 +62,8 @@ Rust code health failed. Rules: - new Rust files must stay at or below 1000 lines; - existing large files are pinned to the current baseline and must not grow; -- when touching a large file, prefer extracting modules or moving integration-style tests to tests/. +- large inline test modules are forbidden outside the legacy allowlist; +- when touching a large file, extract modules or move integration-style tests to tests/. EOF fi diff --git a/tests/unit/test_check_rust_code_health.py b/tests/unit/test_check_rust_code_health.py new file mode 100644 index 0000000..895b42c --- /dev/null +++ b/tests/unit/test_check_rust_code_health.py @@ -0,0 +1,85 @@ +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +CHECKER = ROOT / "scripts" / "check-rust-code-health.sh" + + +class RustCodeHealthCheckTests(unittest.TestCase): + def run_checker(self, root: Path, max_lines: int = 20) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["CRANK_RUST_HEALTH_ROOT"] = str(root) + env["CRANK_RUST_HEALTH_DEFAULT_MAX_LINES"] = str(max_lines) + return subprocess.run( + [str(CHECKER)], + check=False, + text=True, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def prepare_root(self, root: Path) -> None: + (root / "apps" / "demo" / "src").mkdir(parents=True) + (root / "crates" / "demo" / "src").mkdir(parents=True) + + def test_passes_for_small_rust_files(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.prepare_root(root) + (root / "crates" / "demo" / "src" / "lib.rs").write_text( + "pub fn ok() -> bool {\n true\n}\n", + encoding="utf-8", + ) + + result = self.run_checker(root) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("checking file size limits", result.stdout) + + def test_fails_for_new_large_rust_file(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.prepare_root(root) + (root / "apps" / "demo" / "src" / "large.rs").write_text( + "\n".join("// line" for _ in range(21)) + "\n", + encoding="utf-8", + ) + + result = self.run_checker(root) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("apps/demo/src/large.rs has 21 lines", result.stderr) + + def test_fails_for_large_inline_test_module(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.prepare_root(root) + source = "\n".join( + [ + "pub fn ok() -> bool { true }", + "#[cfg(test)]", + "mod tests {", + " #[test]", + " fn works() { assert!(super::ok()); }", + "}", + *["// test debt" for _ in range(8)], + ] + ) + (root / "crates" / "demo" / "src" / "lib.rs").write_text( + source + "\n", + encoding="utf-8", + ) + + result = self.run_checker(root) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("contains inline tests", result.stderr) + + +if __name__ == "__main__": + unittest.main()