Skip to content

Commit 6775967

Browse files
committed
Merge branch 'main' into docs/flatpak-build-notes
2 parents f49594a + 3ea527e commit 6775967

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

tests/conftest.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
Tests, die das notify-Verhalten gezielt pruefen (``tests/test_features.py``),
1111
legen innerhalb ihres ``with patch(...)``-Blocks einen eigenen, verschachtelten
1212
Patch an und bleiben dadurch unveraendert gueltig.
13+
14+
Zweiter Zweck: deterministischer Abbau geleakter Qt-Objekte zwischen Tests
15+
(siehe ``_collect_leaked_qobjects``).
1316
"""
1417
from __future__ import annotations
1518

19+
import gc
1620
from unittest.mock import MagicMock, patch
1721

1822
import pytest
@@ -23,3 +27,34 @@ def _block_real_notifications():
2327
"""Unterbindet echte ``notify-send``-Aufrufe in der gesamten Testsuite."""
2428
with patch("app.notify.subprocess.run", MagicMock()):
2529
yield
30+
31+
32+
@pytest.fixture(autouse=True)
33+
def _collect_leaked_qobjects():
34+
"""Sammelt nach JEDEM Test geleakte, parentlose QObjects deterministisch ein.
35+
36+
Root Cause der Python-3.12-Testisolation (``RuntimeError: wrapped C/C++
37+
object of type HotkeyWorker has been deleted``): Mehrere GUI-Tests erzeugen
38+
``BlitztextApp`` (selbst ein ``QObject``) samt ``HotkeyWorker``, ``QThread``
39+
und Fenstern ohne Qt-Parent und lassen die Python-Referenz am Testende
40+
einfach fallen. Weil diese Objekte in Referenzzyklen haengen (Signal-/Slot-
41+
Verbindungen zeigen auf ``self``), raeumt der Refcount sie nicht ab -- erst
42+
Pythons zyklischer Garbage Collector loescht die zugehoerigen C++-Objekte,
43+
und zwar zu einem nichtdeterministischen Zeitpunkt. Auf CPython 3.12 faellt
44+
dieser GC-Lauf reproduzierbar mitten in ein ``HotkeyWorker.run()`` eines
45+
Folgetests (``tests/test_state_machine.py::TestLeftAltEvents``); sip meldet
46+
dann den gerade laufenden Worker als geloescht, sobald ``run()`` das naechste
47+
Mal auf ``self`` (z. B. ``self.workflow_triggered``) zugreift.
48+
49+
Ein explizites ``gc.collect()`` an der Testgrenze macht diesen Abbau
50+
deterministisch: die geleakten Zyklen werden eingesammelt, WAEHREND kein
51+
Worker laeuft. Im Folgetest bleibt fuer den GC nichts mehr einzusammeln, das
52+
mit einem laufenden ``run()`` kollidieren koennte.
53+
54+
Bewusst NUR ``gc.collect()``: kein ``processEvents``/``DeferredDelete``-Pump,
55+
da dieser noch von Tests referenzierte Widgets vorzeitig loeschen und
56+
C++-seitige Doppel-Frees ausloesen kann. ``gc.collect()`` bricht nur echte,
57+
unerreichbare Zyklen auf und ist damit nebenwirkungsfrei.
58+
"""
59+
yield
60+
gc.collect()

0 commit comments

Comments
 (0)