test: keep cancelled embedding probe rounds incomplete
This commit is contained in:
@@ -169,3 +169,18 @@ date: 2026-09-09
|
|||||||
pi-lens 仍报非 conda 解释器缺 httpx/pytest/dotenv/pydantic 及旧 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 解释器缺 httpx/pytest/dotenv/pydantic 及旧 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=1/completed=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 failed/137 deselected,exit1;两例均先验证取消穿透、报告存在及资源关闭,再因 `completed_rounds` 实际1而期望0失败 |
|
||||||
|
| `pytest tests/unit/test_live_evidence.py -k embed_probe_report -q`,修复后 | `embed-cancel-green`:10 passed/129 deselected,exit0;覆盖原8例与新增2例 |
|
||||||
|
| `pytest tests/unit/ -q` | `embed-cancel-unit`:1385 passed,4.08秒,exit0 |
|
||||||
|
| `make check` | `embed-cancel-check`:格式/ruff通过,import-linter 1 kept/0 broken,exit0 |
|
||||||
|
|
||||||
|
既有非 conda LSP 误报继续只记录(本次额外将 `asyncio.timeout` 误判为缺属性);conda pytest 实际可执行,base RequestsDependencyWarning 未静音。此处是针对独立审查问题的实现及自验,修复后独立复核仍交父会话;不冒称审查门或版本验收已通过。原 slow 日志不改写。
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user