makcu 2.1.2__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- makcu/README.md +405 -0
- makcu/__init__.py +22 -60
- makcu/__main__.py +388 -387
- makcu/conftest.py +33 -33
- makcu/connection.py +553 -459
- makcu/controller.py +410 -376
- makcu/enums.py +7 -7
- makcu/errors.py +13 -13
- makcu/makcu.pyi +10 -10
- makcu/mouse.py +249 -249
- makcu/test_suite.py +141 -144
- {makcu-2.1.2.dist-info → makcu-2.2.0.dist-info}/METADATA +3 -29
- makcu-2.2.0.dist-info/RECORD +17 -0
- makcu-2.1.2.dist-info/RECORD +0 -16
- {makcu-2.1.2.dist-info → makcu-2.2.0.dist-info}/WHEEL +0 -0
- {makcu-2.1.2.dist-info → makcu-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {makcu-2.1.2.dist-info → makcu-2.2.0.dist-info}/top_level.txt +0 -0
makcu/__main__.py
CHANGED
@@ -1,388 +1,389 @@
|
|
1
|
-
import sys
|
2
|
-
import os
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import List, NoReturn, Optional, Tuple
|
5
|
-
import pytest
|
6
|
-
import time
|
7
|
-
from makcu import create_controller, MakcuConnectionError, MakcuController
|
8
|
-
import json
|
9
|
-
import re
|
10
|
-
import subprocess
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
print("
|
20
|
-
print("Type
|
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
|
-
makcu
|
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
|
-
json_str =
|
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
|
-
if
|
134
|
-
|
135
|
-
print("
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
from rich.
|
141
|
-
from rich.
|
142
|
-
from rich.
|
143
|
-
from rich.
|
144
|
-
from rich import
|
145
|
-
from rich
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
console.print()
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
if
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
console.print(f"[dim]
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
"
|
193
|
-
"
|
194
|
-
"--
|
195
|
-
"--
|
196
|
-
"-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
if
|
206
|
-
|
207
|
-
console.print(f"[
|
208
|
-
|
209
|
-
|
210
|
-
console.print(
|
211
|
-
|
212
|
-
|
213
|
-
console.print(
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
console.print(f"[
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
table
|
236
|
-
table.add_column("
|
237
|
-
table.add_column("
|
238
|
-
table.add_column("
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
if duration_ms
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
console.print(
|
273
|
-
console.print()
|
274
|
-
|
275
|
-
|
276
|
-
summary.
|
277
|
-
summary.add_column(justify="
|
278
|
-
summary.
|
279
|
-
summary.add_row("
|
280
|
-
summary.add_row("
|
281
|
-
summary.add_row("
|
282
|
-
summary.add_row("
|
283
|
-
summary.add_row("
|
284
|
-
|
285
|
-
|
286
|
-
console.print()
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
print("
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
"
|
328
|
-
"
|
329
|
-
"--
|
330
|
-
"--
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
print("
|
370
|
-
print(" python -m makcu --
|
371
|
-
print(" python -m makcu --
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
print("
|
383
|
-
print("
|
384
|
-
print(" python -m makcu --
|
385
|
-
print(" python -m makcu --
|
386
|
-
|
387
|
-
|
1
|
+
import sys
|
2
|
+
import os
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List, NoReturn, Optional, Tuple
|
5
|
+
import pytest
|
6
|
+
import time
|
7
|
+
from makcu import create_controller, MakcuConnectionError, MakcuController
|
8
|
+
import json
|
9
|
+
import re
|
10
|
+
import subprocess
|
11
|
+
import makcu
|
12
|
+
|
13
|
+
makcu_version = makcu.__version__
|
14
|
+
|
15
|
+
def debug_console():
|
16
|
+
controller = create_controller(debug=True)
|
17
|
+
transport = controller.transport
|
18
|
+
|
19
|
+
print("🔧 Makcu Debug Console")
|
20
|
+
print("Type a raw command (e.g., km.version()) and press Enter.")
|
21
|
+
print("Type 'exit' or 'quit' to leave.")
|
22
|
+
|
23
|
+
command_counter = 0
|
24
|
+
|
25
|
+
while True:
|
26
|
+
try:
|
27
|
+
cmd = input(">>> ").strip()
|
28
|
+
if cmd.lower() in {"exit", "quit"}:
|
29
|
+
break
|
30
|
+
if not cmd:
|
31
|
+
continue
|
32
|
+
|
33
|
+
command_counter += 1
|
34
|
+
|
35
|
+
response = transport.send_command(cmd, expect_response=True)
|
36
|
+
|
37
|
+
if response and response.strip():
|
38
|
+
if response.strip() == cmd:
|
39
|
+
print(f"{cmd}")
|
40
|
+
else:
|
41
|
+
print(f"{response}")
|
42
|
+
else:
|
43
|
+
print("(no response)")
|
44
|
+
|
45
|
+
except Exception as e:
|
46
|
+
print(f"⚠️ Error: {e}")
|
47
|
+
|
48
|
+
controller.disconnect()
|
49
|
+
print("Disconnected.")
|
50
|
+
|
51
|
+
def test_port(port: str) -> None:
|
52
|
+
try:
|
53
|
+
print(f"Trying to connect to {port}...")
|
54
|
+
makcu = MakcuController(fallback_com_port=port, send_init=False, override_port=True)
|
55
|
+
makcu.connect()
|
56
|
+
if makcu.is_connected:
|
57
|
+
print(f"✅ Successfully connected to {port}.")
|
58
|
+
makcu.disconnect()
|
59
|
+
except MakcuConnectionError as e:
|
60
|
+
if "FileNotFoundError" in str(e):
|
61
|
+
print(f"❌ Port {port} does not exist. Please check the port name.")
|
62
|
+
else:
|
63
|
+
print(f"❌ Failed to connect to {port}: ")
|
64
|
+
except Exception as e:
|
65
|
+
print(f"❌ Unexpected error: {e}")
|
66
|
+
|
67
|
+
def check_pytest_html_installed() -> bool:
|
68
|
+
"""Check if pytest-html is installed."""
|
69
|
+
try:
|
70
|
+
import pytest_html
|
71
|
+
return True
|
72
|
+
except ImportError:
|
73
|
+
return False
|
74
|
+
|
75
|
+
def find_writable_directory() -> Path:
|
76
|
+
"""Find a writable directory for the HTML report."""
|
77
|
+
# Try current working directory first
|
78
|
+
cwd = Path.cwd()
|
79
|
+
if os.access(cwd, os.W_OK):
|
80
|
+
return cwd
|
81
|
+
|
82
|
+
# Try user's home directory
|
83
|
+
home = Path.home()
|
84
|
+
if os.access(home, os.W_OK):
|
85
|
+
return home
|
86
|
+
|
87
|
+
# Try temp directory as last resort
|
88
|
+
import tempfile
|
89
|
+
return Path(tempfile.gettempdir())
|
90
|
+
|
91
|
+
def parse_html_results(html_file: Path) -> Tuple[List[Tuple[str, str, int]], int]:
|
92
|
+
if not html_file.exists():
|
93
|
+
raise FileNotFoundError(f"HTML report not found: {html_file}")
|
94
|
+
|
95
|
+
with open(html_file, 'r', encoding='utf-8') as f:
|
96
|
+
content = f.read()
|
97
|
+
|
98
|
+
match = re.search(r'data-jsonblob="([^"]*)"', content)
|
99
|
+
if not match:
|
100
|
+
raise ValueError("Could not find JSON data in HTML report")
|
101
|
+
|
102
|
+
json_str = match.group(1)
|
103
|
+
json_str = json_str.replace('"', '"').replace(''', "'").replace('&', '&')
|
104
|
+
|
105
|
+
try:
|
106
|
+
data = json.loads(json_str)
|
107
|
+
except json.JSONDecodeError as e:
|
108
|
+
raise ValueError(f"Failed to parse JSON data: {e}")
|
109
|
+
|
110
|
+
test_results = []
|
111
|
+
total_ms = 0
|
112
|
+
|
113
|
+
skip_tests = {'test_connect_to_port'}
|
114
|
+
|
115
|
+
for test_id, test_data_list in data.get('tests', {}).items():
|
116
|
+
test_name = test_id.split('::')[-1]
|
117
|
+
if test_name in skip_tests:
|
118
|
+
continue
|
119
|
+
|
120
|
+
for test_data in test_data_list:
|
121
|
+
status = test_data.get('result', 'UNKNOWN')
|
122
|
+
duration_str = test_data.get('duration', '0 ms')
|
123
|
+
|
124
|
+
duration_match = re.search(r'(\d+)\s*ms', duration_str)
|
125
|
+
duration_ms = int(duration_match.group(1)) if duration_match else 0
|
126
|
+
total_ms += duration_ms
|
127
|
+
|
128
|
+
test_results.append((test_name, status, duration_ms))
|
129
|
+
|
130
|
+
return test_results, total_ms
|
131
|
+
|
132
|
+
def run_tests() -> NoReturn:
|
133
|
+
# Check if pytest-html is installed
|
134
|
+
if not check_pytest_html_installed():
|
135
|
+
print("❌ pytest-html is not installed. Please install it via:")
|
136
|
+
print(" pip install pytest-html")
|
137
|
+
sys.exit(1)
|
138
|
+
|
139
|
+
try:
|
140
|
+
from rich.console import Console
|
141
|
+
from rich.table import Table
|
142
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
|
143
|
+
from rich.panel import Panel
|
144
|
+
from rich.align import Align
|
145
|
+
from rich import print as rprint
|
146
|
+
from rich.text import Text
|
147
|
+
|
148
|
+
console = Console()
|
149
|
+
|
150
|
+
header = Panel.fit(
|
151
|
+
f"[bold cyan]Makcu Test Suite {makcu_version}[/bold cyan]\n[dim]High-Performance Python Library[/dim]",
|
152
|
+
border_style="bright_blue"
|
153
|
+
)
|
154
|
+
console.print(Align.center(header))
|
155
|
+
console.print()
|
156
|
+
|
157
|
+
package_dir: Path = Path(__file__).resolve().parent
|
158
|
+
test_file: Path = package_dir / "test_suite.py"
|
159
|
+
|
160
|
+
# Find writable directory and create HTML path
|
161
|
+
writable_dir = find_writable_directory()
|
162
|
+
html_file: Path = writable_dir / "latest_pytest.html"
|
163
|
+
|
164
|
+
# Clean up old report if it exists
|
165
|
+
if html_file.exists():
|
166
|
+
try:
|
167
|
+
html_file.unlink()
|
168
|
+
except Exception:
|
169
|
+
pass
|
170
|
+
|
171
|
+
console.print(f"[dim]Running pytest to generate: {html_file}[/dim]")
|
172
|
+
console.print(f"[dim]Working directory: {Path.cwd()}[/dim]")
|
173
|
+
|
174
|
+
start_time = time.time()
|
175
|
+
|
176
|
+
with Progress(
|
177
|
+
SpinnerColumn(),
|
178
|
+
TextColumn("[progress.description]{task.description}"),
|
179
|
+
BarColumn(),
|
180
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
181
|
+
TimeElapsedColumn(),
|
182
|
+
console=console,
|
183
|
+
transient=True
|
184
|
+
) as progress:
|
185
|
+
task = progress.add_task("[cyan]Running tests...", total=100)
|
186
|
+
|
187
|
+
# Run pytest with explicit output capturing
|
188
|
+
result = subprocess.run(
|
189
|
+
[
|
190
|
+
sys.executable, "-m", "pytest",
|
191
|
+
str(test_file),
|
192
|
+
"--rootdir", str(package_dir),
|
193
|
+
"-q",
|
194
|
+
"--tb=no",
|
195
|
+
"--html", str(html_file),
|
196
|
+
"--self-contained-html",
|
197
|
+
"-v" # Add verbose to help debug
|
198
|
+
],
|
199
|
+
capture_output=True,
|
200
|
+
text=True
|
201
|
+
)
|
202
|
+
|
203
|
+
progress.update(task, completed=100)
|
204
|
+
|
205
|
+
# Check if HTML file was created
|
206
|
+
if not html_file.exists():
|
207
|
+
console.print(f"[red]❌ HTML report was not created at: {html_file}[/red]")
|
208
|
+
console.print(f"[yellow]pytest exit code: {result.returncode}[/yellow]")
|
209
|
+
if result.stdout:
|
210
|
+
console.print("[yellow]stdout:[/yellow]")
|
211
|
+
console.print(result.stdout)
|
212
|
+
if result.stderr:
|
213
|
+
console.print("[red]stderr:[/red]")
|
214
|
+
console.print(result.stderr)
|
215
|
+
|
216
|
+
# Try to run tests without HTML report
|
217
|
+
console.print("\n[yellow]Running tests without HTML report...[/yellow]")
|
218
|
+
result2 = subprocess.run(
|
219
|
+
[sys.executable, "-m", "pytest", str(test_file), "-v"],
|
220
|
+
capture_output=True,
|
221
|
+
text=True
|
222
|
+
)
|
223
|
+
console.print(result2.stdout)
|
224
|
+
sys.exit(1)
|
225
|
+
|
226
|
+
try:
|
227
|
+
test_results, total_ms = parse_html_results(html_file)
|
228
|
+
except (FileNotFoundError, ValueError) as e:
|
229
|
+
console.print(f"[red]❌ Failed to parse test results: {e}[/red]")
|
230
|
+
console.print(f"[yellow]⚠️ pytest exit code: {result.returncode}[/yellow]")
|
231
|
+
sys.exit(1)
|
232
|
+
|
233
|
+
elapsed_time = time.time() - start_time
|
234
|
+
|
235
|
+
table = Table(title="[bold]Test Results[/bold]", show_header=True, header_style="bold magenta")
|
236
|
+
table.add_column("Test", style="cyan", no_wrap=True)
|
237
|
+
table.add_column("Status", justify="center")
|
238
|
+
table.add_column("Time", justify="right", style="yellow")
|
239
|
+
table.add_column("Performance", justify="center")
|
240
|
+
|
241
|
+
passed = failed = skipped = 0
|
242
|
+
|
243
|
+
for test_name, status, duration_ms in test_results:
|
244
|
+
display_name = test_name.replace("test_", "").replace("_", " ").title()
|
245
|
+
|
246
|
+
if status.upper() == "PASSED":
|
247
|
+
status_text = "[green]✅ PASSED[/green]"
|
248
|
+
passed += 1
|
249
|
+
elif status.upper() == "FAILED":
|
250
|
+
status_text = "[red]❌ FAILED[/red]"
|
251
|
+
failed += 1
|
252
|
+
elif status.upper() == "SKIPPED":
|
253
|
+
status_text = "[yellow]⏭️ SKIPPED[/yellow]"
|
254
|
+
skipped += 1
|
255
|
+
else:
|
256
|
+
status_text = status
|
257
|
+
|
258
|
+
time_str = f"{duration_ms}ms" if duration_ms else "-"
|
259
|
+
if duration_ms <= 3:
|
260
|
+
perf = "[green]Excellent[/green]"
|
261
|
+
elif duration_ms <= 5:
|
262
|
+
perf = "[cyan]Great[/cyan]"
|
263
|
+
elif duration_ms <= 10:
|
264
|
+
perf = "[yellow]Good[/yellow]"
|
265
|
+
elif duration_ms > 0:
|
266
|
+
perf = "[red]🐌 Needs work[/red]"
|
267
|
+
else:
|
268
|
+
perf = "-"
|
269
|
+
|
270
|
+
table.add_row(display_name, status_text, time_str, perf)
|
271
|
+
|
272
|
+
console.print("\n")
|
273
|
+
console.print(table)
|
274
|
+
console.print()
|
275
|
+
|
276
|
+
summary = Table.grid(padding=1)
|
277
|
+
summary.add_column(style="bold cyan", justify="right")
|
278
|
+
summary.add_column(justify="left")
|
279
|
+
summary.add_row("Total Tests:", str(len(test_results)))
|
280
|
+
summary.add_row("Passed:", f"[green]{passed}[/green]")
|
281
|
+
summary.add_row("Failed:", f"[red]{failed}[/red]" if failed else str(failed))
|
282
|
+
summary.add_row("Skipped:", f"[yellow]{skipped}[/yellow]" if skipped else str(skipped))
|
283
|
+
summary.add_row("Total Time:", f"{elapsed_time:.2f}s")
|
284
|
+
summary.add_row("Avg Time/Test:", f"{total_ms/len(test_results):.1f}ms" if test_results else "0ms")
|
285
|
+
|
286
|
+
console.print(Align.center(Panel(summary, title="[bold]Summary[/bold]", border_style="blue", expand=False)))
|
287
|
+
console.print()
|
288
|
+
|
289
|
+
if test_results:
|
290
|
+
avg_time = total_ms / len(test_results)
|
291
|
+
if avg_time < 3:
|
292
|
+
perf_text = Text("Performance: ELITE - Ready for 360Hz+ gaming!", style="bold bright_green")
|
293
|
+
elif avg_time < 5:
|
294
|
+
perf_text = Text("Performance: EXCELLENT - Ready for 240Hz+ gaming!", style="bold green")
|
295
|
+
elif avg_time < 10:
|
296
|
+
perf_text = Text("Performance: GREAT - Ready for 144Hz gaming!", style="bold cyan")
|
297
|
+
else:
|
298
|
+
perf_text = Text("Performance: GOOD - Suitable for standard gaming", style="bold yellow")
|
299
|
+
else:
|
300
|
+
perf_text = Text("⚠️ No test results parsed. Check your test suite.", style="bold red")
|
301
|
+
|
302
|
+
console.print(Align.center(Panel(perf_text, border_style="green")))
|
303
|
+
|
304
|
+
# Print the location of the HTML report
|
305
|
+
console.print(f"\n[dim]HTML report saved to: {html_file}[/dim]")
|
306
|
+
|
307
|
+
sys.exit(0 if failed == 0 else 1)
|
308
|
+
|
309
|
+
except ImportError:
|
310
|
+
print("📦 Rich not installed. Install it via `pip install rich` for enhanced output.")
|
311
|
+
print("\nFallback to raw pytest output...\n")
|
312
|
+
|
313
|
+
package_dir: Path = Path(__file__).resolve().parent
|
314
|
+
test_file: Path = package_dir / "test_suite.py"
|
315
|
+
|
316
|
+
# Find writable directory
|
317
|
+
writable_dir = find_writable_directory()
|
318
|
+
html_file: Path = writable_dir / "latest_pytest.html"
|
319
|
+
|
320
|
+
print(f"HTML report will be saved to: {html_file}")
|
321
|
+
|
322
|
+
# Use subprocess instead of pytest.main for better control
|
323
|
+
result = subprocess.run(
|
324
|
+
[
|
325
|
+
sys.executable, "-m", "pytest",
|
326
|
+
str(test_file),
|
327
|
+
"--rootdir", str(package_dir),
|
328
|
+
"-q",
|
329
|
+
"--tb=no",
|
330
|
+
"--html", str(html_file),
|
331
|
+
"--self-contained-html"
|
332
|
+
],
|
333
|
+
capture_output=True,
|
334
|
+
text=True
|
335
|
+
)
|
336
|
+
|
337
|
+
if not html_file.exists():
|
338
|
+
print(f"\n❌ HTML report was not created. pytest exit code: {result.returncode}")
|
339
|
+
if result.stdout:
|
340
|
+
print("stdout:", result.stdout)
|
341
|
+
if result.stderr:
|
342
|
+
print("stderr:", result.stderr)
|
343
|
+
sys.exit(1)
|
344
|
+
|
345
|
+
try:
|
346
|
+
test_results, total_ms = parse_html_results(html_file)
|
347
|
+
passed = sum(1 for _, status, _ in test_results if status.upper() == "PASSED")
|
348
|
+
failed = sum(1 for _, status, _ in test_results if status.upper() == "FAILED")
|
349
|
+
skipped = sum(1 for _, status, _ in test_results if status.upper() == "SKIPPED")
|
350
|
+
|
351
|
+
print(f"\n📊 Results: {passed} passed, {failed} failed, {skipped} skipped")
|
352
|
+
if test_results:
|
353
|
+
avg_time = total_ms / len(test_results)
|
354
|
+
print(f"⏱️ Average time per test: {avg_time:.1f}ms")
|
355
|
+
except (FileNotFoundError, ValueError):
|
356
|
+
print("\n⚠️ Could not parse HTML results for summary")
|
357
|
+
|
358
|
+
if result.returncode != 0:
|
359
|
+
print("\n❌ Some tests failed.")
|
360
|
+
else:
|
361
|
+
print("\n✅ All tests passed.")
|
362
|
+
|
363
|
+
sys.exit(result.returncode)
|
364
|
+
|
365
|
+
def main() -> None:
|
366
|
+
args: List[str] = sys.argv[1:]
|
367
|
+
|
368
|
+
if not args:
|
369
|
+
print("Usage:")
|
370
|
+
print(" python -m makcu --debug")
|
371
|
+
print(" python -m makcu --testPort COM3")
|
372
|
+
print(" python -m makcu --runtest")
|
373
|
+
return
|
374
|
+
|
375
|
+
if args[0] == "--debug":
|
376
|
+
debug_console()
|
377
|
+
elif args[0] == "--testPort" and len(args) == 2:
|
378
|
+
test_port(args[1])
|
379
|
+
elif args[0] == "--runtest":
|
380
|
+
run_tests()
|
381
|
+
else:
|
382
|
+
print(f"Unknown command: {' '.join(args)}")
|
383
|
+
print("Usage:")
|
384
|
+
print(" python -m makcu --debug")
|
385
|
+
print(" python -m makcu --testPort COM3")
|
386
|
+
print(" python -m makcu --runtest")
|
387
|
+
|
388
|
+
if __name__ == "__main__":
|
388
389
|
main()
|