-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_bug_regressions.py
More file actions
156 lines (121 loc) · 6.08 KB
/
Copy pathtest_bug_regressions.py
File metadata and controls
156 lines (121 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# -*- coding: utf-8 -*-
"""Regressionstests Bugfix-Library-Transfer Batch #24 + CP-Nachfund + v1.3-Delta — REL_Editor_PythonBox.
D1: QMenu() ohne Parent ist GC-Risiko — muss QMenu(self) sein.
D2: Veraltete Qt-Enums in PySide6 6.4+ dürfen nicht mehr als bare Werte vorkommen.
Erweitert (CP-Nachfund): Qt.GlobalColor, QPalette.ColorRole bare,
Qt.PenStyle.NoPen, Qt.ContextMenuPolicy.
U2: json.loads/json.load ohne JSONDecodeError-Handler.
U3: open() im Text-Modus muss encoding= angeben.
U4: Nicht-atomares Schreiben — tmp + os.replace() Muster prüfen.
D4: subprocess.run ohne timeout= — PyInstaller-Build-Aufruf.
"""
import py_compile
import unittest
from pathlib import Path
PROJ = Path(__file__).parent.parent
SRC = PROJ / "PythonBox_v8.py"
class TestD1QMenuParent(unittest.TestCase):
def test_no_bare_qmenu(self):
src = SRC.read_text(encoding="utf-8")
self.assertNotIn("QMenu()", src,
"QMenu() ohne Parent ist GC-Risiko; muss QMenu(self) sein")
class TestD2DeprecatedQtEnums(unittest.TestCase):
def _src(self):
return SRC.read_text(encoding="utf-8")
def test_no_bare_userrole(self):
src = self._src()
self.assertNotIn("Qt.UserRole", src,
"Qt.UserRole (bare) — muss Qt.ItemDataRole.UserRole sein")
def test_no_bare_align(self):
src = self._src()
self.assertNotIn("Qt.AlignRight", src)
self.assertNotIn("Qt.AlignCenter", src)
def test_no_bare_orientation(self):
src = self._src()
self.assertNotIn("Qt.Horizontal", src)
def test_no_bare_messagebox(self):
src = self._src()
self.assertNotIn("QMessageBox.Save ", src)
self.assertNotIn("QMessageBox.Discard", src)
self.assertNotIn("QMessageBox.Cancel", src)
self.assertNotIn("QMessageBox.Yes", src)
def test_no_bare_cursor(self):
src = self._src()
self.assertNotIn("Qt.PointingHandCursor", src)
def test_no_bare_globalcolor(self):
src = self._src()
self.assertNotIn("Qt.white", src,
"Qt.white (bare) — muss Qt.GlobalColor.white sein")
self.assertNotIn("Qt.black", src,
"Qt.black (bare) — muss Qt.GlobalColor.black sein")
def test_no_bare_qpalette_roles(self):
src = self._src()
for role in ("AlternateBase", "ToolTipBase", "ToolTipText",
"Button,", "ButtonText", "Link,"):
self.assertNotIn(f"QPalette.{role.rstrip(',')},", src,
f"QPalette.{role.rstrip(',')} (bare) — muss QPalette.ColorRole.{role.rstrip(',')} sein")
self.assertNotIn("QPalette.Button,", src)
self.assertNotIn("QPalette.Link,", src)
def test_no_bare_nopen(self):
src = self._src()
self.assertNotIn("Qt.NoPen", src,
"Qt.NoPen (bare) — muss Qt.PenStyle.NoPen sein")
def test_no_bare_contextmenupolicy(self):
src = self._src()
self.assertNotIn("Qt.CustomContextMenu", src,
"Qt.CustomContextMenu (bare) — muss Qt.ContextMenuPolicy.CustomContextMenu sein")
class TestU2JsonDecodeError(unittest.TestCase):
def _src(self):
return SRC.read_text(encoding="utf-8")
def test_import_settings_has_json_handler(self):
src = self._src()
self.assertIn("import_settings_from_json", src)
self.assertIn("json.JSONDecodeError", src,
"json.loads in import_settings_from_json braucht JSONDecodeError-Handler")
def test_snippet_import_has_json_handler(self):
src = self._src()
self.assertIn("import_from_json", src)
# Mindestens 2 JSONDecodeError-Handler (settings + snippet)
self.assertGreaterEqual(src.count("json.JSONDecodeError"), 2,
"json.loads in import_from_json braucht eigenen JSONDecodeError-Handler")
class TestU3OpenEncoding(unittest.TestCase):
def test_lock_file_reads_have_encoding(self):
src = SRC.read_text(encoding="utf-8")
self.assertIn("open(self.lock_file, 'r', encoding='utf-8')", src)
def test_lock_file_writes_use_tmp(self):
# BUG-U4 fix: _save_locks schreibt jetzt atomar über tmp + os.replace
src = SRC.read_text(encoding="utf-8")
self.assertIn("str(self.lock_file) + \".tmp\"", src,
"_save_locks muss temporäre .tmp-Datei verwenden (BUG-U4)")
class TestU4AtomicWrite(unittest.TestCase):
"""BUG-U4: Nicht-atomares Schreiben — kein direktes open(path, 'w') für Persistenzdaten."""
def _src(self):
return SRC.read_text(encoding="utf-8")
def test_write_settings_export_atomic(self):
src = self._src()
# write_settings_export muss tmp-Datei anlegen und os.replace nutzen
self.assertIn('path.suffix + ".tmp"', src,
"write_settings_export: atomares Schreiben via .tmp-Suffix fehlt (BUG-U4)")
self.assertIn("os.replace(tmp, path)", src,
"write_settings_export: os.replace(tmp, path) fehlt (BUG-U4)")
def test_save_locks_atomic(self):
src = self._src()
self.assertIn('os.replace(tmp, self.lock_file)', src,
"_save_locks: os.replace(tmp, self.lock_file) fehlt (BUG-U4)")
class TestD4SubprocessTimeout(unittest.TestCase):
"""BUG-D4: subprocess.run ohne timeout= kann GUI einfrieren."""
def test_pyinstaller_run_has_timeout(self):
src = SRC.read_text(encoding="utf-8")
# subprocess.run für PyInstaller-Build muss timeout= haben
import re
matches = re.findall(r'subprocess\.run\(cmd.*?\)', src, re.DOTALL)
for m in matches:
if 'pyinstaller' in src[max(0, src.index(m) - 200):src.index(m)].lower() or \
'pyinstaller' in m.lower():
self.assertIn('timeout=', m,
f"subprocess.run für PyInstaller-Build fehlt timeout= (BUG-D4): {m[:80]}")
class TestSyntaxValidity(unittest.TestCase):
def test_syntax_valid(self):
py_compile.compile(str(SRC), doraise=True)
if __name__ == "__main__":
unittest.main()