Files
rippy/docker/worker/test_verwaltung.py
T
Hitonabi 2a90538473
Ampel / ampel (push) Successful in 30s
fix: Deinstaller, Verwaltungsfenster, Dashboard-Widerspruch, Mount-Tempo
Vier Meldungen des Commanders, alle nachgemessen.

1. DEINSTALLER LIEF NICHT MEHR - reproduziert mit echtem PowerShell:

     Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.

   `Add-Type -AssemblyName System.Windows.Forms` stand EINE ZEILE ZU SPAET, die
   MessageBox wurde davor benutzt. Der Deinstaller starb also in seiner ersten
   Arbeitszeile, jedes Mal. Zwei weitere Maengel gleich mit:
   Kodierung war ASCII trotz Umlauten, und `Remove-Item -Recurse -Force
   $PSScriptRoot` loescht den Ordner, in dem das laufende Skript liegt - das
   klappt auf Windows nicht zuverlaessig (venv-DLLs sind geladen). Jetzt raeumt
   ein losgeloestes cmd nach, sobald PowerShell weg ist. Der erzeugte Deinstaller
   ist gegengeprueft: parst, BOM da, Umlaute intakt.

2. VERWALTUNGSFENSTER (Doppelklick aufs Tray). Zeigt Status, Aufgaben und Log,
   plus Knoepfe fuer Rippy, Log-in-Rippy und Deinstallieren - Deinstallieren geht
   damit auch aus dem Tray-Menue. Eigener PROZESS statt Fenster im Tray, weil
   pystray und tkinter beide den Haupt-Thread wollen; tkinter statt WinForms,
   weil es bei jeder Windows-Python-Installation dabei ist. Headless gerendert
   und angesehen. Alles Fachliche kommt von Rippy (/capabilities, /jobs), damit
   dort nicht eine zweite, abweichende Wahrheit steht.

3. DASHBOARD-WIDERSPRUCH. Oben stand "Akira im Laufwerk erkannt", die
   Server-Status-Karte gleichzeitig "Bereit - keine Disc in Arbeit / Disc
   einlegen". Zwei Aussagen, ein Blick. Die Karte fragt jetzt /devices und sagt
   "Disc erkannt - wartet auf Rippen starten" samt Titel.

4. MOUNT-TEMPO. Der Commander: "150 Sekunden? Das ist verrueckt langsam." Recht
   hat er. Gemessen ging die Zeit fast komplett in einen FEHLVERSUCH: `umount -l`
   ist lazy, der Abbau passiert spaeter, und wer direkt danach mountet, riskiert
   dass der Abbau hinter dem neuen Mount landet. Der erste Reparaturversuch
   scheiterte dadurch regelmaessig - und der zweite kostete 30 s mount-Timeout
   plus Pruefungen. Jetzt werden 1,5 s auf den Abbau gewartet, bevor neu
   gemountet wird; die Wache prueft erstmals nach 3 s statt 10 s und benutzt zum
   ERKENNEN die einfache schnelle Probe (die Doppelprobe steckt dort, wo der
   Wettlauf lauert: direkt nach dem Mount).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:52:53 +02:00

124 lines
4.3 KiB
Python

"""Tests des Verwaltungsfensters — nur die Logik, nicht die Oberfläche.
Die Oberfläche selbst (tkinter) ist ausgenommen; geprüft wird, was das Fenster
ANZEIGT: Zustand des Workers, Aufgabentexte, Log-Ausschnitt.
"""
import verwaltung
# --- Was Rippy über diesen Worker weiß --------------------------------------
CAPS = {"workers": [
{"name": "rippy-hauptworker", "online": True, "encoders": ["cpu-x265"],
"info": {"cpu_kerne": "4"}},
{"name": "tobisnicerpc", "online": False, "encoders": ["cpu-x265", "vce"],
"info": {"cpu_modell": "AMD Ryzen 7 9700X", "cpu_kerne": "16",
"cpu_simd": "avx512f"}},
]}
def test_zustand_findet_den_eigenen_worker():
z = verwaltung.worker_zustand(CAPS, "tobisnicerpc")
assert z["bekannt"] is True
assert z["online"] is False
assert z["encoders"] == ["cpu-x265", "vce"]
assert z["info"]["cpu_simd"] == "avx512f"
def test_zustand_bei_unbekanntem_worker():
z = verwaltung.worker_zustand(CAPS, "gibt-es-nicht")
assert z == {"bekannt": False, "online": False, "encoders": [], "info": {}}
def test_zustand_ohne_antwort_von_rippy():
"""Rippy nicht erreichbar → das Fenster muss trotzdem etwas sagen können."""
assert verwaltung.worker_zustand(None, "x")["bekannt"] is False
assert verwaltung.worker_zustand({}, "x")["bekannt"] is False
# --- Die Statuszeile --------------------------------------------------------
def test_status_nennt_das_wirkliche_problem():
"""Drei Lagen, drei klare Sätze — nicht ein „unbekannt" für alles."""
unbekannt = verwaltung.worker_zustand(CAPS, "gibt-es-nicht")
text, farbe = verwaltung.status_text(unbekannt, None)
assert "kennt diesen Worker nicht" in text
assert farbe == verwaltung.ROSE
offline = verwaltung.worker_zustand(CAPS, "tobisnicerpc")
text, farbe = verwaltung.status_text(offline, None)
assert "NICHT erreichbar" in text
assert farbe == verwaltung.ROSE
online = verwaltung.worker_zustand(CAPS, "rippy-hauptworker")
text, farbe = verwaltung.status_text(online, None)
assert "bereit" in text
assert farbe == verwaltung.EMERALD
def test_status_zeigt_den_laufenden_job():
online = verwaltung.worker_zustand(CAPS, "rippy-hauptworker")
job = {"title": "Akira", "status": "transcoding", "progress": 42,
"eta_text": "noch ca. 3 h 07 min"}
text, farbe = verwaltung.status_text(online, job)
assert "Akira" in text and "42 %" in text and "3 h 07 min" in text
assert farbe == verwaltung.AMBER
# --- Aufgabenliste ----------------------------------------------------------
def test_job_text_uebersetzt_die_status_woerter():
"""„transcoding" sagt einem Nicht-Entwickler nichts."""
assert verwaltung.job_text(
{"title": "Akira", "status": "transcoding", "progress": 7}
) == "Akira — komprimiert, 7 %"
assert verwaltung.job_text(
{"title": "Alien", "status": "completed"}) == "Alien — fertig"
assert verwaltung.job_text(
{"title": "Alien", "status": "failed"}) == "Alien — Fehler"
assert verwaltung.job_text(
{"title": "Alien", "status": "pending"}) == "Alien — wartet"
def test_job_text_ohne_titel_nimmt_die_kurze_id():
text = verwaltung.job_text(
{"id": "95afdc89-2426-4d44", "status": "failed"})
assert text == "95afdc89 — Fehler"
def test_job_text_haengt_keine_restzeit_an_fertige_jobs():
text = verwaltung.job_text(
{"title": "X", "status": "completed", "progress": 100,
"eta_text": "noch ca. 5 min"})
assert "5 min" not in text
assert "%" not in text
# --- Log-Ausschnitt ---------------------------------------------------------
def test_letzte_zeilen_schneidet_hinten_ab():
import io as _io
inhalt = "\n".join(f"Zeile {i}" for i in range(500))
zeilen = verwaltung.letzte_zeilen(
"egal", anzahl=5, oeffnen=lambda p: _io.StringIO(inhalt))
assert zeilen == ["Zeile 495", "Zeile 496", "Zeile 497", "Zeile 498", "Zeile 499"]
def test_letzte_zeilen_ohne_datei_ist_leer():
def kaputt(p):
raise OSError("keine Datei")
assert verwaltung.letzte_zeilen("gibts-nicht", oeffnen=kaputt) == []
def test_hole_ohne_adresse_fragt_nicht():
"""Ohne RIPPY_TRAY_HOST darf nichts versucht werden — sonst hängt das
Fenster beim Öffnen am Timeout."""
assert verwaltung.hole("/jobs", host="") is None