86 lines
3.4 KiB
Python
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"
|