docs: 错误码一致性测试实施计划(TDD 单任务)
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# 错误码一致性测试实施计划(api §7 ↔ exceptions.py)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 新增回归测试解析 `docs/api-design.md` §7 表格,与 `exceptions.py` 的 error_code 双向一致验证,防文档↔代码漂移。
|
||||
|
||||
**Architecture:** 新建 `tests/test_api_design_consistency.py`,内联正则解析函数 + 4 个测试用例;零生产代码改动。断言失败带双向差异清单(DX 修正 F1/F2);0 行解析硬失败护栏(F3);契约 docstring(F4)。
|
||||
|
||||
**Tech Stack:** Python 3.11+、pytest、re、pathlib
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 项目为中文交流:注释、docstring、commit 消息用中文;标识符英文
|
||||
- 零真实网络:本任务是纯本地文件解析,不得引入网络/API Key
|
||||
- 覆盖红线:`pyproject.toml` `fail_under = 99`(当前基线 128 passed / 100.00%);本任务零生产代码,红线不受触碰
|
||||
- 全量回归:`python -m pytest -q`;聚焦:`python -m pytest tests/test_api_design_consistency.py -v`
|
||||
- 提交消息:`feat:` / `test:` / `docs:` + 简中文描述
|
||||
- 每次修改后追加 `_AI_USAGE_LOG.md`(范式步骤:"测试验证",模型:"deepseek-v4-flash-free")
|
||||
- 本仓库工作在 master 分支(项目惯例);Windows/PowerShell 环境
|
||||
- Spec: `docs/superpowers/specs/2026-08-09-error-code-consistency-test-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 新建一致性门禁测试文件
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/test_api_design_consistency.py`
|
||||
- Modify: `_AI_USAGE_LOG.md`(追加一行)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `src/genesis/inference/exceptions.py` 的 `LLMError.error_code` 类属性(基类 `None`;四子类 `LLM_TIMEOUT`/`LLM_NETWORK_ERROR`/`LLM_NOT_CONFIGURED`/`LLM_PARSE_ERROR`);`docs/api-design.md` §7 表格(L311-314 五行 LLM 行,行格式 `| \`CODE\` | 含义 | ... | exceptions.Xxx |`)
|
||||
- Produces: 无(测试文件独立,无下游依赖)
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
创建 `tests/test_api_design_consistency.py`(用例引用解析函数,解析函数尚未实现 → RED):
|
||||
|
||||
```python
|
||||
"""错误码一致性门禁: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 |
|
||||
_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 行数 === 5;新增/删除 LLM 错误码需显式更新此数
|
||||
mapping = _extract_llm_error_rows(API_DOC.read_text(encoding="utf-8"))
|
||||
assert len(mapping) == 5, (
|
||||
f"api §7 LLM 错误码行数应为 5,实际 {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 是否一致。"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行验证失败**
|
||||
|
||||
Run: `python -m pytest tests/test_api_design_consistency.py -v`
|
||||
Expected: FAIL(`ImportError: cannot import name 'LLMError' from 'genesis.inference.exceptions'` 或 `ModuleNotFoundError`——取决于包导入是否已通;若 import 正常则 4 用例中 count/guard 断言失败。**真实预期:导入路径正确则 `_extract_llm_error_rows` 已定义、用例应 PASS** ——正确行为是 TDD 反向(先红),因此若首跑即绿,说明本任务实质是「交付完整测试」,红绿灯以 Step 4 全绿为准;此步只需记录实际输出,若因实现已被写全而直接绿,在报告中注明「RED 无步骤,直接 GREEN」)
|
||||
|
||||
> 说明:本任务整体是一次性创建完整测试文件,TDD 的 RED 信号是"文件不存在时 import 失败"。若你想严格走红,可在 Step 1 先只写用例 + 在调用 `_extract_llm_error_rows` 处留 `raise NotImplementedError`,实现后再替换——不必须,直接完整创建并在 Step 2 观察实际结果即可。
|
||||
|
||||
- [ ] **Step 3: 实现(完整文件已就位)**
|
||||
|
||||
Step 1 已为完整文件的全部内容——无需额外改实现代码。若 Step 2 是 RED(import 或断言失败),对照 Step 1 代码修正后即转绿。
|
||||
|
||||
- [ ] **Step 4: 运行验证通过**
|
||||
|
||||
Run: `python -m pytest tests/test_api_design_consistency.py -v`
|
||||
Expected: PASS(4 passed)
|
||||
|
||||
- [ ] **Step 5: 全量回归**
|
||||
|
||||
Run: `python -m pytest -q`
|
||||
Expected: PASS(128 + 4 = ≥ 132 passed / 覆盖 100.00%,`fail_under=99` 达标)
|
||||
|
||||
- [ ] **Step 6: 追加 `_AI_USAGE_LOG.md`**
|
||||
|
||||
范式步骤:"测试验证",摘要:新增 api §7 ↔ 异常树 error_code 一致性门禁测试(4 用例:双向断言 + 行数/空解析护栏,防文档漂移)。
|
||||
|
||||
- [ ] **Step 7: 提交**
|
||||
|
||||
```bash
|
||||
git add tests/test_api_design_consistency.py _AI_USAGE_LOG.md
|
||||
git commit -m "test: api §7 与异常树错误码一致性门禁测试(防文档漂移)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Spec 覆盖:**
|
||||
- §3.1 落点 → Task 1 新建 `tests/test_api_design_consistency.py` ✓
|
||||
- §3.2 文档定位 → `API_DOC = Path(__file__)....parents[1] / "docs/api-design.md"` ✓
|
||||
- §3.3 解析函数 → `_extract_llm_error_rows` ✓
|
||||
- §3.4 四用例 → 测试 1(树→文档)/测试 2(文档→树)/测试 3(行数护栏)/测试 4(空解析护栏)✓
|
||||
- §3.5 失败可诊断 → `assert not missing, ( "…差异清单" )` 含双向差异 ✓
|
||||
- §3.6 契约 docstring → 文件头 docstring ✓
|
||||
- §4 错误处理 → 文档缺失 `read_text` 抛 FileNotFoundError;空解析护栏 ✓
|
||||
- §5 测试策略 → Step 4 聚焦 + Step 5 全量 ✓
|
||||
- §6 验收 1-4 → Step 4 / Step 5 / 手工破坏验证可选 §4 → Step 7 提交 ✓
|
||||
|
||||
**2. 占位符检查:** 无 TBD/TODO;测试代码全文可见。✓
|
||||
|
||||
**3. 类型一致性:** `mapping: dict[str, str]`(错误码:来源类名)在解析函数与 3 个用例间一致;`_llm_exception_classes()` 返回 `list[type[LLMError]]` 与使用处一致。✓
|
||||
|
||||
## 提交消息汇总
|
||||
|
||||
- Task 1: `test: api §7 与异常树错误码一致性门禁测试(防文档漂移)`
|
||||
Reference in New Issue
Block a user