pipeline-service/test_import_closure.py

126 lines
6.9 KiB
Python
Raw Permalink 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.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""import_closure 门禁离线单测(复刻 pbls pbl_common 半迁移事故形状)。"""
import os
import shutil
import sys
# 本地无 ahserver 等平台依赖 → 绕开包 __init__按文件直接加载被测模块
import importlib.util as _ilu
_spec = _ilu.spec_from_file_location(
"import_closure",
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"pipeline_service", "import_closure.py"))
_ic = _ilu.module_from_spec(_spec)
_spec.loader.exec_module(_ic)
check_closure_for_delivery = _ic.check_closure_for_delivery
closure_report = _ic.closure_report
ROOT = "/tmp/ic_test"
FAILS = []
def check(name, cond, detail=""):
print(("PASS" if cond else "FAIL"), name, detail[:160])
if not cond:
FAILS.append(name)
def w(path, content):
full = os.path.join(ROOT, path)
os.makedirs(os.path.dirname(full), exist_ok=True)
with open(full, "w", encoding="utf-8") as f:
f.write(content)
# ── 用例 1事故形状复刻——重写 errors 删旧符号,内部+依赖仍 import 旧名 ──
shutil.rmtree(ROOT, ignore_errors=True)
w("modules/pbl_common/pbl_common/__init__.py", "from .kernel import PblError\n")
w("modules/pbl_common/pbl_common/errors.py",
"class PBLError(Exception):\n pass\n\n\ndef err(cat, key):\n return {}\n\n\ndef fail(cat, key):\n return {}\n")
w("modules/pbl_common/pbl_common/kernel.py", "class PblError(Exception):\n code = 'X'\n")
w("modules/pbl_common/pbl_common/context.py",
"from pbl_common.errors import TenantMissingError, ErrorCode\n")
w("modules/pbl_validation/pbl_validation/__init__.py", "")
w("modules/pbl_validation/pbl_validation/api.py",
"from pbl_common.errors import fail\nfrom pbl_common.audit import write_audit\n")
# 本任务写了 pbl_common 的 errors.py重写→ 门禁应抓到 context.py 断裂
written = [os.path.join(ROOT, "modules/pbl_common/pbl_common/errors.py")]
probs, involved = check_closure_for_delivery(ROOT, written)
check("事故形状被拦截", probs is not None and len(probs) >= 2,
"problems=%s involved=%s" % (probs and len(probs), involved))
check("涉及包=pbl_common", involved == {"pbl_common"}, str(involved))
syms = {(p["file"], p["symbol"]) for p in (probs or [])}
check("抓到 context.py TenantMissingError",
("modules/pbl_common/pbl_common/context.py", "TenantMissingError") in syms, str(syms))
check("抓到依赖方 pbl_validation missing-module",
any(p["kind"] == "missing-module" and p["symbol"] == "pbl_common.audit"
for p in (probs or [])), str(probs))
rep = closure_report(ROOT, written)
check("closure_report 非空含断裂", "断裂" in rep and "ImportError" in rep, rep[:80])
# ── 用例 2未触碰的包存量断裂不拦防死锁──
w("modules/pbl_unrelated/pbl_unrelated/__init__.py", "")
w("modules/pbl_unrelated/pbl_unrelated/api.py", "from pbl_common.nothere import X\n")
probs2, involved2 = check_closure_for_delivery(ROOT, written)
check("未触碰包不在 involved", "pbl_unrelated" not in involved2, str(involved2))
# 但它 import pbl_common涉及包→ 应作为引用方被核验
check("引用涉及包的文件被核验",
any(p["file"].startswith("modules/pbl_unrelated") for p in (probs2 or [])),
str(probs2))
# ── 用例 3修复后补兼容符号门禁放行 ──
w("modules/pbl_common/pbl_common/errors.py",
"class PBLError(Exception):\n pass\n\n\ndef err(cat, key):\n return {}\n\n\ndef fail(cat, key):\n return {}\n\n\n"
"# 兼容层设计文档§5 向后兼容)\n"
"class PblError(PBLError):\n code = 'PBL_E'\n\n\nclass TenantMissingError(PblError):\n pass\n\n\nclass TenantInvalidError(PblError):\n pass\n\n\nclass ErrorCode(object):\n TENANT_MISSING = 'PBL-TENANT-0001'\n")
w("modules/pbl_common/pbl_common/audit.py", "def write_audit(*a, **kw):\n return ''\n")
# context.py 引用修复 + unrelated 的 nothere 仍在 → 只应剩 unrelated 断裂
probs3, _ = check_closure_for_delivery(ROOT, written)
remain = [p for p in (probs3 or []) if not p["file"].startswith("modules/pbl_unrelated")]
check("补兼容符号后 pbl_common 闭包干净", not remain, str(remain))
# ── 用例 4逃逸阀——__getattr__ 动态模块不误伤 ──
w("modules/pbl_dyn/pbl_dyn/__init__.py", "def __getattr__(name):\n return None\n")
w("modules/pbl_dyn/pbl_dyn/sub.py", "X = 1\n")
w("modules/pbl_validation/pbl_validation/dyn_user.py", "from pbl_dyn import WHATEVER\n")
probs4, _ = check_closure_for_delivery(
ROOT, [os.path.join(ROOT, "modules/pbl_dyn/pbl_dyn/sub.py")])
check("动态模块逃逸阀", not any("dyn" in p["file"] for p in (probs4 or [])), str(probs4))
# ── 用例 5语法坏文件跳过归空壳门禁不崩 ──
w("modules/pbl_validation/pbl_validation/broken.py", "def broken(:\n")
probs5, _ = check_closure_for_delivery(
ROOT, [os.path.join(ROOT, "modules/pbl_validation/pbl_validation/broken.py")])
check("语法坏文件不崩", probs5 is not None, str(probs5))
# ── 用例 6无 .py 写入(纯文档任务)零成本放行 ──
probs6, involved6 = check_closure_for_delivery(ROOT, [os.path.join(ROOT, "docs/x.md")])
check("纯文档任务不拦", probs6 == [] and not involved6, str((probs6, involved6)))
# ── 用例 7相对导入 + 别名 + 星号 ──
w("modules/pbl_rel/pbl_rel/__init__.py", "from .core import helper\n__all__ = ['helper']\n")
w("modules/pbl_rel/pbl_rel/core.py", "def helper():\n return 1\n")
w("modules/pbl_rel/pbl_rel/use.py", "from . import helper\nfrom .core import helper as h2\nfrom pbl_rel.core import *\n")
probs7, _ = check_closure_for_delivery(ROOT, [os.path.join(ROOT, "modules/pbl_rel/pbl_rel/use.py")])
check("相对导入/别名/星号解析", not any("pbl_rel" in p["file"] for p in (probs7 or [])), str(probs7))
w("modules/pbl_rel/pbl_rel/bad.py", "from . import NOT_THERE\n")
probs8, _ = check_closure_for_delivery(ROOT, [os.path.join(ROOT, "modules/pbl_rel/pbl_rel/bad.py")])
check("相对导入断裂也抓", any(p["symbol"] == "NOT_THERE" for p in (probs8 or [])), str(probs8))
# ── 用例 8仓库归属——无包目录的仓库modules/pbls 形态)入口断裂也拦 ──
w("modules/pbls/app/pbls.py", "from world.api import load_world\nfrom pbl_common.kernel import PblError\n")
w("modules/world/world/__init__.py", "def load_world(env=None):\n return True\n")
w("modules/world/__init__.py", "from .world import load_world\n")
probs9, involved9 = check_closure_for_delivery(
ROOT, [os.path.join(ROOT, "modules/pbls/app/pbls.py")])
check("无包仓库入口纳入核验", "pbls" in involved9, str(involved9))
check("抓到 world.api 断裂", any(p["symbol"] == "world.api" for p in (probs9 or [])),
str(probs9))
check("pbl_common.kernel 存在不误报", not any("pbl_common.kernel" in str(p) for p in (probs9 or [])),
str(probs9))
print("\nFAILS:", FAILS or "NONE")
sys.exit(1 if FAILS else 0)