From 14b630d8eb9423273bb7d9301d84efbe82f22e16 Mon Sep 17 00:00:00 2001 From: gummiflip Date: Fri, 19 Jun 2026 17:04:57 +0200 Subject: [PATCH] test: Launch-Smoke-Test (App bootet offscreen) + CI-Schritt Leichter, GUI-gated Test (WHISPER_GUI_TESTS=1), der die echte App in-process hochfaehrt: QApplication + BlitztextApp (Tray + Hauptfenster), kurz idlen via QTimer und sauberer Quit mit Exit 0. Kein echter Hotkey/evdev-Thread (stop_hotkey_worker vor dem Idle), kein echtes Audio, keine Whisper/Piper-Nutzung. Bewusst in-process statt main(), um QApplication-Singleton-Kollisionen und den Display-Guard unter Offscreen zu vermeiden -- analog tests/test_state_machine.py. CI: dedizierter Schritt "Launch smoke test (App bootet offscreen)" vor der Vollsuite als sichtbares Fast-Fail-Boot-Signal bei jedem Push. Verifiziert offscreen: Smoke-Test gruen, RED-Gegenprobe greift, Vollsuite 220 gruen. --- .github/workflows/blitztext-linux-ci.yml | 5 +++ tests/test_smoke_launch.py | 49 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/test_smoke_launch.py diff --git a/.github/workflows/blitztext-linux-ci.yml b/.github/workflows/blitztext-linux-ci.yml index 7aafbf5..c0a444f 100644 --- a/.github/workflows/blitztext-linux-ci.yml +++ b/.github/workflows/blitztext-linux-ci.yml @@ -75,6 +75,11 @@ jobs: - name: Compile-check (Syntax) run: python -m compileall app + - name: Launch smoke test (App bootet offscreen) + env: + WHISPER_GUI_TESTS: "1" + run: python -m pytest tests/test_smoke_launch.py -v + - name: Run test suite (inkl. GUI offscreen) env: WHISPER_GUI_TESTS: "1" diff --git a/tests/test_smoke_launch.py b/tests/test_smoke_launch.py new file mode 100644 index 0000000..571f5fc --- /dev/null +++ b/tests/test_smoke_launch.py @@ -0,0 +1,49 @@ +"""Launch-Smoke-Test: bootet die App offscreen noch und beendet sauber? + +Zweck: bei jedem Push automatisch beantworten, ob sich die echte App noch +hochfahren laesst. Der Test konstruiert dieselbe ``BlitztextApp`` wie der +Produktiv-Einstiegspunkt ``main()`` (QApplication + Tray + Hauptfenster), +laesst einen echten Qt-Event-Loop kurz idlen und prueft, dass er sauber mit +Exit 0 zurueckkehrt. + +Bewusst NICHT ueber ``main()``: ``main()`` ruft ``QApplication(sys.argv)`` neu +auf und wuerde im selben Prozess mit dem von der GUI-Suite wiederverwendeten +QApplication-Singleton kollidieren; ausserdem bricht ``_require_display_environment`` +unter Offscreen (kein DISPLAY) ab. Wir spiegeln daher den Boot-Pfad in-process, +analog zu ``tests/test_state_machine.py`` (Fixture ``gui_app``). + +Kein echter Hotkey/evdev-Thread (``stop_hotkey_worker`` vor dem Idle), kein +echtes Audio, keine echte Whisper/Piper-Nutzung. GUI-gated ueber +``WHISPER_GUI_TESTS=1`` (Display/Offscreen noetig). +""" +import os + +import pytest + +_GUI = os.environ.get("WHISPER_GUI_TESTS") == "1" +gui_only = pytest.mark.skipif(not _GUI, reason="benötigt WHISPER_GUI_TESTS=1 (Display)") + + +@gui_only +def test_app_boots_idles_and_exits_clean(): + """Echte App bootet offscreen, idlet kurz und beendet mit Exit 0.""" + from PyQt6.QtCore import QTimer + from PyQt6.QtWidgets import QApplication + + from app.blitztext_linux import BlitztextApp + + qapp = QApplication.instance() or QApplication([]) + app = BlitztextApp(qapp) + # Kein echter evdev-Thread waehrend des Idle-Loops (Pattern aus gui_app). + app.stop_hotkey_worker() + # Hauptfenster offscreen hochfahren -- exakt wie main(). + app.show_main_window() + + # Kurz idlen, dann deterministisch sauber beenden. + QTimer.singleShot(50, qapp.quit) + exit_code = qapp.exec() + + assert exit_code == 0 + + # Idempotentes Cleanup, damit kein Thread in Folgetests nachhaengt. + app.stop_hotkey_worker()