Files
creator3/tests/test_engine.py
2026-07-23 16:07:36 +02:00

86 lines
3.4 KiB
Python

"""Engine-Kern: atomarer Claim, Zombie-Reset, Idempotenz, Kanten-Validierung."""
import pytest
from backend import db, engine, graph
from .conftest import topic_anlegen
def test_claim_atomar():
topic = topic_anlegen()
run_id = db.insert("runs", topic=topic, status="running")
tid = db.insert("tasks", run_id=run_id, topic=topic, knoten="suche", item="x")
assert db.execute("UPDATE tasks SET status='laufend' WHERE id=? AND "
"status='offen'", tid) == 1
# zweiter Claim verliert
assert db.execute("UPDATE tasks SET status='laufend' WHERE id=? AND "
"status='offen'", tid) == 0
def test_task_unique_idempotent():
topic = topic_anlegen()
run_id = db.insert("runs", topic=topic, status="running")
a = db.insert("tasks", ignore=True, run_id=run_id, topic=topic,
knoten="suche", item="r1:q:abc")
b = db.insert("tasks", ignore=True, run_id=run_id, topic=topic,
knoten="suche", item="r1:q:abc")
assert a is not None and b is None
# Runde im Item-Schlüssel: 2. Runde blockiert nicht
c = db.insert("tasks", ignore=True, run_id=run_id, topic=topic,
knoten="suche", item="r2:q:abc")
assert c is not None
async def test_zombie_reset_beim_start():
import asyncio
topic = topic_anlegen()
run_id = db.insert("runs", topic=topic, status="stopped")
db.insert("tasks", run_id=run_id, topic=topic, knoten="montage",
item="montage", status="laufend")
run_id2 = engine.lauf_starten(topic)
await asyncio.sleep(0.1)
t = db.one("SELECT status, fehler FROM tasks WHERE topic=? AND item='montage'",
topic)
assert t["status"] != "laufend" or t["fehler"] != "" # Zombie zurückgesetzt
engine.lauf_stoppen(topic)
with __import__("contextlib").suppress(asyncio.CancelledError):
await engine._laeufe[topic]
assert run_id2 != run_id
def test_kanten_validierung():
g = graph.get()
kn = g.knoten["suche"]
t = {"id": 1, "run_id": 1, "topic": "x", "knoten": "suche", "item": "i",
"runde": 1}
erg = engine.Ergebnis(neue_tasks=[{"knoten": "montage", "item": "m"}])
with pytest.raises(graph.GraphFehler):
engine._abschluss_schreiben(g, t, kn, erg)
async def test_infra_zaehlt_nicht_gegen_versuch():
"""LaufPause aus einem Worker setzt den Task zurück auf offen (kein versuch+1)
und pausiert den Run."""
import asyncio
async def kaputt(task):
from backend.llm import LaufPause
raise LaufPause("test")
topic = topic_anlegen()
run_id = db.insert("runs", topic=topic, status="running")
tid = db.insert("tasks", run_id=run_id, topic=topic, knoten="suche",
item="x", status="laufend", versuch=0)
g = graph.get()
alt = engine.WORKER["suche.suchen"]
engine.WORKER["suche.suchen"] = kaputt
try:
t = dict(db.one("SELECT * FROM tasks WHERE id=?", tid))
await engine._ausfuehren(g, t, g.knoten["suche"], asyncio.Semaphore(1),
asyncio.Semaphore(1), asyncio.Event())
finally:
engine.WORKER["suche.suchen"] = alt
task = db.one("SELECT * FROM tasks WHERE id=?", tid)
assert task["status"] == "offen"
assert task["versuch"] == 0 # Infra zählt nicht als Inhalts-Versuch
assert db.one("SELECT status FROM runs WHERE id=?", run_id)["status"] == "paused"