Files
rippy/docker/worker/test_caps.py
T
HitonabiandClaude Opus 5 b2acddbdfa
Ampel / ampel (push) Successful in 1m27s
fix(windows): Drueberinstallieren, Linux-Reste im Windows-Betrieb, Ordner-Waehler
Vier Fragen des Commanders vom 29.08.2026, drei davon mit Codefolge.

## 1. „Was passiert wenn man die Setup.exe einfach drueber installiert?"

Bis hierher: nicht zuverlaessig. Rippy startet mit Windows, laeuft also fast
immer — dann ist `Rippy.exe` gesperrt, `shutil.copy2` warf PermissionError,
und die neue Fassung landete als `Rippy.exe.neu` daneben. Dazu die Meldung
„wird beim naechsten Start uebernommen".

**Diese Zusage hat niemand eingeloest.** `.neu` kam im ganzen Projekt genau
einmal vor: an der Stelle, die es schrieb. Wer drueberinstallierte, behielt
still die alte Fassung, und das Setup meldete Erfolg.

Jetzt wird der laufende Rippy vorher beendet (`dienst_beenden` gibt es seit
der Deinstallation und wartet auch die zwei Sekunden ab, die Windows fuer die
Dateihandles braucht). Eine von einer aelteren Setup-Fassung liegengelassene
`.neu` wird dabei uebernommen. Bleibt die Datei DANN noch gesperrt, gibt es
einen klaren Fehler statt einer Zusage — Rippy im Infobereich beenden und das
Setup erneut starten.

Der Tausch laeuft bewusst im SETUP und nicht beim Dienststart: Windows sperrt
eine laufende .exe, und `Rippy.exe` waere genau die zu ersetzende Datei.

## 3. Linux-Reste im Windows-Betrieb (Docker/Headless unveraendert)

**`caps.py`: `os.path.isdir("/app")`.** Damit hielt sich der eigenstaendige
Windows-Rippy fuer einen FREMDEN Worker — und das UI warnte vor fehlender
Pfad-Uebersetzung auf einer Maschine ohne Container und ohne Freigabe.
„Extern" heisst jetzt, was es meint: Rippy laeuft woanders als dieser Worker.

**`caps.py`: `shutil.which("makemkvcon")` + `os.path.ismount(daten_dir)`.**
Beide unter Windows immer falsch (Programme liegen nicht im PATH, ein
normaler Ordner ist kein Mount). Die Schluessel-Auskunft blieb dauerhaft
„unbekannt", obwohl MakeMKV samt Datenverzeichnis da war. Der Mount-Test
bleibt fuer den Container, wo er einen Zweck hat.

**`rohdaten.py`: `/app/temp/raw` und `/app/media` fest.** Dieses Modul findet
die Rohdaten eines Jobs wieder — fuer den Wiederholen-Dialog und fuer
„Rohdaten mitloeschen". Unter Windows fand es NIE etwas: Der Dialog meldete
„keine Rohdaten", das Aufraeumen loeschte nichts, und die Bruchstuecke eines
abgebrochenen Rips blieben liegen (bei 4K-UHD bis 100 GB).

Sieben Tests wurden dabei rot, und zwar zu Recht: Sie pruefen Container-Regeln,
liefen aber unter Windows. Die Wurzeln sind jetzt einspritzbar — beide
Betriebsfaelle auf jedem Rechner pruefbar statt vom laufenden abhaengig.

## 4. „Der Durchsuchen button fehlt. Wie es der Installer auch macht"

Neu: `OrdnerWaehler` — Pfadfeld plus „Durchsuchen …", benutzt fuer Ablage und
Arbeitsverzeichnis. Es waere der DRITTE fest eingebaute Ordner-Browser
geworden (RipTargetModal, StorageMounts); dieser hier ist wiederverwendbar.
`/browse` weiss seit dem 28.08. selbst, in welchem Betrieb es laeuft.

⚠️ Beim Einbau fiel der Import unter den Tisch. `vite` pruefte das NICHT — das
Buendel blieb byte-gleich gross, und zur Laufzeit waere es der naechste leere
Bildschirm gewesen. Aufgefallen nur, weil die erwartete Anzahl Ersetzungen
nicht stimmte. Im Browser gegengeprueft: alle sieben Laufwerke, Navigation in
D:\, keine Konsolenfehler.

843 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 14:28:01 +02:00

247 lines
9.9 KiB
Python

"""Tests der Fähigkeiten-Erkennung — reine Funktionen, keine Infrastruktur.
Die Testdaten sind ECHTE Ausgaben, am 25.07.2026 im Worker-Image der Rippy-VM
abgenommen (AGENTS Regel D). Erfundene Formate hätten hier keinen Wert: genau
daran ist die Erkennung vorher gescheitert.
"""
import caps
# Wörtlich aus `docker exec rippy-worker-1 HandBrakeCLI --help` (HandBrake im
# Worker-Image, 25.07.2026). Bemerkenswert: KEIN Hardware-Encoder dabei.
HB_HILFE_ECHT = """
-Z, --preset <string> Select preset by name
-e, --encoder <string> Select video encoder:
svt_av1
svt_av1_10bit
x264
x264_10bit
x265
x265_10bit
x265_12bit
mpeg4
mpeg2
VP8
VP9
VP9_10bit
theora
--encoder-preset <string>
Adjust video encoding settings
"""
# Wie dieselbe Stelle auf einer Maschine MIT Hardware-Encodern aussieht
# (HandBrake-Windows-Build; Struktur identisch, nur mehr Namen).
HB_HILFE_MIT_HARDWARE = """
-e, --encoder <string> Select video encoder:
x264
x265
nvenc_h264
nvenc_h265
qsv_h264
qsv_h265
--encoder-preset <string>
"""
def test_encoder_liste_wird_aus_echter_hilfe_gelesen():
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
assert encoder == [
"svt_av1", "svt_av1_10bit",
"x264", "x264_10bit",
"x265", "x265_10bit", "x265_12bit",
"mpeg4", "mpeg2", "VP8", "VP9", "VP9_10bit", "theora",
]
# Der Block endet an der nächsten Option — sie darf nicht mitgelesen werden
assert not any("encoder-preset" in e for e in encoder)
assert not any(e.startswith("-") for e in encoder)
def test_encoder_liste_ohne_handbrake_ist_leer():
assert caps.parse_encoder_liste("") == []
assert caps.parse_encoder_liste(None) == []
assert caps.parse_encoder_liste("irgendein Text ohne Encoder-Abschnitt") == []
def test_ohne_handbrake_keine_encoder():
"""Der wichtigste Fall: vorher standen cpu-x264/cpu-x265 fest verdrahtet in
der Liste. Ein Rip-Worker ohne HandBrake behauptete damit, komprimieren zu
können — und jeder Transcode dort endete sofort mit 'nicht installiert'."""
assert caps.leite_backends_ab([]) == []
assert caps.leite_backends_ab(None) == []
def test_backends_aus_echter_liste():
"""Das Worker-Image der VM kann laut eigener Auskunft keine Hardware —
entsprechend steht hier nur CPU."""
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
assert caps.leite_backends_ab(encoder) == ["cpu-x264", "cpu-x265", "cpu-av1"]
def test_handbrakes_liste_ist_die_auskunft_ohne_geraetepruefung():
"""Regel-Wechsel 25.07.2026, weil die alte Fassung einen Bug hatte.
Vorher wurde zusätzlich ein Gerät verlangt: /dev/dri für VAAPI/QSV/VCE bzw.
nvidia-smi für NVENC. Unter WINDOWS gibt es beides nicht — der PC des
Commanders (RX 9070 XT) meldete deshalb nur CPU-Encoder, obwohl HandBrakes
Windows-Build vce_*/nvenc_*/qsv_* beherrscht. Der Bug traf genau den
Anwendungsfall, für den externe Worker gedacht sind.
Die Prüfung war zudem überflüssig: HandBrake probiert Hardware beim Start
selbst an und listet nur Nutzbares (auf der VM belegt — 'qsv: not available
on this system', und qsv_* fehlt dann in --help). Wer die Liste hat, hat die
Antwort; eine zweite Meinung braucht es nicht.
"""
mit_hw = caps.parse_encoder_liste(HB_HILFE_MIT_HARDWARE)
backends = caps.leite_backends_ab(mit_hw)
assert "nvenc" in backends
assert "qsv" in backends
# Ohne Geräteprüfung — es gibt keine Parameter mehr, die das verhindern
assert caps.leite_backends_ab(mit_hw) == backends
def test_hardware_wird_nach_familie_unterschieden():
"""Vorher landete alles in „vaapi" — eine AMD-Karte lief damit unter dem
Intel-Namen, und das UI konnte nicht sagen, welches Preset passt."""
amd = caps.leite_backends_ab(["x265", "vce_h264", "vce_h265"])
assert "vce" in amd
assert "qsv" not in amd and "nvenc" not in amd
intel = caps.leite_backends_ab(["x265", "qsv_h265"])
assert "qsv" in intel and "vce" not in intel
def test_hardware_av1_bekommt_eigene_kennung():
"""AV1 in Hardware ist derzeit die beste Kombination aus Tempo und
Dateigröße — das darf nicht in „nvenc" verschwinden, sonst kann das UI es
nicht empfehlen."""
backends = caps.leite_backends_ab(["x265", "nvenc_h265", "nvenc_av1"])
assert "nvenc" in backends
assert "nvenc-av1" in backends
# Ohne AV1 in der Liste gibt es die Kennung auch nicht
assert "nvenc-av1" not in caps.leite_backends_ab(["nvenc_h265"])
def test_simd_stufe_erkennt_die_hoechste():
assert caps.beste_simd("fpu vme de sse4_2 avx avx2") == "avx2"
assert caps.beste_simd("sse4_2 avx avx2 avx512f") == "avx512f"
assert caps.beste_simd("fpu vme de pse tsc sse4_2") == "sse4_2"
assert caps.beste_simd("") == "unbekannt"
assert caps.beste_simd(None) == "unbekannt"
def test_simd_stufe_der_rippy_vm_ist_sse4_2():
"""Echt gemessen auf der Rippy-VM (25.07.2026): generisches QEMU-CPU-Modell,
`grep -c avx2 /proc/cpuinfo` = 0. Genau das macht Software-4K dort so lahm."""
flags_vm = (
"fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 "
"clflush mmx fxsr sse sse2 syscall nx lm rep_good nopl cpuid "
"pni cx16 sse4_1 sse4_2 x2apic popcnt aes hypervisor lahf_lm"
)
assert caps.beste_simd(flags_vm) == "sse4_2"
# --- Preset-Liste: die Namen, die HandBrake wirklich kennt -------------------
# Wörtlich aus `HandBrakeCLI --preset-list` im Worker-Image (26.07.2026,
# HandBrake 1.6.1) — gekürzt auf je zwei Presets pro Kategorie, Struktur und
# Einrückung unverändert. Die zwei Logzeilen oben stehen dort wirklich mit
# drin (HandBrake schreibt sie in denselben Strom) und müssen wegfallen.
HB_PRESETS_ECHT = """[10:26:05] Compile-time hardening features are enabled
[10:26:05] qsv: not available on this system
Cannot load libnvidia-encode.so.1
General/
Very Fast 2160p60 4K AV1
AV1 video (up to 2160p60) and AAC stereo audio, in an MP4
container.
Super HQ 1080p30 Surround
High quality H.264 video (up to 1080p30) and high bit rate AAC
surround audio, in an MP4 container.
Matroska/
H.265 MKV 2160p60 4K
H.265 video (up to 2160p60) and surround audio, in an MKV
container.
H.265 MKV 1080p30
H.265 video (up to 1080p30) and surround audio, in an MKV
container.
Hardware/
AV1 QSV 2160p 4K
AV1 video (up to 2160p) and surround audio, in an MP4 container.
H.265 VCN 2160p 4K
H.265 video (up to 2160p) and surround audio, in an MP4 container.
CLI Defaults/
CLI Default
"""
def test_preset_liste_kommt_aus_der_einrueckung():
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
assert presets == [
"Very Fast 2160p60 4K AV1",
"Super HQ 1080p30 Surround",
"H.265 MKV 2160p60 4K",
"H.265 MKV 1080p30",
"AV1 QSV 2160p 4K",
"H.265 VCN 2160p 4K",
"CLI Default",
]
def test_preset_liste_nimmt_keine_beschreibung_und_kein_log():
"""Beschreibungen (8 Leerzeichen), Kategorien (0) und HandBrakes eigene
Logzeilen (0) dürfen nicht als Preset-Namen durchgehen."""
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
assert not any(p.endswith("/") for p in presets)
assert not any("container" in p for p in presets)
assert not any(p.startswith("[") for p in presets)
assert "Cannot load libnvidia-encode.so.1" not in presets
def test_preset_liste_leer_bleibt_leer():
assert caps.parse_preset_liste("") == []
assert caps.parse_preset_liste(None) == []
def test_hardware_presets_stehen_auch_ohne_hardware_in_der_liste():
"""Richtigstellung zum SAVEPOINT v3.16: Dort galt es als unmöglich, die
Hardware-Preset-Namen auf der Rippy-VM zu ermitteln, weil dort kein
Hardware-Encoder läuft. Die Messung sagt das Gegenteil — `--preset-list`
nennt sie, `--help` nennt nur die nutzbaren Encoder. Zwei Fragen, zwei
Quellen."""
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
assert "H.265 VCN 2160p 4K" in presets # AMD, aus der VM-Liste
assert "AV1 QSV 2160p 4K" in presets # Intel, aus der VM-Liste
# ... und dieselbe Maschine meldet trotzdem KEINEN Hardware-Encoder:
assert caps.leite_backends_ab(caps.parse_encoder_liste(HB_HILFE_ECHT)) == [
"cpu-x264", "cpu-x265", "cpu-av1",
]
# ── „Extern" heisst: Rippy laeuft woanders (Befund 29.08.2026) ──────────
#
# Hier stand `os.path.isdir("/app")`. Im Image stimmt das. Auf einem
# Windows-PC gibt es `/app` nicht — und damit hielt sich der eigenstaendige
# Windows-Rippy fuer einen FREMDEN Worker. Folge: Das UI warnte vor fehlender
# Pfad-Uebersetzung auf einer Maschine, auf der es weder Container noch
# Freigabe gibt.
def test_im_container_ist_nichts_extern():
from caps import _extern
assert _extern({}, container=True) == "nein"
def test_die_eigenstaendige_windows_app_ist_NICHT_extern():
"""Dieser Prozess IST Rippy — die Frage ist dort gegenstandslos."""
from caps import _extern
assert _extern({"profil": "standalone"}, container=False) == "nein"
def test_ein_worker_bei_einem_docker_rippy_ist_extern():
"""Der Fall, fuer den die Auskunft gemacht ist: Er braucht RIPPY_PATH_MAP."""
from caps import _extern
assert _extern({"profil": "node", "queue": {"treiber": "celery"}},
container=False) == "ja"