Files
PolyLoop/tests/contract/conftest.py
T
iomgaa a14bf8d288 test(contract): 落成五个接缝的契约骨架,用它逼出七个设计洞
第 ④ 阶段的核心交付物。这套测试不针对任何具体实现,写的是「不管你怎么实现
都必须满足这些行为」,下游写完自己的实现接到 fixture 上跑一遍即可,
是任何新适配器的准入标准(CLAUDE.md §0)。

现在全部跳过,因为公共类型与 Protocol 还没落地。价值不在跑,在写:
写一条契约要求把每次调用逐字写出来——方法叫什么、参数填什么、返回值怎么取,
而散文里读着通顺的地方,落到这一步就露出来了。

三轮文档评审没报出的七个洞,写这套测试时全部撞了出来:
没有动作的步 result_id 填什么;三个 ActionStatus 取值的触发条件;
动作被拒绝时观察的来源(执行器与 SyntheticObservations 两处都有);
解释器能不能抛异常;Event 没有字段所以事件出口的契约只写得出一半;
read_log 读不存在的运行必须返回空日志而不是抛异常;
以及原子性与前缀持久性这两条 0005 的承诺根本没有机器兜底——
写这套测试之前我们默认它们会被契约测试接住。

洞标成 xfail(strict=True) 而不是常驻 fail:一个永远红的套件会训练所有人忽略红。
它们都不带 fixture,否则会被「实现还没有」那个跳过挡住,
于是「答不上来」就伪装成了「还没轮到」。补上之后 XPASS 会报错,逼人回来删标记。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 23:37:13 -04:00

74 lines
2.8 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.
"""契约套件的装配点。
这套测试**不针对任何具体实现**。它写的是「不管你怎么实现,都必须满足这些行为」,所以它
自己不造实现,只声明「你得提供什么」。任何一个下游写完自己的存储或适配器,把它接到这里
的 fixture 上跑一遍,全绿就算合格——这是 `CLAUDE.md` §0 说的「任何新适配器的准入标准」。
**接法**:下游在自己的 `conftest.py` 里覆盖同名 fixture,返回自己的实现。
**现在这套测试全部跳过**,因为 `polyloop` 下还没有任何公共类型与 Protocol
`research-wiki/design/0006-public-names-and-signatures.md` 还没过人类门)。跳过的理由写在
每个 fixture 里,读跳过原因就能知道缺的是哪一块。
**为什么在实现之前就写它**:写一条契约测试要求把每一次调用逐字写出来——方法叫什么、参数
填什么、返回值怎么取。散文里读着通顺的地方,落到这一步就会露出来。前三轮文档评审抓不到的
洞,几乎全是这么冒出来的。
"""
import pytest
#: 公共类型与 Protocol 落地之前,套件里的每一条都缺同一样东西。
_NOT_YET = (
"polyloop 的公共类型与 Protocol 还没落地(design/0006 待确认)。"
"这条契约要断言的行为已经写在测试的 docstring 里,落地之后去掉这个跳过即可。"
)
@pytest.fixture
def store():
"""被测的存储接缝实现。
下游覆盖这个 fixture,返回自己的实例。每次调用应当返回一个**空的**存储——套件里的每条
测试都假设自己面对一份干净的日志,共用状态会让测试之间的顺序变成隐式依赖。
"""
pytest.skip(_NOT_YET)
@pytest.fixture
def records():
"""构造各类记录的辅助工厂。
它不是被测对象,是让测试正文读得懂的一层薄封装:`records.model_call_intent(...)` 比
直接写一长串构造参数更能看出这条测试在断言什么。工厂本身由库提供,因为记录类的字段
是库的公共承诺,下游不该为了跑契约测试去手写构造。
"""
pytest.skip(_NOT_YET)
@pytest.fixture
def action_executor():
"""被测的动作执行接缝实现。"""
pytest.skip(_NOT_YET)
@pytest.fixture
def decision_parser():
"""被测的决策解释接缝实现。"""
pytest.skip(_NOT_YET)
@pytest.fixture
def model_client():
"""被测的模型调用接缝实现。
注意这一层的契约测试**不打真实网关**——那是 e2e 的事。这里断言的是返回结构体的形状
与失败时的表达方式,用一个受控的替身就能验。
"""
pytest.skip(_NOT_YET)
@pytest.fixture
def event_sink():
"""被测的事件出口实现。"""
pytest.skip(_NOT_YET)