Files
MemeMeow/tests/test_runtime_execution_domain.py

69 lines
3.2 KiB
Python

"""任务、图片阶段和 OpenCode 结果边界的黑盒契约测试。"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from backend.image_stage_plan import ImageStagePlan, ImageStagePlanError, image_task_requires_single_attempt
from backend.opencode_result_store import OpenCodeResultStore, ResultStoreError
from backend.runtime_execution import AttemptFence, ExecutionAttempt, ExecutionBinding, ExecutionBindingError
def test_execution_binding_fences_attempt_and_scope() -> None:
"""旧 attempt、错误 scope 和错误 generation 不能通过同一绑定。"""
binding = ExecutionBinding(
task_id="task-1",
attempt_id="attempt-1",
scope_id="scope-1",
claim_generation=3,
workspace_selector="scope-1",
)
fence = AttemptFence()
fence.bind(binding)
assert fence.accepts(binding)
assert not fence.accepts(ExecutionBinding(task_id="task-1", attempt_id="attempt-2", scope_id="scope-1", claim_generation=3, workspace_selector="scope-1"))
with pytest.raises(ExecutionBindingError):
binding.require_matches({"task_id": "task-1", "attempt_id": "attempt-1", "scope_id": "scope-2", "claim_generation": 3, "workspace_selector": "scope-1"})
def test_execution_attempt_unknown_is_terminal_and_not_replayable() -> None:
"""外部副作用状态未知时,attempt 只能收束为 unknown_execution。"""
binding = ExecutionBinding(task_id="task-1", attempt_id="attempt-1", scope_id="scope-1")
attempt = ExecutionAttempt(binding).started()
unknown = attempt.unknown("agent_executor_unavailable")
assert unknown.status == "unknown_execution"
assert unknown.terminal
with pytest.raises(ExecutionBindingError):
unknown.started()
def test_image_stage_plan_stops_after_failure_and_requires_single_attempt() -> None:
"""固定阶段不越过失败阶段,图片叶子任务使用显式新 Task 重试。"""
plan = ImageStagePlan(auto_name=False)
assert plan.next_stage({"visual": "queued"}) == "visual"
assert plan.next_stage({"visual": "succeeded", "agent": "failed", "text_embedding": "queued"}) is None
assert plan.next_stage({"visual": "succeeded", "agent": "succeeded", "text_embedding": "queued"}) == "text_embedding"
assert image_task_requires_single_attempt("meme_context_generation", submission_mode="pipeline")
assert not image_task_requires_single_attempt("cache_generation", submission_mode="pipeline")
with pytest.raises(ImageStagePlanError):
plan.can_run("unknown", {})
def test_result_store_rejects_symlink_and_accepts_atomic_json(tmp_path: Path) -> None:
"""结果 store 拒绝符号链接,并可通过专属目录原子读取 JSON。"""
store = OpenCodeResultStore(tmp_path / "results", max_bytes=1024)
draft, result = store.prepare("task-1")
result.write_text(json.dumps({"ok": True}), encoding="utf-8")
assert store.read_json(result)["ok"] is True
draft.unlink(missing_ok=True)
result.unlink(missing_ok=True)
outside = tmp_path / "outside.json"
outside.write_text("{}", encoding="utf-8")
result.symlink_to(outside)
with pytest.raises(ResultStoreError) as error:
store.read_json(result)
assert error.value.code == "agent_result_path_invalid"