Files
Jyotisha/tests/test_python_hygiene_budget.py
T

75 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Budgets that only allow the silent-except and scripts-root counts to fall."""
from __future__ import annotations
import ast
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
# Measured at the end of TASK-python-hygiene-20261013. New silent handlers
# must log or carry ``# noqa: BLE001`` with a reason. New research scripts
# belong in scripts/audit or scripts/research.
# 原值:0
# 新值:1
# 原因:产品 2026-10-18(issue #81 选 A)要求 classical_astrology.py 保持上游原文。
# 其中 scripts/classical_astrology.py:12558 是 except Exception: continue。本单不得改这个文件。
# 本地新写的静默处理仍不得出现;允许的命中只有这一处。
# 原值(2):1 处(只有 classical_astrology.py:12558)
# 新值(2):仍 1 处。上游 professional_parity_closure.py(含 4 处静默处理)原样放在 scripts/research/,不在本预算统计内
# 原因(2):BUG-1698 产品 2026-11-07:放在根目录会被引擎可选导入、替换大运表并回退 BUG-1674
UPSTREAM_SILENT_EXCEPTS = [
"scripts/classical_astrology.py:12558",
]
SILENT_EXCEPT_BUDGET = len(UPSTREAM_SILENT_EXCEPTS)
UPSTREAM_SILENT_EXCEPT = "scripts/classical_astrology.py:12558"
# 原值:448 → 452(issue #94:上游原样文件按上游路径放根目录;本地新写的文件仍不得增加)
# 新值:441
# 原因:issue #138 删除 11 个只被自己的测试引用的 closure 类台账脚本,预算同步下调
# 原值(2):441
# 新值(2):475(相对 staging 新增 54 个:50 个占星师上游原样文件按上游路径放根目录;本地 4 个:
# shadbala_chesta_profiles、ashtakavarga_profiles、book_rule_layer(原文件冻结 ERR-110 只能另起)与 upstream_technique_layers(16 项上游技法的接入层))
# 原因(2):产品 2026-11-07 完整运行占星师 skill:BUG-1698 按上游路径原样导入她 SKILL.md / 子 skill 点名的根目录脚本;
# 原值(3):475 / 新值(3):476 / 原因(3):占星师上游原样 prediction_verdict_matrix.py(报告导出的校时判决矩阵,按上游路径放根目录)
ROOT_PY_BUDGET = 476
def _silent_handlers() -> list[str]:
hits: list[str] = []
paths = list(SCRIPTS.glob("*.py")) + [ROOT / "mcp_server.py"]
for path in paths:
source = path.read_text(encoding="utf-8")
lines = source.splitlines()
tree = ast.parse(source)
for node in ast.walk(tree):
if not isinstance(node, ast.ExceptHandler):
continue
typ = node.type
broad = typ is None or (isinstance(typ, ast.Name) and typ.id in {"Exception", "BaseException"})
if not broad or len(node.body) != 1:
continue
stmt = node.body[0]
silent = isinstance(stmt, (ast.Pass, ast.Continue)) or (
isinstance(stmt, ast.Return)
and (stmt.value is None or (isinstance(stmt.value, ast.Constant) and stmt.value.value is None))
)
if not silent:
continue
segment = "\n".join(lines[node.lineno - 1: node.end_lineno])
if "noqa: BLE001" in segment:
continue
hits.append(f"{path.relative_to(ROOT).as_posix()}:{node.lineno}")
return hits
def test_silent_except_budget_does_not_grow() -> None:
hits = _silent_handlers()
assert sorted(hits) == sorted(UPSTREAM_SILENT_EXCEPTS)
assert len(hits) <= SILENT_EXCEPT_BUDGET, hits
def test_scripts_root_py_count_does_not_grow() -> None:
count = len(list(SCRIPTS.glob("*.py")))
assert count <= ROOT_PY_BUDGET, count