test: keep cancelled embedding probe rounds incomplete

This commit is contained in:
2026-09-09 05:14:37 -04:00
parent 7f6a824e79
commit b7e6943497
3 changed files with 44 additions and 5 deletions
@@ -169,3 +169,18 @@ date: 2026-09-09
pi-lens 仍报非 conda 解释器缺 httpxpytestdotenvpydantic 及旧 StrEnum 噪音;按任务授权记录,不添加 ignore、不改枚举、不扩环境修复范围。实际 conda 解释器为 `/home/iomgaa/miniconda3/envs/PolyGateway/bin/python`,本会话导入四依赖成功(httpx 0.28.1、pytest 9.1.1、python-dotenv 1.2.3、pydantic 2.13.4)。conda 启动器自身另有 base Python 3.13 的 RequestsDependencyWarning;未静音,不宣称输出零告警,测试进程与静态门实际退出0。 pi-lens 仍报非 conda 解释器缺 httpxpytestdotenvpydantic 及旧 StrEnum 噪音;按任务授权记录,不添加 ignore、不改枚举、不扩环境修复范围。实际 conda 解释器为 `/home/iomgaa/miniconda3/envs/PolyGateway/bin/python`,本会话导入四依赖成功(httpx 0.28.1、pytest 9.1.1、python-dotenv 1.2.3、pydantic 2.13.4)。conda 启动器自身另有 base Python 3.13 的 RequestsDependencyWarning;未静音,不宣称输出零告警,测试进程与静态门实际退出0。
本修复不补写真正缺失的历史报告、不改变 embedding 能力判据;真实服务、slow、下游与发布证据仍由后续验收负责。 本修复不补写真正缺失的历史报告、不改变 embedding 能力判据;真实服务、slow、下游与发布证据仍由后续验收负责。
## 独立审查补正:取消不计完成轮(起点 7f6a824)
独立 verifier 指出:probe 的 finally 无条件写 `completed_rounds=1`,但 CancelledError 穿透时仍是 `FAIL/轮次未完成`,分母记录自相矛盾。已对照源码并在真实消费者复现,接受该问题;上节“已收尾即完成”的措辞不适用于取消,本节修正为**取得正常成功或普通异常分类终态才算完成**,不是 finally 执行过就完成。
最小修复仅在 probe 初始化 `completed_rounds=0`,成功判定或普通异常分类返回后置1;finally 写实际计数。取消仍穿透、计数保留0,不新增捕获 BaseException、不动生产 API/版本/分类器。扩展原消费者参数化测试增加两型号取消节点:真实 task 在 MockTransport 进入等待后由调用方 cancel,断言 CancelledError 穿透、task.cancelled、报告 FAIL/未完成、planned=1completed=0、原调用关联及客户端关闭;其他8例保持完成1。所有节点只替换外部 HTTP,不联网、不付费、不跑 slow。
| 命令(pytest 前缀为 `conda run --no-capture-output -n PolyGateway` | 本轮实际证据(tests/outputs/134/,各有 .log.exit |
| --- | --- |
| `pytest tests/unit/test_live_evidence.py -k 'embed_probe_report and cancelled' -q`,修复前 | `embed-cancel-red`2 failed137 deselectedexit1;两例均先验证取消穿透、报告存在及资源关闭,再因 `completed_rounds` 实际1而期望0失败 |
| `pytest tests/unit/test_live_evidence.py -k embed_probe_report -q`,修复后 | `embed-cancel-green`10 passed129 deselectedexit0;覆盖原8例与新增2例 |
| `pytest tests/unit/ -q` | `embed-cancel-unit`1385 passed4.08秒,exit0 |
| `make check` | `embed-cancel-check`:格式/ruff通过,import-linter 1 kept0 brokenexit0 |
既有非 conda LSP 误报继续只记录(本次额外将 `asyncio.timeout` 误判为缺属性);conda pytest 实际可执行,base RequestsDependencyWarning 未静音。此处是针对独立审查问题的实现及自验,修复后独立复核仍交父会话;不冒称审查门或版本验收已通过。原 slow 日志不改写。
+4 -1
View File
@@ -53,6 +53,7 @@ async def test_probe_real_gateway_embeddings():
transport = ObservedTransport(real, capture) transport = ObservedTransport(real, capture)
run_id, parent, call_id = uuid4().hex, uuid4().hex, uuid4().hex run_id, parent, call_id = uuid4().hex, uuid4().hex, uuid4().hex
verdict = LiveVerdict("FAIL", "轮次未完成") verdict = LiveVerdict("FAIL", "轮次未完成")
completed_rounds = 0
try: try:
with capture.round_context(session_id=run_id, parent_call_id=parent): with capture.round_context(session_id=run_id, parent_call_id=parent):
try: try:
@@ -65,10 +66,12 @@ async def test_probe_real_gateway_embeddings():
assert len(events) == 1 and request_is_valid(events[0]) assert len(events) == 1 and request_is_valid(events[0])
assert result.dim > 0 and len(result.vectors) == 1 assert result.dim > 0 and len(result.vectors) == 1
verdict = LiveVerdict("PASS", "向量形状与实发请求合格") verdict = LiveVerdict("PASS", "向量形状与实发请求合格")
completed_rounds = 1
except Exception as error: except Exception as error:
verdict = classify_live_failure( verdict = classify_live_failure(
error, capture.attempts(session_id=run_id, parent_call_id=parent) error, capture.attempts(session_id=run_id, parent_call_id=parent)
) )
completed_rounds = 1
finally: finally:
write_live_round( write_live_round(
Path("tests/outputs/134/live"), Path("tests/outputs/134/live"),
@@ -79,7 +82,7 @@ async def test_probe_real_gateway_embeddings():
"requested_model": source.model, "requested_model": source.model,
"provider": source.provider, "provider": source.provider,
"planned_rounds": 1, "planned_rounds": 1,
"completed_rounds": 1, "completed_rounds": completed_rounds,
"status": verdict.status, "status": verdict.status,
"reason": verdict.reason, "reason": verdict.reason,
"session_id": run_id, "session_id": run_id,
+25 -4
View File
@@ -692,6 +692,7 @@ async def test_round_consumer_keeps_first_success_when_second_assertion_fails(tm
("503", "FAIL", "TransientError"), ("503", "FAIL", "TransientError"),
("404", "UNCOVERED", "RequestRejectedError"), ("404", "UNCOVERED", "RequestRejectedError"),
("request_error", "FAIL", "TransientError"), ("request_error", "FAIL", "TransientError"),
("cancelled", "FAIL", None),
], ],
) )
async def test_embed_probe_report_keeps_actual_source_and_round_identity( async def test_embed_probe_report_keeps_actual_source_and_round_identity(
@@ -731,18 +732,22 @@ async def test_embed_probe_report_keeps_actual_source_and_round_identity(
original_factory = LiveCapture.client_factory original_factory = LiveCapture.client_factory
clients = [] clients = []
calls = [] calls = []
reached = asyncio.Event()
def factory(capture, source): def factory(capture, source):
"""保留真实取证 hooks、源与逻辑 ID,只隔离网络出口。""" """保留真实取证 hooks、源与逻辑 ID,只隔离网络出口。"""
client = original_factory(capture, source) client = original_factory(capture, source)
def handler(request): async def handler(request):
"""提供完整成功/错误样本,敏感回显不得进入报告。""" """提供完整成功/错误样本,敏感回显不得进入报告。"""
payload = json.loads(request.content) payload = json.loads(request.content)
assert payload["model"] == model == source.model assert payload["model"] == model == source.model
assert source.model != env["LLM__MINIMAX__1__MODEL"] assert source.model != env["LLM__MINIMAX__1__MODEL"]
assert request.headers["Authorization"] == f"Bearer {_SECRET}" assert request.headers["Authorization"] == f"Bearer {_SECRET}"
calls.append((source, capture._round.get(), capture._attempt.get().call_id)) calls.append((source, capture._round.get(), capture._attempt.get().call_id))
if outcome == "cancelled":
reached.set()
await asyncio.Future()
if outcome == "request_error": if outcome == "request_error":
raise httpx.ConnectError(_SECRET + _PROMPT, request=request) raise httpx.ConnectError(_SECRET + _PROMPT, request=request)
if outcome == "success": if outcome == "success":
@@ -765,7 +770,19 @@ async def test_embed_probe_report_keeps_actual_source_and_round_identity(
monkeypatch.setattr(LiveCapture, "client_factory", factory) monkeypatch.setattr(LiveCapture, "client_factory", factory)
probe = namespace["test_probe_real_gateway_embeddings"] probe = namespace["test_probe_real_gateway_embeddings"]
if status == "PASS": if outcome == "cancelled":
task = asyncio.create_task(probe())
try:
async with asyncio.timeout(5):
await reached.wait()
task.cancel()
with pytest.raises(asyncio.CancelledError):
await task
assert task.cancelled()
finally:
task.cancel()
await asyncio.gather(task, return_exceptions=True)
elif status == "PASS":
await probe() await probe()
elif status == "UNCOVERED": elif status == "UNCOVERED":
with pytest.raises(pytest.skip.Exception): with pytest.raises(pytest.skip.Exception):
@@ -785,7 +802,10 @@ async def test_embed_probe_report_keeps_actual_source_and_round_identity(
assert row["status"] == status assert row["status"] == status
assert row["requested_model"] == source.model == model assert row["requested_model"] == source.model == model
assert row["provider"] == source.provider == "minimax" assert row["provider"] == source.provider == "minimax"
assert row["planned_rounds"] == row["completed_rounds"] == 1 assert row["planned_rounds"] == 1
assert row["completed_rounds"] == (0 if outcome == "cancelled" else 1)
if outcome == "cancelled":
assert row["reason"] == "轮次未完成"
assert row["session_id"] == paths[0].parent.name == session_id assert row["session_id"] == paths[0].parent.name == session_id
assert row["parent_call_id"] == parent_call_id assert row["parent_call_id"] == parent_call_id
assert len({session_id, parent_call_id, call_id}) == 3 assert len({session_id, parent_call_id, call_id}) == 3
@@ -804,12 +824,13 @@ async def test_embed_probe_report_keeps_actual_source_and_round_identity(
"503": 503, "503": 503,
"404": 404, "404": 404,
"request_error": 0, "request_error": 0,
"cancelled": 0,
}[outcome] }[outcome]
) )
if outcome in {"503", "404"}: if outcome in {"503", "404"}:
assert event["error_body_complete"] is True assert event["error_body_complete"] is True
assert event["machine_type"] == "model_not_found" assert event["machine_type"] == "model_not_found"
elif outcome == "request_error": elif outcome in {"request_error", "cancelled"}:
assert "无可配对响应" in row["evidence_notes"] assert "无可配对响应" in row["evidence_notes"]