3 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 2b2d5e8580 Tests: Alt+F4 unabhängig von der Tastaturzustellung des Build-Rechners prüfen
Auf dem Runner gingen erzeugte Tastendrücke verloren (Tk stellt sie unter Windows nur dem Fenster
mit Tastaturfokus zu); der Watchdog brach dann ab und es sah aus, als hätte sich das Programm
beendet. Handler wird jetzt direkt aufgerufen, die Bindung separat geprüft; Watchdog-Abbrüche
werden als Zeitüberschreitung gemeldet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:43:29 +01:00
adminandClaude Opus 5.5 86192c853d Test: Diagnose für Alt+F4 im Dialog (wer löst Alt+F4/Beenden aus)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:39:11 +01:00
adminandClaude Opus 5.5 bd45ffea17 Alt+F4 bei gerade geöffnetem Dialog schließt nicht mehr das Programm
Auf dem langsameren Build-Runner schlug der Alt+F4-Test fehl: Kam der Tastendruck an, bevor der
Dialog gezeichnet und gesperrt war, übersah die Ausweichsuche das Popup (winfo_ismapped = 0) und
Alt+F4 galt dem Hauptfenster. Die Suche nutzt jetzt den Fensterstatus. Neuer Test erzwingt genau
diesen Zustand plattformunabhängig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:33:54 +01:00
3 changed files with 77 additions and 11 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
"""Provisions-Manager 95 – Provisionsabrechnung für Unterhändler der Handy-Insel Miesbach."""
__version__ = "1.1.2"
__version__ = "1.1.3"
APP_NAME = "Provisions-Manager 95"
APP_ORG = "Handy-Insel Miesbach"
+9 -2
View File
@@ -288,8 +288,15 @@ class Hauptrahmen:
def suche(widget):
for kind in widget.winfo_children():
if isinstance(kind, tk.Toplevel) and hasattr(kind, "abbrechen") and kind.winfo_ismapped():
gefunden.append(kind)
# Fensterstatus statt winfo_ismapped(): direkt nach dem Einblenden ist der Status schon
# „normal“, gezeichnet ist das Fenster aber evtl. noch nicht (langsamer PC, Wine).
if isinstance(kind, tk.Toplevel) and hasattr(kind, "abbrechen"):
try:
offen = kind.winfo_exists() and kind.wm_state() != "withdrawn"
except tk.TclError:
offen = False
if offen:
gefunden.append(kind)
suche(kind)
suche(self.root)
return gefunden[-1] if gefunden else None
+67 -8
View File
@@ -47,6 +47,7 @@ class Lauf:
raise
pytest.skip(f"kein Bildschirm: {exc}")
self.fehler: list[str] = []
self.watchdog = False # True = der TEST hat wegen Zeitüberschreitung abgebrochen
from provtool.gui import app as A
self.app = A.App(self.root)
echt = self.root.report_callback_exception
@@ -62,6 +63,13 @@ class Lauf:
self.root.report_callback_exception = melden
# ---------------------------------------------------------------- Zustand
def lebt_sonst(self, text: str) -> str:
"""Fehlertext für „Programm ist zu“, der Watchdog-Abbrüche klar unterscheidet."""
if self.watchdog:
return "Zeitüberschreitung – der Test hat abgebrochen (nicht das Programm hat sich beendet):\n" \
+ "\n".join(self.fehler)
return text
@property
def lebt(self) -> bool:
try:
@@ -95,6 +103,7 @@ class Lauf:
d.destroy()
def _notaus(self):
self.watchdog = True
for d in reversed(self.dialoge()):
try:
d.destroy()
@@ -219,11 +228,11 @@ def test_alt_f4_im_assistent_fragt_und_schliesst_nur_assistent(home):
d.destroy()
lauf.ablauf([
(_assistent_offen(lauf), lambda: lauf.dialog(Einrichtungsassistent).event_generate("<Alt-F4>"), "Alt+F4"),
(_assistent_offen(lauf), lambda: lauf.app.rahmen.alt_f4(lauf.dialog(Einrichtungsassistent)), "Alt+F4"),
(lambda: rueckfrage() is not None, ja_klicken, "Rückfrage mit Ja beantworten"),
(lambda: lauf.dialog(Einrichtungsassistent) is None, lambda: None, "Assistent ist zu"),
])
assert lauf.lebt, "Programm darf nicht beendet werden"
assert lauf.lebt, lauf.lebt_sonst("Programm darf nicht beendet werden")
assert not lauf.uebrig, lauf.uebrig
assert lauf.app.settings.get("assistent_erledigt") is True
lauf.pruefen()
@@ -231,17 +240,37 @@ def test_alt_f4_im_assistent_fragt_und_schliesst_nur_assistent(home):
lauf.pruefen()
def test_alt_f4_im_dialog_schliesst_nur_den_dialog(lauf_mit_daten):
def test_alt_f4_im_dialog_schliesst_nur_den_dialog(lauf_mit_daten, monkeypatch):
from provtool.gui import rahmen
from provtool.gui.dialogs import InfoDialog
lauf = lauf_mit_daten
protokoll: list[str] = [] # Diagnose: wer hat Alt+F4 bzw. Beenden ausgelöst?
alt_f4, beenden = rahmen.Hauptrahmen.alt_f4, type(lauf.app).beenden
def alt_f4_spion(self, widget):
protokoll.append(f"alt_f4(widget={widget}, sperre={self.root.grab_current()}, "
f"popup={self._offenes_popup()}, fokus={self.root.focus_get()})\n"
+ "".join(traceback.format_stack(limit=8)))
return alt_f4(self, widget)
def beenden_spion(self):
protokoll.append("beenden()\n" + "".join(traceback.format_stack(limit=10)))
return beenden(self)
monkeypatch.setattr(rahmen.Hauptrahmen, "alt_f4", alt_f4_spion)
monkeypatch.setattr(type(lauf.app), "beenden", beenden_spion)
lauf.ablauf([
(IMMER, lambda: InfoDialog(lauf.root).zeigen(), "Info öffnen"),
# Handler direkt mit dem Dialog als Empfänger aufrufen: Erzeugte Tastendrücke stellt Tk unter
# Windows nur dem Fenster mit Tastaturfokus zu – auf dem Build-Rechner gingen sie verloren.
# Dass Alt+F4 an diesen Handler gebunden ist, prüft test_alt_f4_ist_gebunden.
(lambda: lauf.dialog(InfoDialog) is not None,
lambda: lauf.dialog(InfoDialog).event_generate("<Alt-F4>"), "Alt+F4 im Dialog"),
lambda: lauf.app.rahmen.alt_f4(lauf.dialog(InfoDialog)), "Alt+F4 im Dialog"),
(lambda: lauf.dialog(InfoDialog) is None, lambda: None, "Dialog geschlossen"),
], sekunden=8)
assert lauf.lebt, "Alt+F4 im Dialog darf nicht das ganze Programm beenden"
assert not lauf.uebrig, lauf.uebrig
diagnose = "\n---\n".join(protokoll) + "\nFehler:\n" + "\n".join(lauf.fehler)
assert lauf.lebt, lauf.lebt_sonst("Alt+F4 im Dialog darf nicht das ganze Programm beenden\n" + diagnose)
assert not lauf.uebrig, f"{lauf.uebrig}\n{diagnose}"
lauf.pruefen()
@@ -298,16 +327,46 @@ def test_schliessen_waehrend_rundgang(lauf_mit_daten):
lauf.pruefen()
def test_alt_f4_ist_gebunden(lauf_mit_daten):
"""Alt+F4 muss im Win95-Rahmen an den eigenen Handler gebunden sein (für alle Fenster)."""
lauf = lauf_mit_daten
if not lauf.app.rahmen.klassisch:
pytest.skip("nur im Win95-Rahmen")
skript = lauf.root.bind_all("<Alt-F4>")
assert skript, "Alt+F4 ist nicht gebunden"
lauf.pruefen()
def test_alt_f4_bevor_die_sperre_steht(lauf_mit_daten, monkeypatch):
"""Erzwingt den Zustand vom langsamen Build-Rechner: Dialog ist offen, aber Tk-Sperre und
Zeichnen sind noch nicht fertig. Alt+F4 muss trotzdem nur den Dialog schließen."""
from provtool.gui import win95 as w
from provtool.gui.dialogs import InfoDialog
monkeypatch.setattr(w.Dialog, "_sperre_setzen", lambda self, versuch=0: None) # keine Sperre
monkeypatch.setattr(w.Dialog, "winfo_ismapped", lambda self: 0) # „noch nicht gezeichnet“
lauf = lauf_mit_daten
lauf.ablauf([
(IMMER, lambda: InfoDialog(lauf.root).zeigen(), "Info öffnen"),
# wie unter Windows: Tk liefert den Tastendruck an das Fenster mit Fokus – hier noch das Hauptfenster
(lambda: lauf.dialog(InfoDialog) is not None, lambda: lauf.app.rahmen.alt_f4(lauf.root),
"Alt+F4 (Fokus noch beim Hauptfenster)"),
(lambda: lauf.dialog(InfoDialog) is None, lambda: None, "Dialog geschlossen"),
], sekunden=8)
assert lauf.lebt, lauf.lebt_sonst("Alt+F4 darf bei offenem Dialog nicht das ganze Programm beenden")
assert not lauf.uebrig, lauf.uebrig
lauf.pruefen()
def test_alt_f4_im_rundgang_beendet_nur_den_rundgang(lauf_mit_daten):
lauf = lauf_mit_daten
rg = lambda: getattr(lauf.app, "_rundgang", None) # noqa: E731
lauf.ablauf([
(IMMER, lauf.app.rundgang, "Rundgang starten"),
(lambda: rg() is not None and rg().blase is not None,
lambda: rg().blase.event_generate("<Alt-F4>"), "Alt+F4"),
lambda: lauf.app.rahmen.alt_f4(rg().blase), "Alt+F4"),
(lambda: rg().blase is None and not rg().rahmen, lambda: None, "Rundgang samt Rahmen weg"),
], sekunden=8)
assert lauf.lebt, "Alt+F4 im Rundgang darf nicht das Programm beenden"
assert lauf.lebt, lauf.lebt_sonst("Alt+F4 im Rundgang darf nicht das Programm beenden")
assert not lauf.uebrig, lauf.uebrig
lauf.pruefen()