test_maintainability_ratchet.py (6308B)
1 from __future__ import annotations 2 3 import json 4 import sys 5 import tempfile 6 import unittest 7 from pathlib import Path 8 9 SCRIPTS = Path(__file__).resolve().parent 10 if str(SCRIPTS) not in sys.path: 11 sys.path.insert(0, str(SCRIPTS)) 12 13 import maintainability_ratchet as ratchet # noqa: E402 14 15 16 class MaintainabilityRatchetTests(unittest.TestCase): 17 def _repository(self) -> tuple[tempfile.TemporaryDirectory[str], Path]: 18 temporary = tempfile.TemporaryDirectory() 19 root = Path(temporary.name) 20 (root / "Tera").mkdir() 21 (root / "TeraTests").mkdir() 22 (root / "TeraUITests").mkdir() 23 (root / "scripts").mkdir() 24 (root / "test-fixtures").mkdir() 25 (root / "Tera/App.swift").write_text("struct App {}\n", encoding="utf-8") 26 (root / "scripts/check.py").write_text( 27 "def check():\n return True\n", encoding="utf-8" 28 ) 29 self._write_baseline(root) 30 return temporary, root 31 32 def _write_baseline(self, root: Path, **overrides: object) -> None: 33 baseline: dict[str, object] = { 34 "schema": "radroots.ios.maintainability-baseline.v1", 35 "schema_version": 1, 36 "source_revision": "c63002bcc4d3f6656e93aabe4fca6bd771376629", 37 "thresholds": { 38 "swift_file_lines": 600, 39 "python_file_lines": 800, 40 "python_function_complexity": 10, 41 }, 42 "swift_file_exception": [], 43 "python_file_exception": [], 44 "python_complexity_exception": [], 45 "bounded_module": ["Tera/App.swift", "scripts/check.py"], 46 } 47 baseline.update(overrides) 48 (root / ratchet.BASELINE_PATH).write_text( 49 json.dumps(baseline, sort_keys=True), encoding="utf-8" 50 ) 51 52 def test_current_repository_satisfies_ratchet(self) -> None: 53 ratchet.verify(SCRIPTS.parent) 54 55 def test_new_oversized_swift_file_is_rejected(self) -> None: 56 temporary, root = self._repository() 57 with temporary: 58 (root / "Tera/Large.swift").write_text("x\n" * 601, encoding="utf-8") 59 with self.assertRaisesRegex(ratchet.MaintainabilityError, "inventory"): 60 ratchet.verify(root) 61 62 def test_exception_cannot_grow_or_become_stale(self) -> None: 63 temporary, root = self._repository() 64 with temporary: 65 path = root / "Tera/App.swift" 66 path.write_text("x\n" * 602, encoding="utf-8") 67 exception = [{"path": "Tera/App.swift", "maximum_lines": 601}] 68 self._write_baseline(root, swift_file_exception=exception) 69 with self.assertRaisesRegex(ratchet.MaintainabilityError, "regressed"): 70 ratchet.verify(root) 71 path.write_text("struct App {}\n", encoding="utf-8") 72 with self.assertRaisesRegex(ratchet.MaintainabilityError, "inventory"): 73 ratchet.verify(root) 74 75 def test_new_oversized_python_file_is_rejected(self) -> None: 76 temporary, root = self._repository() 77 with temporary: 78 (root / "scripts/large.py").write_text("x = 1\n" * 801, encoding="utf-8") 79 with self.assertRaisesRegex(ratchet.MaintainabilityError, "inventory"): 80 ratchet.verify(root) 81 82 def test_new_complex_python_function_is_rejected(self) -> None: 83 temporary, root = self._repository() 84 with temporary: 85 branches = "".join( 86 f" if value == {index}:\n return {index}\n" 87 for index in range(10) 88 ) 89 (root / "scripts/complex.py").write_text( 90 "def complex(value):\n" + branches + " return -1\n", 91 encoding="utf-8", 92 ) 93 with self.assertRaisesRegex(ratchet.MaintainabilityError, "inventory"): 94 ratchet.verify(root) 95 96 def test_baseline_identity_threshold_and_order_are_closed(self) -> None: 97 temporary, root = self._repository() 98 with temporary: 99 self._write_baseline(root, source_revision="f" * 40) 100 with self.assertRaisesRegex(ratchet.MaintainabilityError, "identity"): 101 ratchet.verify(root) 102 self._write_baseline( 103 root, 104 thresholds={ 105 "swift_file_lines": 601, 106 "python_file_lines": 800, 107 "python_function_complexity": 10, 108 }, 109 ) 110 with self.assertRaisesRegex(ratchet.MaintainabilityError, "threshold"): 111 ratchet.verify(root) 112 self._write_baseline( 113 root, 114 bounded_module=["scripts/check.py", "Tera/App.swift"], 115 ) 116 with self.assertRaisesRegex(ratchet.MaintainabilityError, "inventory"): 117 ratchet.verify(root) 118 119 def test_bounded_module_must_exist_and_remain_small(self) -> None: 120 temporary, root = self._repository() 121 with temporary: 122 self._write_baseline(root, bounded_module=["Tera/Missing.swift"]) 123 with self.assertRaisesRegex(ratchet.MaintainabilityError, "absent"): 124 ratchet.verify(root) 125 (root / "Tera/App.swift").write_text("x\n" * 601, encoding="utf-8") 126 self._write_baseline( 127 root, 128 swift_file_exception=[{"path": "Tera/App.swift", "maximum_lines": 601}], 129 ) 130 with self.assertRaisesRegex(ratchet.MaintainabilityError, "bounded"): 131 ratchet.verify(root) 132 133 def test_exception_rows_are_exact_unique_and_ordered(self) -> None: 134 invalid = [ 135 [ 136 {"path": "b", "maximum_lines": 601}, 137 {"path": "a", "maximum_lines": 601}, 138 ], 139 [ 140 {"path": "a", "maximum_lines": 601}, 141 {"path": "a", "maximum_lines": 602}, 142 ], 143 [{"path": "a", "maximum_lines": 601, "extra": True}], 144 ] 145 for rows in invalid: 146 with self.subTest(rows=rows): 147 with self.assertRaises(ratchet.MaintainabilityError): 148 ratchet._closed_exception_map( 149 rows, key_name="path", ceiling_name="maximum_lines" 150 ) 151 152 153 if __name__ == "__main__": 154 unittest.main()