fix(tests): collect leaked QObjects between tests to end Py3.12 isolation flake#50
Merged
Merged
Conversation
…tion flake
Ursache:
Mehrere GUI-Tests (test_smoke_launch, test_tray_preset_menu,
test_settings_dialog, gui_app in test_state_machine) erzeugen BlitztextApp
(selbst ein QObject) samt HotkeyWorker, QThread und Fenstern ohne Qt-Parent
und lassen die Python-Referenz am Testende fallen. Diese Objekte haengen in
Referenzzyklen (Signal/Slot-Verbindungen zeigen auf self), sodass der Refcount
sie nicht abraeumt -- erst Pythons zyklischer GC loescht die C++-Objekte, zu
nichtdeterministischem Zeitpunkt. Auf CPython 3.12 (CI-Matrix) faellt dieser
GC-Lauf reproduzierbar mitten in HotkeyWorker.run() eines Folgetests; sip
meldet dann den laufenden Worker als geloescht:
RuntimeError: wrapped C/C++ object of type HotkeyWorker has been deleted
(app/hotkey_service.py:307, self.workflow_triggered.emit)
Betroffen: tests/test_state_machine.py::TestLeftAltEvents::
test_leftalt_keydown_triggers_toggle.
Fix:
Autouse-Fixture in tests/conftest.py ruft nach JEDEM Test gc.collect(). Der
Abbau der geleakten Zyklen wird damit deterministisch an der Testgrenze
erzwungen -- waehrend kein Worker laeuft. Im Folgetest bleibt fuer den GC
nichts mehr einzusammeln, das mit einem laufenden run() kollidieren koennte.
Reiner Test-Fix, kein Produktivcode.
Bewusst NUR gc.collect(): eine erste, aggressivere Variante mit
processEvents()+sendPostedEvents(DeferredDelete) loeschte noch von Tests
referenzierte Widgets vorzeitig und provozierte C++-Doppel-Frees
(Fatal Python error: Aborted). gc.collect() bricht nur echte, unerreichbare
Zyklen auf und ist nebenwirkungsfrei.
Verifikation (Py3.12-venv wie CI, QT_QPA_PLATFORM=offscreen, WHISPER_GUI_TESTS=1):
- Einzeltest, test_state_machine.py, volle Suite: gruen (494 passed)
- Zusaetzlich Py3.14: gruen; Non-GUI-Default: 424 passed / 70 skipped
- Leak-Messung: geleakte BlitztextApp zwischen Tests 7 -> 2 (Peak)
- compileall app + secret-scan (CI-Muster): sauber
Risiken:
gc.collect() nach jedem Test erhoeht die Suite-Laufzeit der Offscreen-GUI-
Suite (~10s -> ~20s bei 494 Tests). Bewusst zugunsten deterministischer
Isolation akzeptiert. Keine Testdeaktivierung, kein skip/xfail/retry.
Rollback:
git revert <diese Commit-SHA> bzw. Entfernen der Fixture
_collect_leaked_qobjects aus tests/conftest.py stellt den alten Stand her.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ursache
Mehrere GUI-Tests (
test_smoke_launch,test_tray_preset_menu,test_settings_dialog,gui_appintest_state_machine) erzeugenBlitztextAppsamtHotkeyWorker,QThreadund Fenstern ohne Qt-Parent und lassen die Python-Referenz am Testende fallen. Diese
Objekte hängen in Referenzzyklen (Signal/Slot-Verbindungen zeigen auf
self), sodass derRefcount sie nicht abräumt — erst Pythons zyklischer GC löscht die C++-Objekte, zu
nichtdeterministischem Zeitpunkt.
Auf CPython 3.12 (CI-Matrix) fällt dieser GC-Lauf reproduzierbar mitten in
HotkeyWorker.run()eines Folgetests; sip meldet dann den laufenden Worker als gelöscht:Betroffen:
tests/test_state_machine.py::TestLeftAltEvents::test_leftalt_keydown_triggers_toggle.Fix
Autouse-Fixture in
tests/conftest.pyruft nach jedem Testgc.collect(). Der Abbauder geleakten Zyklen wird damit deterministisch an der Testgrenze erzwungen — während kein
Worker läuft. Im Folgetest bleibt für den GC nichts mehr einzusammeln, das mit einem
laufenden
run()kollidieren könnte.Reiner Test-Fix, kein Produktivcode. Bewusst NUR
gc.collect(): eine erste,aggressivere Variante mit
processEvents()+sendPostedEvents(DeferredDelete)löschte nochvon Tests referenzierte Widgets vorzeitig und provozierte C++-Doppel-Frees
(
Fatal Python error: Aborted).gc.collect()bricht nur echte, unerreichbare Zyklen aufund ist nebenwirkungsfrei.
Akzeptierter Laufzeit-Nebeneffekt
Das
gc.collect()nach jedem Test kann die Testsuite messbar verlangsamen. DiesesCI-/Entwicklungsrisiko wird bewusst akzeptiert, da App-Code, Installation und
Nutzerbetrieb nicht betroffen sind — die Fixture greift ausschließlich in der Testumgebung.
Tests
Py3.12-venv wie CI (
QT_QPA_PLATFORM=offscreen,WHISPER_GUI_TESTS=1):test_state_machine.py, volle Suite: grün (494 passed)BlitztextAppzwischen Tests 7 → 2 (Peak)compileall app+ secret-scan (CI-Muster): sauberRollback
Einzelner Commit, reine Testinfrastruktur. Rückgängig via
git revert a764161(bzw. Revert des Squash-Merge-Commits) — kein Produktivcode, keine Migration, kein State.