User 25.09.: „Warum kann ich Modell-Tests nicht selbst anstoßen, sondern muss immer auf die Box warten?“ Nachts testet das Radar weiter selbst. Tagsüber jetzt auch per Knopf – aber nur, wenn Kandidat, Warm-Set UND der heutige Coder zusammen unter die 115-GB-Grenze passen (der Test-Wächter stoppt den Kandidaten erst bis zu 10 s, nachdem der Coder zu laden beginnt), und nicht 00:00–03:30. Sonst steht der Grund beim Kandidaten. Test als Auftrag (radar_lauf.py --test), höchstens 2 h, dieselben Leitplanken wie nachts; jedes Ergebnis kommt als Meldung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
6.1 KiB
Python
130 lines
6.1 KiB
Python
"""„Jetzt suchen“ im Radar als Auftrag (24.09.2026): Die Route antwortet sofort mit einer Auftrags-ID, gesucht wird
|
|
in radar_lauf.py --nur-suche. Vorher lief die Suche synchron in der Anfrage und konnte Minuten dauern."""
|
|
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
import radar_lauf
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from routers import radar as radar_router
|
|
from services import radar
|
|
|
|
|
|
@pytest.fixture
|
|
def auftraege(monkeypatch):
|
|
"""Job-Engine-Attrappe: merkt sich den Start; läuft schon einer, kommt der zurück."""
|
|
stand: dict = {"gestartet": [], "laeuft": None}
|
|
|
|
def start_job_exklusiv(gruppe, args, label, **kw):
|
|
if stand["laeuft"]:
|
|
return None, stand["laeuft"]
|
|
stand["gestartet"].append({"gruppe": gruppe, "args": args, "label": label, **kw})
|
|
return "job123", None
|
|
|
|
monkeypatch.setattr(radar.jobengine, "start_job_exklusiv", start_job_exklusiv)
|
|
return stand
|
|
|
|
|
|
def test_suche_startet_einen_auftrag(auftraege):
|
|
ergebnis = radar.suche_starten()
|
|
assert ergebnis["ok"] is True and ergebnis["job_id"] == "job123" and "Aufträge" in ergebnis["text"]
|
|
(start,) = auftraege["gestartet"]
|
|
assert start["gruppe"] == "radar" and start["zeitlimit_s"] == radar.SUCHE_ZEITLIMIT_S
|
|
assert start["args"][0] == sys.executable
|
|
assert start["args"][1].endswith("radar_lauf.py") and start["args"][2:] == ["--nur-suche"]
|
|
|
|
|
|
def test_laufende_suche_wird_nicht_doppelt_gestartet(auftraege):
|
|
auftraege["laeuft"] = {"id": "job-alt", "state": "running"}
|
|
ergebnis = radar.suche_starten()
|
|
assert (ergebnis["job_id"], ergebnis["laeuft_schon"]) == ("job-alt", True)
|
|
assert auftraege["gestartet"] == []
|
|
|
|
|
|
def test_route_antwortet_sofort_ohne_selbst_zu_suchen(auftraege, monkeypatch):
|
|
def nicht_hier():
|
|
raise AssertionError("Die Route darf nicht selbst suchen.")
|
|
|
|
monkeypatch.setattr(radar, "suche", nicht_hier)
|
|
app = FastAPI()
|
|
app.include_router(radar_router.router)
|
|
antwort = TestClient(app).post("/api/radar/suche")
|
|
assert antwort.status_code == 200 and antwort.json()["job_id"] == "job123"
|
|
|
|
|
|
@pytest.mark.parametrize("ergebnis, code, zeile", [
|
|
({"neu": ["Ornith 1.5"], "wartend": 2, "quellen": {"watchlist": True, "discover": True}}, 0,
|
|
"Suche fertig: 1 neu (Ornith 1.5), 2 warten auf den Nachttest."),
|
|
({"neu": [], "wartend": 1, "quellen": {"watchlist": True, "discover": False}}, 0,
|
|
"Suche fertig: nichts Neues, 1 warten auf den Nachttest, Hugging Face war nicht erreichbar."),
|
|
({"neu": [], "wartend": 0, "quellen": {"watchlist": False, "discover": False}}, 1,
|
|
("Suche fertig: nichts Neues, 0 warten auf den Nachttest, Hugging Face war nicht erreichbar, "
|
|
"die Merkliste war nicht lesbar.")),
|
|
])
|
|
def test_nur_suche_sagt_das_ergebnis_in_der_letzten_zeile(monkeypatch, capsys, ergebnis, code, zeile):
|
|
monkeypatch.setattr(radar_lauf.radar, "suche", lambda: ergebnis)
|
|
assert radar_lauf.nur_suche() == code
|
|
assert capsys.readouterr().out.strip().splitlines()[-1] == zeile
|
|
|
|
|
|
def test_nur_suche_bei_absturz(monkeypatch, capsys):
|
|
def kaputt():
|
|
raise OSError("Platte voll")
|
|
|
|
monkeypatch.setattr(radar_lauf.radar, "suche", kaputt)
|
|
assert radar_lauf.nur_suche() == 1
|
|
assert capsys.readouterr().out.strip().endswith("Die Suche ist gescheitert: OSError: Platte voll")
|
|
|
|
|
|
# --- „Jetzt testen“ (seit 25.09.2026) ------------------------------------------------------------------------
|
|
|
|
@pytest.fixture
|
|
def radarstand(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(radar, "STORE_PATH", tmp_path / "mc2-radar.json")
|
|
monkeypatch.setattr(radar, "RADAR_DIR", tmp_path / "radar")
|
|
(tmp_path / "radar").mkdir()
|
|
stand = radar._leer()
|
|
# Zahlen vom 25.09.: Ornith (~27 GB mit KV-Cache) passt tagsüber, Flash-Next (84,6 GB) nur nachts.
|
|
stand["kandidaten"]["ornith"] = {"id": "ornith", "name": "Ornith-1.5-35B-A3B", "rolle": "hirn", "status": "neu",
|
|
"passt": True, "eng": False, "bedarf_gb": 27.4, "groesse_gb": 22.6}
|
|
stand["kandidaten"]["flash"] = {"id": "flash", "name": "Qwen3.8-Flash-Next", "rolle": "coder", "status": "wartet",
|
|
"passt": True, "eng": True, "bedarf_gb": 84.6, "groesse_gb": 82.9}
|
|
stand["kandidaten"]["alt"] = {"id": "alt", "name": "Alt", "rolle": "hirn", "status": "durchgefallen",
|
|
"passt": True, "bedarf_gb": 20.0}
|
|
radar._speichere(stand)
|
|
return stand
|
|
|
|
|
|
MITTAG = datetime(2026, 9, 25, 13, 0, tzinfo=radar.LOCAL_TZ)
|
|
NACHT = datetime(2026, 9, 26, 1, 15, tzinfo=radar.LOCAL_TZ)
|
|
|
|
|
|
def test_jetzt_testbar_nur_wenn_auch_der_coder_noch_passt(radarstand):
|
|
k = radarstand["kandidaten"]
|
|
assert radar.jetzt_testbar(k["ornith"], MITTAG) == (True, None)
|
|
testbar, grund = radar.jetzt_testbar(k["flash"], MITTAG)
|
|
assert not testbar and grund.startswith("Zu groß für einen Test tagsüber: braucht ~84,6 GB")
|
|
assert radar.jetzt_testbar(k["alt"], MITTAG) == (False, None) # schon beurteilt
|
|
testbar, grund = radar.jetzt_testbar(k["ornith"], NACHT)
|
|
assert not testbar and "testet das Radar selbst" in grund
|
|
oeffentlich = {x["id"]: x for x in radar.uebersicht(MITTAG)["kandidaten"]}
|
|
assert oeffentlich["ornith"]["testbar_jetzt"] is True and oeffentlich["flash"]["testbar_jetzt"] is False
|
|
|
|
|
|
def test_jetzt_testen_startet_einen_auftrag(radarstand, auftraege, monkeypatch):
|
|
monkeypatch.setattr(radar, "jetzt", lambda: MITTAG)
|
|
ergebnis = radar.test_starten("ornith")
|
|
assert ergebnis["ok"] and ergebnis["job_id"] == "job123"
|
|
start = auftraege["gestartet"][0]
|
|
assert start["gruppe"] == "radar" and start["args"][1:] == [str(radar._LAUF_SKRIPT), "--test", "ornith"]
|
|
assert start["label"] == "Modell-Radar: Test von Ornith-1.5-35B-A3B"
|
|
zu_gross = radar.test_starten("flash")
|
|
assert zu_gross["ok"] is False and zu_gross["status"] == 409 and "Zu groß" in zu_gross["detail"]
|
|
assert radar.test_starten("gibt-es-nicht")["status"] == 404
|
|
app = FastAPI()
|
|
app.include_router(radar_router.router)
|
|
antwort = TestClient(app).post("/api/radar/flash/testen")
|
|
assert antwort.status_code == 409 and "Zu groß" in antwort.json()["detail"]
|