v3.6.0 同步发版
- 交互契约改对话式询问(禁点选工具、一次1~2问、先铺垫再问),规则库 3.6 - 盘面图默认生成+chart_shot 截图;品牌区、大运/流年小卡信息补全、交流群二维码卡 - day_boundary 子正换日;memory_save --update-chart 换盘留痕;share_export 事件简介+城市级地域 - share_push 失败诊断+代理引导;_runtime_guard;修 STAR_ORDER 三奇贵人 KeyError - README 规则计数 541→542;manifest 重建(1275 文件);测试 142 项全绿
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""运行环境守卫(scripts/_runtime_guard.py):版本判定文案、import 时拦截、入口脚本接线。
|
||||
|
||||
import 时拦截用子进程模拟:伪造 sys.version_info = (3, 9, 6) 后再 import,
|
||||
守卫应打印中文报错并以退出码 3 结束进程。
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import _runtime_guard as guard
|
||||
|
||||
ENTRY_SCRIPTS = ("cast_chart.py", "chart_shot.py", "memory_lookup.py",
|
||||
"memory_save.py", "query_shensha.py", "self_update.py",
|
||||
"share_export.py", "share_push.py")
|
||||
|
||||
|
||||
def run_with_fake_py39(code: str) -> subprocess.CompletedProcess:
|
||||
"""在子进程中把 sys.version_info 伪造成 3.9.6 后执行 code。"""
|
||||
prefix = "import sys, runpy; sys.version_info = (3, 9, 6); "
|
||||
return subprocess.run([sys.executable, "-c", prefix + code],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
|
||||
|
||||
class TestVersionError:
|
||||
def test_current_interpreter_passes(self):
|
||||
assert guard.version_error() is None
|
||||
|
||||
def test_py39_denied_with_chinese_message(self):
|
||||
msg = guard.version_error((3, 9, 6))
|
||||
assert msg is not None
|
||||
assert "Python 3.10" in msg
|
||||
assert "3.9.6" in msg
|
||||
assert sys.executable in msg
|
||||
|
||||
def test_boundary_versions(self):
|
||||
assert guard.version_error((3, 10, 0)) is None
|
||||
assert guard.version_error((4, 0, 0)) is None
|
||||
assert guard.version_error((3, 8, 10)) is not None
|
||||
assert guard.version_error((2, 7, 18)) is not None
|
||||
|
||||
|
||||
class TestImportTimeGuard:
|
||||
def test_import_denied_exits_3(self):
|
||||
cp = run_with_fake_py39(
|
||||
f"sys.path.insert(0, {str(SCRIPTS)!r}); import _runtime_guard")
|
||||
assert cp.returncode == 3
|
||||
assert "Python 3.10" in cp.stderr
|
||||
|
||||
def test_import_allowed_is_silent(self):
|
||||
cp = subprocess.run(
|
||||
[sys.executable, "-c",
|
||||
f"import sys; sys.path.insert(0, {str(SCRIPTS)!r}); import _runtime_guard"],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
assert cp.returncode == 0
|
||||
assert cp.stderr == ""
|
||||
|
||||
def test_entry_script_intercepts_before_deep_error(self):
|
||||
"""chart_shot.py 在 3.9 下原本 def 期即 TypeError;接线后守卫先拦截,报错清晰。"""
|
||||
cp = run_with_fake_py39(
|
||||
f"sys.argv = ['chart_shot.py', 'x.html']; "
|
||||
f"runpy.run_path({str(SCRIPTS / 'chart_shot.py')!r}, run_name='__main__')")
|
||||
assert cp.returncode == 3
|
||||
assert "Python 3.10" in cp.stderr
|
||||
assert "TypeError" not in cp.stderr
|
||||
|
||||
|
||||
class TestEntryScriptsWired:
|
||||
def test_every_entry_script_imports_guard(self):
|
||||
for name in ENTRY_SCRIPTS:
|
||||
src = (SCRIPTS / name).read_text(encoding="utf-8")
|
||||
assert "import _runtime_guard" in src, f"{name} 未接入 _runtime_guard"
|
||||
Reference in New Issue
Block a user