"""Rule and structure precision of the code-health ratchet (scripts/code_health): each case is a reviewer's repro paired with the control that must keep its verdict.""" from __future__ import annotations import ast import pytest from scripts.code_health.config import RULES_BY_ID from scripts.code_health.py_rules import CHECKERS, Ctx, canonical_tree from scripts.code_health.py_structure import body_hash, nesting_depth from tests.scripts.test_code_health import _commit, _repo, _verdict def _hits(rule: str, src: str) -> list[int]: """Lines ``rule`` reports for ``src``, through the same canonical tree the measurer uses.""" return sorted(set(CHECKERS[rule](canonical_tree(ast.parse(src)), Ctx()))) def _judge(tmp_path, capsys, base_files: dict[str, str], head_files: dict[str, str | None]): repo, base = _repo(tmp_path) if base_files: base = _commit(repo, base_files) return _verdict(repo, base, head_files, capsys) # --- F12: an import alias is shadowed only in the scope that rebinds it --- _ALIAS = "from subprocess import run as execute\n\n\n{}\n\n\ndef launch(cmd):\n{} return execute(cmd)\n" @pytest.mark.parametrize("elsewhere, local, flagged", [ ("def identity(execute):\n return execute", "", True), # unrelated parameter ("def other():\n execute = 42\n return execute", "", True), # unrelated local ("def other():\n def execute():\n return 1\n return execute", "", True), # nested def ("def identity(x):\n return x", "", True), # genuine shadowing where the call is made still stops resolution ("def identity(x):\n return x", " execute = print\n", False), ("execute = print", "", False), # the module itself rebinds it: ambiguous ]) def test_import_alias_resolves_per_scope(elsewhere, local, flagged): src = _ALIAS.format(elsewhere, local) assert bool(_hits("HX006", src)) is flagged, src def test_import_alias_parameter_of_consuming_function_shadows(): src = "from subprocess import run as execute\n\n\ndef launch(cmd, execute):\n return execute(cmd)\n" assert _hits("HX006", src) == [] def test_import_alias_with_unrelated_parameter_blocks_through_the_verdict(tmp_path, capsys): head = {"pkg/c.py": _ALIAS.format("def identity(execute):\n return execute", "")} code, out = _judge(tmp_path, capsys, {}, head) assert code == 1 and "HX006" in out, out # `global` / `nonlocal` select the outer binding; only an assignment through them rebinds it. @pytest.mark.parametrize("src, flagged", [ (_ALIAS.format("", " global execute\n"), True), ("def outer(cmd):\n from subprocess import run as execute\n\n def launch():\n" " nonlocal execute\n return execute(cmd)\n return launch\n", True), (_ALIAS.format("def setup():\n global execute\n execute = print", ""), False), ]) def test_global_and_nonlocal_select_the_outer_binding(src, flagged): assert bool(_hits("HX006", src)) is flagged, src # --- F19: every independent eager capture is its own occurrence --- _DICT = 'import os\n\nCACHE = {{\n{} "old": os.getenv("PATH"),\n}}\n' _LIST = "import os\n\nPATHS = [\n{} os.getenv(\"PATH\"),\n]\n" _DEFAULT = "import os\n\n\ndef f(\n{} a=os.getenv(\"PATH\"),\n):\n return a\n" @pytest.mark.parametrize("shape, added, blocks", [ (_DICT, ' "new": os.getenv("HOME"),\n', True), (_LIST, ' os.getenv("HOME"),\n', True), (_DEFAULT, ' b=os.getenv("HOME"),\n', True), (_DICT, ' "new": lambda: os.getenv("HOME"),\n', False), # deferred read: the fix (_DICT, ' "new": "literal",\n', False), # unchanged debt stays grandfathered ]) def test_added_capture_inside_an_existing_expression_is_new(tmp_path, capsys, shape, added, blocks): code, out = _judge(tmp_path, capsys, {"pkg/c.py": shape.format("")}, {"pkg/c.py": shape.format(added)}) assert code == (1 if blocks else 0), out assert ("HX005" in out) is blocks, out def test_added_capture_in_its_own_assignment_is_new(tmp_path, capsys): base = _DICT.format("") code, out = _judge(tmp_path, capsys, {"pkg/c.py": base}, {"pkg/c.py": base + 'NEW = os.getenv("HOME")\n'}) assert code == 1 and "HX005" in out, out def test_capture_lines_reports_each_independent_capture_once(): two = 'import os\n\nCACHE = {\n "a": os.getenv("A"),\n "b": os.getenv("B"),\n}\n' assert _hits("HX005", two) == [4, 5] # a capture nested inside another is the same occurrence nested = 'import os\n\nHOME = os.path.expanduser(\n os.getenv("A", "~")\n)\n' assert _hits("HX005", nested) == [3] # --- HX006: process handles are tracked per lexical scope --- _SYNC_ASYNC = ( "import asyncio\nimport subprocess\n\n\n" "def sync(cmd):\n proc = subprocess.Popen(cmd)\n return proc.communicate(timeout=1)\n\n\n" "async def asynchronous(cmd):\n {name} = await asyncio.create_subprocess_exec(*cmd)\n" " return await asyncio.wait_for({name}.communicate(), timeout=1)\n" ) @pytest.mark.parametrize("name", ["proc", "child"]) def test_process_handles_in_different_functions_do_not_collide(name): assert _hits("HX006", _SYNC_ASYNC.format(name=name)) == [] def test_adding_a_bounded_async_helper_keeps_the_sync_one_clean(tmp_path, capsys): sync_only = _SYNC_ASYNC.format(name="proc").split("\n\n\nasync def")[0] + "\n" code, out = _judge(tmp_path, capsys, {"pkg/p.py": sync_only}, {"pkg/p.py": _SYNC_ASYNC.format(name="proc")}) assert code == 0, out def test_process_handle_follows_reassignment(): src = ("import asyncio\nimport subprocess\n\n\nasync def f(c):\n" " proc = subprocess.Popen(c)\n proc.wait(timeout=1)\n" " proc = await asyncio.create_subprocess_exec(*c)\n await proc.wait()\n" " proc = None\n return proc\n") assert _hits("HX006", src) == [9] # only the asyncio wait, which nothing bounds unbounded = "import subprocess\n\n\ndef f(c):\n proc = subprocess.Popen(c)\n return proc.wait()\n" assert _hits("HX006", unbounded) == [6] with_block = "import subprocess\n\n\ndef f(c):\n with subprocess.Popen(c) as proc:\n return proc.wait()\n" assert _hits("HX006", with_block) == [6] # a parameter that merely shares the spelling is not the other function's process param = unbounded + "\n\ndef stop(proc):\n return proc.wait()\n" assert _hits("HX006", param) == [6] # ...but one annotated as a process is a process typed = param.replace("def stop(proc):", "def stop(proc: subprocess.Popen[str]):") assert _hits("HX006", typed) == [6, 10] typed_async = ("import asyncio\n\n\nasync def stop(proc: asyncio.subprocess.Process):\n" " return await proc.wait()\n") assert _hits("HX006", typed_async) == [5] def test_process_handle_on_an_attribute_spans_the_class_methods(): src = ("import subprocess\n\n\nclass Runner:\n def start(self, c):\n" " self._proc = subprocess.Popen(c)\n\n def stop(self):\n" " return self._proc.wait()\n") assert _hits("HX006", src) == [9] closure = ("import subprocess\n\n\ndef f(c):\n proc = subprocess.Popen(c)\n\n" " def reap():\n return proc.wait()\n return reap\n") assert _hits("HX006", closure) == [8] # --- HX006: a wait right after kill() is bounded, however its value is used --- _KILL = "import asyncio\nimport subprocess\n\n\ndef reap(cmd, other):\n proc = subprocess.Popen(cmd)\n{}" @pytest.mark.parametrize("tail, flagged", [ (" proc.kill()\n proc.wait()\n", False), (" proc.kill()\n rc = proc.wait()\n return rc\n", False), (" proc.kill()\n return proc.wait()\n", False), (" proc.kill()\n rc: int = proc.wait()\n return rc\n", False), (" proc.kill()\n return proc.wait(timeout=5)\n", False), (" return proc.wait()\n", True), # no preceding kill (" other.kill()\n return proc.wait()\n", True), # a different receiver was killed (" proc.kill()\n return proc.communicate()\n", True), # reads until grandchildren exit ]) def test_wait_after_kill(tail, flagged): assert bool(_hits("HX006", _KILL.format(tail))) is flagged def test_async_wait_after_kill_stays_flagged(): src = ("import asyncio\n\n\nasync def f(c):\n proc = await asyncio.create_subprocess_exec(*c)\n" " proc.kill()\n return await proc.wait()\n") assert _hits("HX006", src) == [7] def test_assignment_refactored_into_return_after_kill_passes(tmp_path, capsys): base = _KILL.format(" proc.kill()\n rc = proc.wait()\n return rc\n") head = _KILL.format(" proc.kill()\n return proc.wait()\n") code, out = _judge(tmp_path, capsys, {"pkg/k.py": base}, {"pkg/k.py": head}) assert code == 0, out # --- HX009: annotated results and projected loop variables --- _GATHER = "import asyncio\n\n\nasync def run(awaitables, payloads):\n results{ann} = await asyncio.gather(\n *awaitables, return_exceptions=True\n )\n{loop}" @pytest.mark.parametrize("ann, loop, flagged", [ (": list", " for result in results:\n if isinstance(result, Exception):\n raise result\n", True), ("", " for result in results:\n if isinstance(result, Exception):\n raise result\n", True), ("", " for payload, result in zip(payloads, results):\n if isinstance(result, Exception):\n raise result\n", True), ("", " for payload, result in zip(payloads, results):\n if isinstance(payload, Exception):\n raise payload\n", False), ("", " for payload, result in zip(payloads, results, strict=True):\n if isinstance(payload, Exception):\n raise payload\n", False), ("", " for i, r in enumerate(results):\n if isinstance(r, Exception):\n raise r\n", True), ("", " for i, r in enumerate(results):\n if isinstance(i, Exception):\n raise i\n", False), ("", " return [r for i, r in enumerate(results) if isinstance(i, Exception)]\n", False), (": list", " return [r for i, r in enumerate(results) if isinstance(r, Exception)]\n", True), ]) def test_gather_results_projection(ann, loop, flagged): assert bool(_hits("HX009", _GATHER.format(ann=ann, loop=loop))) is flagged # --- HX005: annotations are evaluated at def time unless postponed --- _FUTURE = "from __future__ import annotations\n\n" @pytest.mark.parametrize("src, flagged", [ ("import os\n\n\ndef f(x: os.getenv('A')):\n return x\n", True), ("import os\n\n\ndef f(x) -> os.getenv('A'):\n return x\n", True), ("import os\n\n\nclass C:\n def f(self, *a: os.getenv('A')):\n return a\n", True), (_FUTURE + "import os\n\n\ndef f(x: os.getenv('A')):\n return x\n", False), (_FUTURE + "import os\n\n\ndef f(x) -> os.getenv('A'):\n return x\n", False), (_FUTURE + "import os\n\nx: os.getenv('PATH')\n", False), (_FUTURE + "import os\n\n\nclass C:\n x: os.getenv('PATH')\n", False), (_FUTURE + "import os\n\nx: str = os.getenv('PATH')\n", True), # the value still runs ("import os\n\nx: os.getenv('PATH')\n", True), ("import os\n\n\ndef f():\n def g(x: os.getenv('A')):\n return x\n return g\n", False), ]) def test_annotation_evaluation_time(src, flagged): assert bool(_hits("HX005", src)) is flagged, src # --- F15: only the function's own bindings stop the recursive-rename normalization --- def _recursive(name: str, inner: str = "legacy") -> str: return (f"def {name}(n):\n def identity({inner}):\n return {inner}\n" + " pass\n" * 300 + f" return {name}(n-1) if n else identity(0)\n") def _hash(src: str) -> str: return body_hash(ast.parse(src).body[0]) @pytest.mark.parametrize("inner", ["legacy", "value"]) def test_recursive_rename_ignores_nested_bindings(inner): assert _hash(_recursive("legacy", inner)) == _hash(_recursive("renamed", inner)) def test_recursive_rename_with_nested_parameter_keeps_its_cap(tmp_path, capsys): code, out = _judge(tmp_path, capsys, {"pkg/r.py": _recursive("legacy")}, {"pkg/r.py": _recursive("renamed")}) assert code == 0, out def test_recursive_rename_controls(): # a rename that leaves the old self-call behind is different code stale = _recursive("renamed").replace("return renamed(", "return legacy(") assert _hash(_recursive("legacy")) != _hash(stale) # a body that rebinds the name in its own scope refers to that local, not to itself own = "def {0}(n):\n {0} = n\n return {0}\n" assert _hash(own.format("legacy")) != _hash(own.format("renamed")) # a closure's reference to the enclosing function is still a self-reference closure = "def {0}(n):\n def helper():\n return {0}(n - 1)\n return helper()\n" assert _hash(closure.format("legacy")) == _hash(closure.format("renamed")) # --- nesting: `else:` + indented `if` is nesting; `elif` is not --- # `>` keeps the fixtures off HX011 (an if/elif ladder on one name). def _else_if(levels: int, inert: bool = False) -> str: out, pad = ["def f(x):"], " " for k in range(levels): if inert and k: out.append(pad + "pass") out += [f"{pad}if x > {k}:", f"{pad} return {k}", f"{pad}else:"] pad += " " out.append(pad + "return -1") return "\n".join(out) + "\n" def _elif(levels: int) -> str: arms = "".join(f" {'if' if k == 0 else 'elif'} x > {k}:\n return {k}\n" for k in range(levels)) return "def f(x):\n" + arms + " else:\n return -1\n" def _depth(src: str) -> int: return nesting_depth(ast.parse(src).body[0].body) def test_nesting_counts_physically_nested_else_if(): assert _depth(_else_if(7)) == _depth(_else_if(7, inert=True)) == 7 assert _depth(_elif(7)) == 1 assert _depth(_else_if(6)) == 6 def test_seven_physical_else_if_levels_block(tmp_path, capsys): code, out = _judge(tmp_path / "nested", capsys, {}, {"pkg/n.py": _else_if(7)}) assert code == 1 and "NESTING" in out, out code, out = _judge(tmp_path / "flat", capsys, {}, {"pkg/n.py": _elif(7)}) assert code == 0, out # --- HX001 / HX003 / HX012 precision --- @pytest.mark.parametrize("expr, flagged", [ ('Path.home() / ".hermes"', True), ('Path.home() / ".hermes/profiles"', True), ('Path.home() / ".hermes" / "x"', True), ('Path.home() / ".hermes-profile-exports"', False), ('Path.home() / ".hermes_backup"', False), ('os.path.expanduser("~/.hermes")', True), ('os.path.expanduser("~/.hermes/config.yaml")', True), ('os.path.expanduser("~/.hermes-profile-exports")', False), ]) def test_hardcoded_home_matches_the_exact_component(expr, flagged): src = f"import os\nfrom pathlib import Path\n\n\ndef f():\n return {expr}\n" assert bool(_hits("HX001", src)) is flagged @pytest.mark.parametrize("src, flagged", [ ('"""Unlike `ps aux`, this reads /proc."""\n', False), ('def f():\n """Never `pgrep -f hermes`: argv substrings lie."""\n return 1\n', False), ('import subprocess\n\n\ndef f():\n return subprocess.run("ps aux", shell=True, timeout=5)\n', True), ('CMD = "pgrep -f hermes"\n', True), ('def f(n):\n return f"pgrep -f {n}"\n', True), ]) def test_argv_identity_ignores_docstrings(src, flagged): assert bool(_hits("HX003", src)) is flagged _THREAD = "import contextvars\nimport threading\n\n\ndef f(fn):\n{}" @pytest.mark.parametrize("body, flagged", [ (" return threading.Thread(target=contextvars.copy_context().run, args=(fn,))\n", False), (" ctx = contextvars.copy_context()\n return threading.Thread(target=ctx.run, args=(fn,))\n", False), (" ctx = contextvars.copy_context()\n return threading.Thread(None, ctx.run, args=(fn,))\n", False), (" return threading.Thread(target=fn)\n", True), (" ctx = make()\n return threading.Thread(target=ctx.run, args=(fn,))\n", True), (" ctx = contextvars.copy_context()\n ctx = make()\n return threading.Thread(target=ctx.run)\n", True), (" return threading.Thread(target=ctx.run, args=(fn,))\n", True), # ctx from elsewhere ]) def test_raw_thread_accepts_a_copied_context(body, flagged): assert bool(_hits("HX012", _THREAD.format(body))) is flagged def test_raw_thread_accepts_from_import_copy_context(): src = ("from contextvars import copy_context\nimport threading\n\n\ndef f(fn):\n" " return threading.Thread(target=copy_context().run, args=(fn,))\n") assert _hits("HX012", src) == [] def test_raw_thread_context_from_another_function_is_not_accepted(): src = ("import contextvars\nimport threading\n\n\ndef g():\n ctx = contextvars.copy_context()\n" " return ctx\n\n\ndef f(fn, ctx):\n return threading.Thread(target=ctx.run)\n") assert _hits("HX012", src) == [11] # --- fix texts say what the engine actually accepts --- def test_fix_texts(): hx010 = RULES_BY_ID["HX010"].fix assert "is not None" in hx010 and "is_truthy_value" in hx010 assert hx010.index("is not None") < hx010.index("is_truthy_value") assert "allow" in RULES_BY_ID["HX001"].fix ble = RULES_BY_ID["BLE001"].fix assert "noqa" in ble and "health: allow BLE001" in ble and "exc_info=True" in ble _HANDLER = "import logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef boundary():\n try:\n pass\n{}" @pytest.mark.parametrize("handler, blocks", [ (" except Exception:\n logger.exception('boundary failed')\n", False), (" except Exception:\n logger.warning('boundary failed', exc_info=True)\n", False), (" except Exception as exc:\n raise RuntimeError('boundary failed') from exc\n", False), (" except Exception as exc:\n logger.warning('boundary failed: %s', exc)\n", True), (" except Exception: # noqa: BLE001\n logger.warning('boundary failed')\n", True), (" except Exception: # health: allow BLE001 -- top-level boundary\n logger.warning('x')\n", False), ]) def test_ble001_fix_text_matches_the_engine(tmp_path, capsys, handler, blocks): code, out = _judge(tmp_path, capsys, {}, {"pkg/h.py": _HANDLER.format(handler)}) assert code == (1 if blocks else 0), out