Enforce Rust code health gate
This commit is contained in:
+1
-1
@@ -90,7 +90,7 @@ shell env file.
|
|||||||
|
|
||||||
- новые `.rs` файлы не больше 1000 строк;
|
- новые `.rs` файлы не больше 1000 строк;
|
||||||
- существующие крупные файлы не должны расти сверх текущего baseline;
|
- существующие крупные файлы не должны расти сверх текущего baseline;
|
||||||
- крупные inline test modules подсвечиваются как debt.
|
- крупные inline test modules запрещены вне legacy allowlist.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/check-rust-code-health.sh
|
./scripts/check-rust-code-health.sh
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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
|
# Existing debt baseline. New files must stay under DEFAULT_MAX_LINES. These
|
||||||
# files are allowed to exist, but CI fails if they grow further.
|
# files are allowed to exist, but CI fails if they grow further.
|
||||||
@@ -46,7 +46,8 @@ while IFS=: read -r file line _; do
|
|||||||
fi
|
fi
|
||||||
lines="$(wc -l < "$file" | tr -d ' ')"
|
lines="$(wc -l < "$file" | tr -d ' ')"
|
||||||
if (( lines > DEFAULT_MAX_LINES / 2 )); then
|
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
|
fi
|
||||||
done < <(
|
done < <(
|
||||||
grep -RIn --include='*.rs' '^[[:space:]]*mod tests[[:space:]]*{' \
|
grep -RIn --include='*.rs' '^[[:space:]]*mod tests[[:space:]]*{' \
|
||||||
@@ -61,7 +62,8 @@ Rust code health failed.
|
|||||||
Rules:
|
Rules:
|
||||||
- new Rust files must stay at or below 1000 lines;
|
- new Rust files must stay at or below 1000 lines;
|
||||||
- existing large files are pinned to the current baseline and must not grow;
|
- 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
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user