"""Selbsttest der fertigen Programmdatei: `Provisionsmanager95.exe --selbsttest ergebnis.txt` Läuft im Build (Wine + virtueller Bildschirm) gegen die tatsächlich ausgelieferte .exe. Startet die echte Oberfläche in einem Wegwerf-Profil, spielt kritische Abläufe durch und schreibt „OK“ bzw. die gefundenen Fehler in die Ergebnisdatei (Exit-Code 0 = bestanden). Geprüft wird u. a. der Fall „Programm schließen, während der Einrichtungs-Assistent offen ist“ sowie Import und Export (openpyxl/xlrd müssen in der .exe enthalten sein). """ from __future__ import annotations import os import sys import tempfile import time import tkinter as tk import traceback from pathlib import Path ZEITLIMIT = 60 # Sekunden je Szenario class _Szenario: def __init__(self, name: str, home: Path, einstellungen: str | None = None): self.name, self.home, self.fehler = name, home, [] home.mkdir(parents=True, exist_ok=True) os.environ["PROVTOOL_HOME"] = str(home) if einstellungen: (home / "einstellungen.json").write_text(einstellungen, encoding="utf-8") from provtool.gui.app import App self.root = tk.Tk() self.app = App(self.root) echt = self.root.report_callback_exception def melden(exc, val, tb): self.fehler.append("".join(traceback.format_exception(exc, val, tb))) try: echt(exc, val, tb) except Exception: self.fehler.append("Fehler-Handler ist abgestürzt:\n" + traceback.format_exc()) self.root.report_callback_exception = melden def lebt(self) -> bool: try: return bool(self.root.winfo_exists()) except tk.TclError: return False def dialog(self, typ): from provtool.gui import win95 as w gefunden = [] def suche(widget): for kind in widget.winfo_children(): if isinstance(kind, w.Dialog) and isinstance(kind, typ) and kind.winfo_exists(): gefunden.append(kind) suche(kind) if self.lebt(): suche(self.root) return gefunden[0] if gefunden else None def ablauf(self, schritte): offen, start = list(schritte), time.time() def poll(): if not self.lebt(): return if time.time() - start > ZEITLIMIT: self.fehler.append(f"Zeitüberschreitung bei: {offen[0][2] if offen else 'Ende'}") try: self.root.destroy() except tk.TclError: pass return if offen: try: bereit = offen[0][0]() except Exception: bereit = False if bereit: _, aktion, _ = offen.pop(0) def ausfuehren(a=aktion): try: a() except Exception: self.fehler.append(traceback.format_exc()) self.root.after(0, ausfuehren) self.root.after(20, poll) self.root.after(20, poll) self.root.mainloop() if offen: self.fehler.append("Nicht ausgeführt: " + ", ".join(s[2] for s in offen)) log = self.home / "fehler.log" if log.exists() and log.read_text(encoding="utf-8").strip(): self.fehler.append("fehler.log:\n" + log.read_text(encoding="utf-8")) return [f"[{self.name}] {f}" for f in self.fehler] def _beispiel_csv(pfad: Path): kopf = ("Zeitraum;Verguetungsart;Haendlernummer;Unterhaendler;Vertragsnummer;Aktiv;Deaktiv;Rufnummer;" "Kartennummer;IMEI_SN;Geraetename;Kundenvorname;Kundename;Tarif-Kurz;Tarif;Prozent;Betrag") zeilen = [kopf, "2026.10.01-2026.10.07;SIM-Karten;1;111;1;05.10.2026;;1601111111;;;;;Muster GmbH;;Allnet;0;8", "2026.10.01-2026.10.07;Sonderbonus Aktion;1;111;2;06.10.2026;;1602222222;;;;;Beispiel KG;;Classic;0;170"] pfad.write_bytes(("\r\n".join(zeilen) + "\r\n").encode("cp1252")) def ausfuehren(ergebnisdatei: str) -> int: from provtool.gui.assistent import Einrichtungsassistent basis = Path(tempfile.mkdtemp(prefix="pm95-selbsttest-")) fehler: list[str] = [] immer = lambda: True # noqa: E731 try: # 1) Gemeldeter Absturz: Schließen, während der Einrichtungs-Assistent offen ist s = _Szenario("Schließen bei offenem Assistenten", basis / "a") cmd = s.root.tk.call("wm", "protocol", s.root._w, "WM_DELETE_WINDOW") fehler += s.ablauf([(lambda: s.dialog(Einrichtungsassistent) is not None, lambda: None, "Assistent erscheint"), (immer, lambda: s.root.tk.call(cmd), "Fenster schließen")]) # 2) Assistent komplett, Import, Export, Rundgang, Beenden csv = basis / "freenet.csv" _beispiel_csv(csv) s = _Szenario("Assistent, Import, Export", basis / "b") wiz = lambda: s.dialog(Einrichtungsassistent) # noqa: E731 seite = lambda n: (lambda: wiz() is not None and wiz().nr == n) # noqa: E731 def haendler(): wiz().v_name.set("Selbsttest-Händler") wiz().v_fn.set("111") wiz()._haendler_dazu() def abrechnung(): wiz().dateien.append(str(csv)) wiz().v_rundgang.set(False) wiz()._weiter() def import_bericht_zu(): from provtool.gui.dialogs import ImportBericht s.dialog(ImportBericht).abbrechen() def export(): from provtool import export as exp app = s.app pos = [p for p in app.positionen if p["haendler_id"] > 0] dateien = exp.exportiere_haendler(basis / "export", "Selbsttest", pos, "Test") if len(pos) != 2 or len(dateien) != 1 or not dateien[0].exists(): s.fehler.append(f"Export fehlerhaft: {len(pos)} Positionen, Dateien {dateien}") from provtool.gui.dialogs import ImportBericht fehler += s.ablauf([ (seite(0), lambda: wiz()._weiter(), "Willkommen"), (seite(1), lambda: wiz()._weiter(), "Speicherort"), (seite(2), haendler, "Händler anlegen"), (lambda: len(s.app.db.haendler_liste()) == 1, lambda: wiz()._weiter(), "Händler → weiter"), (seite(3), abrechnung, "Abrechnung mit Datei"), (seite(4), lambda: wiz()._weiter(), "Fertig stellen"), (lambda: s.dialog(ImportBericht) is not None, import_bericht_zu, "Import-Bericht schließen"), (lambda: len(s.app.positionen) == 2, export, "Export"), (immer, s.app.rundgang, "Rundgang"), (lambda: getattr(s.app, "_rundgang", None) is not None, s.app.beenden, "Beenden während Rundgang"), ]) except Exception: fehler.append(traceback.format_exc()) Path(ergebnisdatei).write_text("OK\n" if not fehler else "FEHLER\n\n" + "\n\n".join(fehler), encoding="utf-8") return 0 if not fehler else 1 if __name__ == "__main__": sys.exit(ausfuehren(sys.argv[1] if len(sys.argv) > 1 else "selbsttest.txt"))