Files
2026Technology-Competition/tests/test_api_design_consistency.py
T

85 lines
3.5 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.
"""错误码一致性门禁:api-design §7 ↔ 异常树 error_code 的机器契约。
变更错误码必须两处同步修改:api-design.md §7 错误码表
与 src/genesis/inference/exceptions.py 的 error_code 类属性。
本测试防文档漂移:文档多/少/错一行都会变红,且失败信息给出差异清单。
"""
from __future__ import annotations
import re
from pathlib import Path
from genesis.inference.exceptions import LLMError
# 项目根定位(与 tests/test_real_samples.py:7 同款先例;不依赖 CWD
API_DOC = Path(__file__).resolve().parents[1] / "docs/api-design.md"
# §7 表格 LLM 行:| `CODE` | 语义 | ... | exceptions.Xxx |
# 当前全文档仅 §7 含 exceptions. 行,此正则隐式限域;若他处新增类似行需加小节锚定。
_LLM_ROW_RE = re.compile(
r"^\|\s*`([A-Z][A-Z_]+)`\s*\|.*\|\s*exceptions\.([A-Za-z_]+)\s*\|$"
)
def _extract_llm_error_rows(document: str) -> dict[str, str]:
"""解析 §7 表格中来源列以 exceptions. 开头的行,返回 {错误码: 来源类名}。
非 LLM 行(来源列为 "—")被天然排除。
"""
mapping: dict[str, str] = {}
for line in document.splitlines():
m = _LLM_ROW_RE.match(line.strip())
if m:
mapping[m.group(1)] = m.group(2)
return mapping
def _llm_exception_classes() -> list[type[LLMError]]:
"""error_code 非 None 的全部 LLM 异常子类(不硬编码清单,新增异常自动纳入)。"""
return sorted(
(cls for cls in LLMError.__subclasses__() if cls.error_code is not None),
key=lambda cls: cls.__name__,
)
def test_error_doc_lists_every_llm_error_code():
# 异常树 → 文档:每个子类 error_code 必须登记在 §7,且来源类名匹配
mapping = _extract_llm_error_rows(API_DOC.read_text(encoding="utf-8"))
missing = {
cls.__name__: cls.error_code
for cls in _llm_exception_classes()
if cls.error_code not in mapping or mapping[cls.error_code] != cls.__name__
}
assert not missing, (
"异常类存在文档未登记或来源类名失配的 error_code(请同步 api-design.md §7):\n"
+ repr(missing)
)
def test_error_doc_llm_rows_resolve_to_exception():
# 文档 → 异常树:每个 LLM 行都要能解析到同码同名的异常类
mapping = _extract_llm_error_rows(API_DOC.read_text(encoding="utf-8"))
by_code = {cls.error_code: cls.__name__ for cls in _llm_exception_classes()}
unresolved = {code: name for code, name in mapping.items() if by_code.get(code) != name}
assert not unresolved, (
"§7 LLM 行解析不到对应异常类(请核对来源列或错误码名):\n"
+ repr(unresolved)
)
def test_error_doc_stable_llm_row_count():
# 格式护栏:LLM 行数 === 4(与 _llm_exception_classes 子类数一致,现为 4
# 新增/删除 LLM 错误码需显式更新此数
mapping = _extract_llm_error_rows(API_DOC.read_text(encoding="utf-8"))
assert len(mapping) == 4, (
f"api §7 LLM 错误码行数应为 4,实际 {len(mapping)}"
"新增/删除错误码须同步更新此计数。"
)
def test_error_doc_parse_guard_prevents_silent_noop():
# 格式护栏:解析结果为空须硬失败(表格格式变更会静默失效,这里立即暴露)
mapping = _extract_llm_error_rows(API_DOC.read_text(encoding="utf-8"))
assert mapping, "api §7 表格解析结果为空:若无匹配行,请检查表格格式与 _LLM_ROW_RE 是否一致。"