"""GPU-Erkennung/-Maske und das daraus abgeleitete TTS-Menü in /api/config (offline).""" import asyncio import os from fastapi.testclient import TestClient import app.capabilities as cap import app.api.config as cfgmod from app.config import settings, _parse_gpu_indices, _apply_gpu_visibility from app.main import app client = TestClient(app) def _gpus(*indices): async def fake(): return [ {"index": i, "name": f"GPU{i}", "used_mib": 0, "total_mib": 1, "percent": 0} for i in indices ] return fake # ── Maske/Parsing ──────────────────────────────────────────────────────────── def test_parse_gpu_indices(): assert _parse_gpu_indices("0, 2 ,1") == [0, 1, 2] assert _parse_gpu_indices("") == [] assert _parse_gpu_indices("auto") == [] def test_apply_gpu_visibility_none(monkeypatch): monkeypatch.setattr(settings, "gpus", "none") monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False) _apply_gpu_visibility() assert os.environ["CUDA_VISIBLE_DEVICES"] == "" def test_apply_gpu_visibility_explicit_sets_cuda(monkeypatch): monkeypatch.setattr(settings, "gpus", "2,0") monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False) _apply_gpu_visibility() assert os.environ["CUDA_VISIBLE_DEVICES"] == "0,2" def test_apply_gpu_visibility_auto_keeps_existing(monkeypatch): monkeypatch.setattr(settings, "gpus", "auto") monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "1") _apply_gpu_visibility() assert os.environ["CUDA_VISIBLE_DEVICES"] == "1" # unveraendert # ── resolve_gpus ───────────────────────────────────────────────────────────── def test_resolve_gpus_none(monkeypatch): monkeypatch.setattr(settings, "gpus", "none") assert asyncio.run(cap.resolve_gpus()) == [] def test_resolve_gpus_auto_all(monkeypatch): monkeypatch.setattr(settings, "gpus", "auto") monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False) monkeypatch.setattr(cap, "detect_physical_gpus", _gpus(0, 1)) assert [g["index"] for g in asyncio.run(cap.resolve_gpus())] == [0, 1] def test_resolve_gpus_explicit_subset(monkeypatch): monkeypatch.setattr(settings, "gpus", "0,2") monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False) monkeypatch.setattr(cap, "detect_physical_gpus", _gpus(0, 1, 2)) assert [g["index"] for g in asyncio.run(cap.resolve_gpus())] == [0, 2] def test_resolve_gpus_intersects_cuda_mask(monkeypatch): monkeypatch.setattr(settings, "gpus", "auto") monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "1") monkeypatch.setattr(cap, "detect_physical_gpus", _gpus(0, 1)) assert [g["index"] for g in asyncio.run(cap.resolve_gpus())] == [1] # ── /api/config: Menü-Sichtbarkeit + gpus ──────────────────────────────────── def _patch_probes(monkeypatch, *, chatterbox, openrouter, cartesia, gpus): async def _t(): return True async def _f(): return False async def _gpu(): return [{"index": i, "name": f"GPU{i}"} for i in gpus] monkeypatch.setattr(cfgmod, "_chatterbox_reachable", _t if chatterbox else _f) monkeypatch.setattr(cfgmod, "_openrouter_tts_available", _t if openrouter else _f) monkeypatch.setattr(cfgmod, "_cartesia_available", _t if cartesia else _f) monkeypatch.setattr(cfgmod, "resolve_gpus", _gpu) monkeypatch.setattr(cfgmod, "_CACHE", None) # Cache je Test zuruecksetzen monkeypatch.setattr(cfgmod, "_CACHE_TS", 0.0) def test_config_tts_menu_hides_unavailable(monkeypatch): _patch_probes(monkeypatch, chatterbox=False, openrouter=False, cartesia=True, gpus=[]) data = client.get("/api/config").json() menu = {m["provider"]: m for m in data["tts_menu"]} assert menu["piper"]["visible"] is True # garantierter Default assert menu["chatterbox"]["visible"] is False # nicht erreichbar -> aus assert menu["openrouter"]["visible"] is False # kein TTS-Modell -> aus assert menu["cartesia"]["visible"] is True assert menu["device"]["client_only"] is True # Mobil-Regel macht das Frontend assert data["available"]["gpus"] == [] def test_config_reports_gpus(monkeypatch): _patch_probes(monkeypatch, chatterbox=True, openrouter=True, cartesia=True, gpus=[0, 1]) data = client.get("/api/config").json() assert [g["index"] for g in data["available"]["gpus"]] == [0, 1] assert {m["provider"] for m in data["tts_menu"]} == { "piper", "chatterbox", "cartesia", "openrouter", "device" }