hardpy 0.6.0__py3-none-any.whl → 0.7.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.
- hardpy/__init__.py +28 -26
- hardpy/cli/cli.py +8 -8
- hardpy/cli/template.py +17 -7
- hardpy/common/config.py +15 -14
- hardpy/hardpy_panel/api.py +9 -9
- hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
- hardpy/hardpy_panel/frontend/dist/index.html +1 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.942e57d4.js +3 -0
- hardpy/hardpy_panel/frontend/dist/static/js/main.942e57d4.js.map +1 -0
- hardpy/pytest_hardpy/db/__init__.py +3 -4
- hardpy/pytest_hardpy/db/base_connector.py +9 -2
- hardpy/pytest_hardpy/db/base_server.py +1 -1
- hardpy/pytest_hardpy/db/base_store.py +14 -9
- hardpy/pytest_hardpy/db/const.py +3 -1
- hardpy/pytest_hardpy/db/runstore.py +13 -15
- hardpy/pytest_hardpy/db/schema/__init__.py +9 -0
- hardpy/pytest_hardpy/db/{schema.py → schema/v1.py} +120 -79
- hardpy/pytest_hardpy/db/statestore.py +8 -10
- hardpy/pytest_hardpy/plugin.py +107 -49
- hardpy/pytest_hardpy/pytest_call.py +75 -30
- hardpy/pytest_hardpy/pytest_wrapper.py +8 -7
- hardpy/pytest_hardpy/reporter/__init__.py +1 -1
- hardpy/pytest_hardpy/reporter/base.py +32 -7
- hardpy/pytest_hardpy/reporter/hook_reporter.py +65 -37
- hardpy/pytest_hardpy/reporter/runner_reporter.py +6 -8
- hardpy/pytest_hardpy/result/__init__.py +1 -1
- hardpy/pytest_hardpy/result/couchdb_config.py +20 -16
- hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +2 -2
- hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +36 -20
- hardpy/pytest_hardpy/utils/__init__.py +20 -19
- hardpy/pytest_hardpy/utils/connection_data.py +6 -8
- hardpy/pytest_hardpy/utils/const.py +1 -1
- hardpy/pytest_hardpy/utils/dialog_box.py +34 -22
- hardpy/pytest_hardpy/utils/exception.py +8 -8
- hardpy/pytest_hardpy/utils/machineid.py +15 -0
- hardpy/pytest_hardpy/utils/node_info.py +45 -16
- hardpy/pytest_hardpy/utils/progress_calculator.py +4 -3
- hardpy/pytest_hardpy/utils/singleton.py +23 -16
- {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/METADATA +19 -28
- {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/RECORD +44 -42
- hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js.map +0 -1
- /hardpy/hardpy_panel/frontend/dist/static/js/{main.7c954faf.js.LICENSE.txt → main.942e57d4.js.LICENSE.txt} +0 -0
- {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/WHEEL +0 -0
- {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/entry_points.txt +0 -0
- {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/licenses/LICENSE +0 -0
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import signal
|
|
5
6
|
from logging import getLogger
|
|
@@ -8,37 +9,38 @@ from platform import system
|
|
|
8
9
|
from re import compile as re_compile
|
|
9
10
|
from typing import Any, Callable
|
|
10
11
|
|
|
12
|
+
from _pytest._code.code import (
|
|
13
|
+
ExceptionInfo,
|
|
14
|
+
ExceptionRepr,
|
|
15
|
+
ReprExceptionInfo,
|
|
16
|
+
ReprFileLocation,
|
|
17
|
+
TerminalRepr,
|
|
18
|
+
)
|
|
11
19
|
from natsort import natsorted
|
|
12
20
|
from pytest import (
|
|
13
|
-
|
|
14
|
-
exit,
|
|
15
|
-
TestReport,
|
|
16
|
-
Item,
|
|
17
|
-
Session,
|
|
21
|
+
CallInfo,
|
|
18
22
|
Config,
|
|
23
|
+
ExitCode,
|
|
24
|
+
Item,
|
|
19
25
|
Parser,
|
|
26
|
+
Session,
|
|
27
|
+
TestReport,
|
|
28
|
+
exit,
|
|
20
29
|
fixture,
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
from _pytest._code.code import (
|
|
24
|
-
ExceptionRepr,
|
|
25
|
-
ReprFileLocation,
|
|
26
|
-
ExceptionInfo,
|
|
27
|
-
ReprExceptionInfo,
|
|
28
|
-
TerminalRepr,
|
|
30
|
+
skip,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
from hardpy.pytest_hardpy.reporter import HookReporter
|
|
32
34
|
from hardpy.pytest_hardpy.utils import (
|
|
33
|
-
|
|
35
|
+
ConnectionData,
|
|
34
36
|
NodeInfo,
|
|
35
37
|
ProgressCalculator,
|
|
36
|
-
|
|
38
|
+
TestStatus,
|
|
37
39
|
)
|
|
38
40
|
from hardpy.pytest_hardpy.utils.node_info import TestDependencyInfo
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def pytest_addoption(parser: Parser):
|
|
43
|
+
def pytest_addoption(parser: Parser) -> None:
|
|
42
44
|
"""Register argparse-style options."""
|
|
43
45
|
con_data = ConnectionData()
|
|
44
46
|
parser.addoption(
|
|
@@ -74,7 +76,12 @@ def pytest_addoption(parser: Parser):
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
# Bootstrapping hooks
|
|
77
|
-
def pytest_load_initial_conftests(
|
|
79
|
+
def pytest_load_initial_conftests(
|
|
80
|
+
early_config: Config,
|
|
81
|
+
parser: Parser, # noqa: ARG001
|
|
82
|
+
args: Any, # noqa: ANN401
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Load initial conftests."""
|
|
78
85
|
if "--hardpy-pt" in args:
|
|
79
86
|
plugin = HardpyPlugin()
|
|
80
87
|
early_config.pluginmanager.register(plugin)
|
|
@@ -86,7 +93,7 @@ class HardpyPlugin:
|
|
|
86
93
|
Extends hook functions from pytest API.
|
|
87
94
|
"""
|
|
88
95
|
|
|
89
|
-
def __init__(self):
|
|
96
|
+
def __init__(self) -> None:
|
|
90
97
|
self._progress = ProgressCalculator()
|
|
91
98
|
self._results = {}
|
|
92
99
|
self._post_run_functions: list[Callable] = []
|
|
@@ -95,18 +102,18 @@ class HardpyPlugin:
|
|
|
95
102
|
if system() == "Linux":
|
|
96
103
|
signal.signal(signal.SIGTERM, self._stop_handler)
|
|
97
104
|
elif system() == "Windows":
|
|
98
|
-
signal.signal(signal.SIGBREAK, self._stop_handler)
|
|
105
|
+
signal.signal(signal.SIGBREAK, self._stop_handler) # type: ignore
|
|
99
106
|
self._log = getLogger(__name__)
|
|
100
107
|
|
|
101
108
|
# Initialization hooks
|
|
102
109
|
|
|
103
|
-
def pytest_configure(self, config: Config):
|
|
110
|
+
def pytest_configure(self, config: Config) -> None:
|
|
104
111
|
"""Configure pytest."""
|
|
105
112
|
con_data = ConnectionData()
|
|
106
113
|
|
|
107
114
|
database_url = config.getoption("--hardpy-db-url")
|
|
108
115
|
if database_url:
|
|
109
|
-
con_data.database_url = str(database_url)
|
|
116
|
+
con_data.database_url = str(database_url) # type: ignore
|
|
110
117
|
|
|
111
118
|
is_clear_database = config.getoption("--hardpy-clear-database")
|
|
112
119
|
is_clear_statestore = is_clear_database == str(True)
|
|
@@ -117,16 +124,20 @@ class HardpyPlugin:
|
|
|
117
124
|
|
|
118
125
|
socket_host = config.getoption("--hardpy-sh")
|
|
119
126
|
if socket_host:
|
|
120
|
-
con_data.socket_host = str(socket_host)
|
|
127
|
+
con_data.socket_host = str(socket_host) # type: ignore
|
|
121
128
|
|
|
122
129
|
config.addinivalue_line("markers", "case_name")
|
|
123
130
|
config.addinivalue_line("markers", "module_name")
|
|
124
131
|
config.addinivalue_line("markers", "dependency")
|
|
132
|
+
config.addinivalue_line("markers", "attempt")
|
|
125
133
|
|
|
126
134
|
# must be init after config data is set
|
|
127
|
-
|
|
135
|
+
try:
|
|
136
|
+
self._reporter = HookReporter(is_clear_statestore)
|
|
137
|
+
except RuntimeError as exc:
|
|
138
|
+
exit(str(exc), 1)
|
|
128
139
|
|
|
129
|
-
def pytest_sessionfinish(self, session: Session, exitstatus: int):
|
|
140
|
+
def pytest_sessionfinish(self, session: Session, exitstatus: int) -> None:
|
|
130
141
|
"""Call at the end of test session."""
|
|
131
142
|
if "--collect-only" in session.config.invocation_params.args:
|
|
132
143
|
return
|
|
@@ -143,8 +154,11 @@ class HardpyPlugin:
|
|
|
143
154
|
# Collection hooks
|
|
144
155
|
|
|
145
156
|
def pytest_collection_modifyitems(
|
|
146
|
-
self,
|
|
147
|
-
|
|
157
|
+
self,
|
|
158
|
+
session: Session,
|
|
159
|
+
config: Config,
|
|
160
|
+
items: list[Item], # noqa: ARG002
|
|
161
|
+
) -> None:
|
|
148
162
|
"""Call after collection phase."""
|
|
149
163
|
self._reporter.init_doc(str(PurePath(config.rootpath).name))
|
|
150
164
|
|
|
@@ -160,8 +174,8 @@ class HardpyPlugin:
|
|
|
160
174
|
continue
|
|
161
175
|
try:
|
|
162
176
|
node_info = NodeInfo(item)
|
|
163
|
-
except ValueError:
|
|
164
|
-
error_msg = f"Error creating NodeInfo for item: {item}
|
|
177
|
+
except ValueError as exc:
|
|
178
|
+
error_msg = f"Error creating NodeInfo for item: {item}. {exc}"
|
|
165
179
|
exit(error_msg, 1)
|
|
166
180
|
|
|
167
181
|
self._init_case_result(node_info.module_id, node_info.case_id)
|
|
@@ -181,7 +195,7 @@ class HardpyPlugin:
|
|
|
181
195
|
|
|
182
196
|
# Test running (runtest) hooks
|
|
183
197
|
|
|
184
|
-
def pytest_runtestloop(self, session: Session):
|
|
198
|
+
def pytest_runtestloop(self, session: Session) -> bool | None:
|
|
185
199
|
"""Call at the start of test run."""
|
|
186
200
|
self._progress.set_test_amount(session.testscollected)
|
|
187
201
|
if session.config.option.collectonly:
|
|
@@ -191,8 +205,9 @@ class HardpyPlugin:
|
|
|
191
205
|
# testrun entrypoint
|
|
192
206
|
self._reporter.start()
|
|
193
207
|
self._reporter.update_db_by_doc()
|
|
208
|
+
return None
|
|
194
209
|
|
|
195
|
-
def pytest_runtest_setup(self, item: Item):
|
|
210
|
+
def pytest_runtest_setup(self, item: Item) -> None:
|
|
196
211
|
"""Call before each test setup phase."""
|
|
197
212
|
if item.parent is None:
|
|
198
213
|
self._log.error(f"Test module name for test {item.name} not found.")
|
|
@@ -215,13 +230,53 @@ class HardpyPlugin:
|
|
|
215
230
|
)
|
|
216
231
|
self._reporter.update_db_by_doc()
|
|
217
232
|
|
|
233
|
+
def pytest_runtest_call(self, item: Item) -> None:
|
|
234
|
+
"""Call the test item."""
|
|
235
|
+
node_info = NodeInfo(item)
|
|
236
|
+
self._reporter.set_case_attempt(
|
|
237
|
+
node_info.module_id,
|
|
238
|
+
node_info.case_id,
|
|
239
|
+
1,
|
|
240
|
+
)
|
|
241
|
+
self._reporter.update_db_by_doc()
|
|
242
|
+
|
|
243
|
+
def pytest_runtest_makereport(self, item: Item, call: CallInfo) -> None:
|
|
244
|
+
"""Call after call of each test item."""
|
|
245
|
+
if call.when != "call":
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
node_info = NodeInfo(item)
|
|
249
|
+
attempt = node_info.attempt
|
|
250
|
+
module_id = node_info.module_id
|
|
251
|
+
case_id = node_info.case_id
|
|
252
|
+
|
|
253
|
+
if call.excinfo:
|
|
254
|
+
# first attempt was in pytest_runtest_call
|
|
255
|
+
for current_attempt in range(2, attempt + 1):
|
|
256
|
+
self._reporter.set_module_status(module_id, TestStatus.RUN)
|
|
257
|
+
self._reporter.set_case_status(module_id, case_id, TestStatus.RUN)
|
|
258
|
+
self._reporter.set_case_attempt(module_id, case_id, current_attempt)
|
|
259
|
+
self._reporter.update_db_by_doc()
|
|
260
|
+
|
|
261
|
+
# fmt: off
|
|
262
|
+
try:
|
|
263
|
+
item.runtest()
|
|
264
|
+
call.excinfo = None
|
|
265
|
+
self._reporter.set_case_status(module_id, case_id, TestStatus.PASSED) # noqa: E501
|
|
266
|
+
break
|
|
267
|
+
except AssertionError:
|
|
268
|
+
self._reporter.set_case_status(module_id, case_id, TestStatus.FAILED) # noqa: E501
|
|
269
|
+
if current_attempt == attempt:
|
|
270
|
+
return
|
|
271
|
+
# fmt: on
|
|
272
|
+
|
|
218
273
|
# Reporting hooks
|
|
219
274
|
|
|
220
|
-
def pytest_runtest_logreport(self, report: TestReport):
|
|
275
|
+
def pytest_runtest_logreport(self, report: TestReport) -> bool | None:
|
|
221
276
|
"""Call after call of each test item."""
|
|
222
277
|
if report.when != "call" and report.failed is False:
|
|
223
|
-
# ignore setup and teardown phase
|
|
224
|
-
#
|
|
278
|
+
# ignore setup and teardown phase or continue processing setup
|
|
279
|
+
# and teardown failure (fixture exception handler)
|
|
225
280
|
return True
|
|
226
281
|
|
|
227
282
|
module_id = Path(report.fspath).stem
|
|
@@ -240,11 +295,12 @@ class HardpyPlugin:
|
|
|
240
295
|
assertion_msg = self._decode_assertion_msg(report.longrepr)
|
|
241
296
|
self._reporter.set_assertion_msg(module_id, case_id, assertion_msg)
|
|
242
297
|
self._reporter.set_progress(self._progress.calculate(report.nodeid))
|
|
243
|
-
self._results[module_id][case_id] = report.outcome
|
|
298
|
+
self._results[module_id][case_id] = report.outcome
|
|
244
299
|
|
|
245
300
|
if None not in self._results[module_id].values():
|
|
246
301
|
self._collect_module_result(module_id)
|
|
247
302
|
self._reporter.update_db_by_doc()
|
|
303
|
+
return None
|
|
248
304
|
|
|
249
305
|
# Fixture
|
|
250
306
|
|
|
@@ -262,10 +318,10 @@ class HardpyPlugin:
|
|
|
262
318
|
|
|
263
319
|
# Not hooks
|
|
264
320
|
|
|
265
|
-
def _stop_handler(self, signum: int, frame: Any):
|
|
321
|
+
def _stop_handler(self, signum: int, frame: Any) -> None: # noqa: ANN401, ARG002
|
|
266
322
|
exit("Tests stopped by user")
|
|
267
323
|
|
|
268
|
-
def _init_case_result(self, module_id: str, case_id: str):
|
|
324
|
+
def _init_case_result(self, module_id: str, case_id: str) -> None:
|
|
269
325
|
if self._results.get(module_id) is None:
|
|
270
326
|
self._results[module_id] = {
|
|
271
327
|
"module_status": TestStatus.READY,
|
|
@@ -274,7 +330,7 @@ class HardpyPlugin:
|
|
|
274
330
|
else:
|
|
275
331
|
self._results[module_id][case_id] = None
|
|
276
332
|
|
|
277
|
-
def _collect_module_result(self, module_id: str):
|
|
333
|
+
def _collect_module_result(self, module_id: str) -> None:
|
|
278
334
|
if TestStatus.ERROR in self._results[module_id].values():
|
|
279
335
|
status = TestStatus.ERROR
|
|
280
336
|
elif TestStatus.FAILED in self._results[module_id].values():
|
|
@@ -299,7 +355,7 @@ class HardpyPlugin:
|
|
|
299
355
|
case _:
|
|
300
356
|
return TestStatus.ERROR
|
|
301
357
|
|
|
302
|
-
def _stop_tests(self)
|
|
358
|
+
def _stop_tests(self) -> None:
|
|
303
359
|
"""Update module and case statuses from READY or RUN to STOPPED."""
|
|
304
360
|
for module_id, module_data in self._results.items():
|
|
305
361
|
module_status = module_data["module_status"]
|
|
@@ -330,8 +386,8 @@ class HardpyPlugin:
|
|
|
330
386
|
|
|
331
387
|
def _decode_assertion_msg(
|
|
332
388
|
self,
|
|
333
|
-
error: (
|
|
334
|
-
ExceptionInfo[BaseException]
|
|
389
|
+
error: (
|
|
390
|
+
ExceptionInfo[BaseException]
|
|
335
391
|
| tuple[str, int, str]
|
|
336
392
|
| str
|
|
337
393
|
| TerminalRepr
|
|
@@ -345,41 +401,43 @@ class HardpyPlugin:
|
|
|
345
401
|
match error:
|
|
346
402
|
case str():
|
|
347
403
|
return error
|
|
348
|
-
case tuple() if len(error) == 3:
|
|
404
|
+
case tuple() if len(error) == 3: # noqa: PLR2004
|
|
349
405
|
return error[2]
|
|
350
406
|
case ExceptionInfo():
|
|
351
407
|
error_repr = error.getrepr()
|
|
352
408
|
if isinstance(error_repr, ReprExceptionInfo) and error_repr.reprcrash:
|
|
353
409
|
return error_repr.reprcrash.message
|
|
410
|
+
return None
|
|
354
411
|
case TerminalRepr():
|
|
355
|
-
if isinstance(error, ExceptionRepr) and isinstance(
|
|
356
|
-
error.reprcrash,
|
|
412
|
+
if isinstance(error, ExceptionRepr) and isinstance(
|
|
413
|
+
error.reprcrash,
|
|
414
|
+
ReprFileLocation,
|
|
357
415
|
):
|
|
358
416
|
# remove ansi codes
|
|
359
417
|
ansi_pattern = re_compile(
|
|
360
|
-
r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])" # noqa: E501
|
|
418
|
+
r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])", # noqa: E501
|
|
361
419
|
)
|
|
362
420
|
return ansi_pattern.sub("", error.reprcrash.message)
|
|
363
421
|
return str(error)
|
|
364
422
|
case _:
|
|
365
423
|
return None
|
|
366
424
|
|
|
367
|
-
def _handle_dependency(self, node_info: NodeInfo):
|
|
425
|
+
def _handle_dependency(self, node_info: NodeInfo) -> None:
|
|
368
426
|
dependency = self._dependencies.get(
|
|
369
427
|
TestDependencyInfo(
|
|
370
428
|
node_info.module_id,
|
|
371
429
|
node_info.case_id,
|
|
372
|
-
)
|
|
430
|
+
),
|
|
373
431
|
)
|
|
374
432
|
if dependency and self._is_dependency_failed(dependency):
|
|
375
433
|
self._log.debug(f"Skipping test due to dependency: {dependency}")
|
|
376
434
|
self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
|
|
377
435
|
self._reporter.set_progress(
|
|
378
|
-
self._progress.calculate(f"{node_info.module_id}::{node_info.case_id}")
|
|
436
|
+
self._progress.calculate(f"{node_info.module_id}::{node_info.case_id}"),
|
|
379
437
|
)
|
|
380
438
|
skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
|
|
381
439
|
|
|
382
|
-
def _is_dependency_failed(self, dependency) -> bool:
|
|
440
|
+
def _is_dependency_failed(self, dependency: TestDependencyInfo) -> bool:
|
|
383
441
|
if isinstance(dependency, TestDependencyInfo):
|
|
384
442
|
incorrect_status = {
|
|
385
443
|
TestStatus.FAILED,
|
|
@@ -395,7 +453,7 @@ class HardpyPlugin:
|
|
|
395
453
|
)
|
|
396
454
|
return False
|
|
397
455
|
|
|
398
|
-
def _add_dependency(self, node_info, nodes):
|
|
456
|
+
def _add_dependency(self, node_info: NodeInfo, nodes: dict) -> None:
|
|
399
457
|
dependency = node_info.dependency
|
|
400
458
|
if dependency is None or dependency == "":
|
|
401
459
|
return
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import socket
|
|
5
|
-
from os import environ
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from
|
|
7
|
+
from os import environ
|
|
8
|
+
from time import sleep
|
|
9
|
+
from typing import Any
|
|
8
10
|
from uuid import uuid4
|
|
9
11
|
|
|
10
12
|
from pycouchdb.exceptions import NotFound
|
|
11
13
|
from pydantic import ValidationError
|
|
12
14
|
|
|
13
15
|
from hardpy.pytest_hardpy.db import (
|
|
14
|
-
DatabaseField as DF,
|
|
16
|
+
DatabaseField as DF, # noqa: N817
|
|
15
17
|
ResultRunStore,
|
|
16
18
|
RunStore,
|
|
17
19
|
)
|
|
20
|
+
from hardpy.pytest_hardpy.reporter import RunnerReporter
|
|
18
21
|
from hardpy.pytest_hardpy.utils import (
|
|
19
22
|
ConnectionData,
|
|
20
|
-
|
|
23
|
+
DialogBox,
|
|
21
24
|
DuplicatePartNumberError,
|
|
25
|
+
DuplicateSerialNumberError,
|
|
26
|
+
DuplicateTestStandLocationError,
|
|
22
27
|
DuplicateTestStandNameError,
|
|
23
|
-
DuplicateDialogBoxError,
|
|
24
|
-
DialogBox,
|
|
25
28
|
)
|
|
26
|
-
from hardpy.pytest_hardpy.reporter import RunnerReporter
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@dataclass
|
|
@@ -42,7 +44,7 @@ def get_current_report() -> ResultRunStore | None:
|
|
|
42
44
|
"""
|
|
43
45
|
runstore = RunStore()
|
|
44
46
|
try:
|
|
45
|
-
return runstore.get_document()
|
|
47
|
+
return runstore.get_document() # type: ignore
|
|
46
48
|
except NotFound:
|
|
47
49
|
return None
|
|
48
50
|
except ValidationError:
|
|
@@ -51,7 +53,7 @@ def get_current_report() -> ResultRunStore | None:
|
|
|
51
53
|
return None
|
|
52
54
|
|
|
53
55
|
|
|
54
|
-
def set_dut_info(info: dict):
|
|
56
|
+
def set_dut_info(info: dict) -> None:
|
|
55
57
|
"""Add DUT info to document.
|
|
56
58
|
|
|
57
59
|
Args:
|
|
@@ -64,7 +66,7 @@ def set_dut_info(info: dict):
|
|
|
64
66
|
reporter.update_db_by_doc()
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
def set_dut_serial_number(serial_number: str):
|
|
69
|
+
def set_dut_serial_number(serial_number: str) -> None:
|
|
68
70
|
"""Add DUT serial number to document.
|
|
69
71
|
|
|
70
72
|
Args:
|
|
@@ -81,7 +83,7 @@ def set_dut_serial_number(serial_number: str):
|
|
|
81
83
|
reporter.update_db_by_doc()
|
|
82
84
|
|
|
83
85
|
|
|
84
|
-
def set_dut_part_number(part_number: str):
|
|
86
|
+
def set_dut_part_number(part_number: str) -> None:
|
|
85
87
|
"""Add DUT part number to document.
|
|
86
88
|
|
|
87
89
|
Args:
|
|
@@ -98,7 +100,7 @@ def set_dut_part_number(part_number: str):
|
|
|
98
100
|
reporter.update_db_by_doc()
|
|
99
101
|
|
|
100
102
|
|
|
101
|
-
def set_stand_name(name: str):
|
|
103
|
+
def set_stand_name(name: str) -> None:
|
|
102
104
|
"""Add test stand name to document.
|
|
103
105
|
|
|
104
106
|
Args:
|
|
@@ -115,7 +117,7 @@ def set_stand_name(name: str):
|
|
|
115
117
|
reporter.update_db_by_doc()
|
|
116
118
|
|
|
117
119
|
|
|
118
|
-
def set_stand_info(info: dict):
|
|
120
|
+
def set_stand_info(info: dict) -> None:
|
|
119
121
|
"""Add test stand info to document.
|
|
120
122
|
|
|
121
123
|
Args:
|
|
@@ -128,7 +130,21 @@ def set_stand_info(info: dict):
|
|
|
128
130
|
reporter.update_db_by_doc()
|
|
129
131
|
|
|
130
132
|
|
|
131
|
-
def
|
|
133
|
+
def set_stand_location(location: str) -> None:
|
|
134
|
+
"""Add test stand location to document.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
location (str): test stand location
|
|
138
|
+
"""
|
|
139
|
+
reporter = RunnerReporter()
|
|
140
|
+
key = reporter.generate_key(DF.TEST_STAND, DF.LOCATION)
|
|
141
|
+
if reporter.get_field(key):
|
|
142
|
+
raise DuplicateTestStandLocationError
|
|
143
|
+
reporter.set_doc_value(key, location)
|
|
144
|
+
reporter.update_db_by_doc()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def set_message(msg: str, msg_key: str | None = None) -> None:
|
|
132
148
|
"""Add or update message in current test.
|
|
133
149
|
|
|
134
150
|
Args:
|
|
@@ -160,7 +176,7 @@ def set_message(msg: str, msg_key: Optional[str] = None) -> None:
|
|
|
160
176
|
reporter.update_db_by_doc()
|
|
161
177
|
|
|
162
178
|
|
|
163
|
-
def set_case_artifact(data: dict):
|
|
179
|
+
def set_case_artifact(data: dict) -> None:
|
|
164
180
|
"""Add data to current test case.
|
|
165
181
|
|
|
166
182
|
Artifact saves only in RunStore database
|
|
@@ -184,7 +200,7 @@ def set_case_artifact(data: dict):
|
|
|
184
200
|
reporter.update_db_by_doc()
|
|
185
201
|
|
|
186
202
|
|
|
187
|
-
def set_module_artifact(data: dict):
|
|
203
|
+
def set_module_artifact(data: dict) -> None:
|
|
188
204
|
"""Add data to current test module.
|
|
189
205
|
|
|
190
206
|
Artifact saves only in RunStore database
|
|
@@ -206,7 +222,7 @@ def set_module_artifact(data: dict):
|
|
|
206
222
|
reporter.update_db_by_doc()
|
|
207
223
|
|
|
208
224
|
|
|
209
|
-
def set_run_artifact(data: dict):
|
|
225
|
+
def set_run_artifact(data: dict) -> None:
|
|
210
226
|
"""Add data to current test run.
|
|
211
227
|
|
|
212
228
|
Artifact saves only in RunStore database
|
|
@@ -226,7 +242,7 @@ def set_run_artifact(data: dict):
|
|
|
226
242
|
|
|
227
243
|
|
|
228
244
|
def set_driver_info(drivers: dict) -> None:
|
|
229
|
-
"""Add or update drivers data.
|
|
245
|
+
"""Add or update test stand drivers data.
|
|
230
246
|
|
|
231
247
|
Driver data is stored in both StateStore and RunStore databases.
|
|
232
248
|
|
|
@@ -238,6 +254,7 @@ def set_driver_info(drivers: dict) -> None:
|
|
|
238
254
|
|
|
239
255
|
for driver_name, driver_data in drivers.items():
|
|
240
256
|
key = reporter.generate_key(
|
|
257
|
+
DF.TEST_STAND,
|
|
241
258
|
DF.DRIVERS,
|
|
242
259
|
driver_name,
|
|
243
260
|
)
|
|
@@ -245,7 +262,7 @@ def set_driver_info(drivers: dict) -> None:
|
|
|
245
262
|
reporter.update_db_by_doc()
|
|
246
263
|
|
|
247
264
|
|
|
248
|
-
def run_dialog_box(dialog_box_data: DialogBox) -> Any:
|
|
265
|
+
def run_dialog_box(dialog_box_data: DialogBox) -> Any: # noqa: ANN401
|
|
249
266
|
"""Display a dialog box.
|
|
250
267
|
|
|
251
268
|
Args:
|
|
@@ -273,11 +290,10 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any:
|
|
|
273
290
|
|
|
274
291
|
Raises:
|
|
275
292
|
ValueError: If the 'message' argument is empty.
|
|
276
|
-
DuplicateDialogBoxError: If the dialog box is already caused.
|
|
277
293
|
"""
|
|
278
294
|
if not dialog_box_data.dialog_text:
|
|
279
|
-
|
|
280
|
-
|
|
295
|
+
msg = "The 'dialog_text' argument cannot be empty."
|
|
296
|
+
raise ValueError(msg)
|
|
281
297
|
current_test = _get_current_test()
|
|
282
298
|
reporter = RunnerReporter()
|
|
283
299
|
key = reporter.generate_key(
|
|
@@ -287,13 +303,20 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any:
|
|
|
287
303
|
current_test.case_id,
|
|
288
304
|
DF.DIALOG_BOX,
|
|
289
305
|
)
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
|
|
307
|
+
reporter.set_doc_value(key, {}, statestore_only=True)
|
|
308
|
+
reporter.update_db_by_doc()
|
|
309
|
+
debounce_time = 0.2
|
|
310
|
+
sleep(debounce_time)
|
|
292
311
|
|
|
293
312
|
reporter.set_doc_value(key, dialog_box_data.to_dict(), statestore_only=True)
|
|
294
313
|
reporter.update_db_by_doc()
|
|
295
314
|
|
|
296
315
|
input_dbx_data = _get_socket_raw_data()
|
|
316
|
+
|
|
317
|
+
# cleanup widget
|
|
318
|
+
reporter.set_doc_value(key, {}, statestore_only=True)
|
|
319
|
+
reporter.update_db_by_doc()
|
|
297
320
|
return dialog_box_data.widget.convert_data(input_dbx_data)
|
|
298
321
|
|
|
299
322
|
|
|
@@ -308,9 +331,13 @@ def set_operator_message(msg: str, title: str | None = None) -> None:
|
|
|
308
331
|
title (str | None): Title
|
|
309
332
|
"""
|
|
310
333
|
reporter = RunnerReporter()
|
|
311
|
-
key = reporter.generate_key(
|
|
312
|
-
|
|
313
|
-
)
|
|
334
|
+
key = reporter.generate_key(DF.OPERATOR_MSG)
|
|
335
|
+
|
|
336
|
+
reporter.set_doc_value(key, {}, statestore_only=True)
|
|
337
|
+
reporter.update_db_by_doc()
|
|
338
|
+
debounce_time = 0.2
|
|
339
|
+
sleep(debounce_time)
|
|
340
|
+
|
|
314
341
|
msg_data = {"msg": msg, "title": title, "visible": True}
|
|
315
342
|
reporter.set_doc_value(key, msg_data, statestore_only=True)
|
|
316
343
|
reporter.update_db_by_doc()
|
|
@@ -319,12 +346,28 @@ def set_operator_message(msg: str, title: str | None = None) -> None:
|
|
|
319
346
|
reporter.set_doc_value(key, msg_data, statestore_only=True)
|
|
320
347
|
reporter.update_db_by_doc()
|
|
321
348
|
|
|
349
|
+
# cleanup widget
|
|
350
|
+
reporter.set_doc_value(key, {}, statestore_only=True)
|
|
351
|
+
reporter.update_db_by_doc()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def get_current_attempt() -> int:
|
|
355
|
+
"""Get current attempt.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
int: current attempt
|
|
359
|
+
"""
|
|
360
|
+
reporter = RunnerReporter()
|
|
361
|
+
module_id, case_id = _get_current_test().module_id, _get_current_test().case_id
|
|
362
|
+
return reporter.get_current_attempt(module_id, case_id)
|
|
363
|
+
|
|
322
364
|
|
|
323
365
|
def _get_current_test() -> CurrentTestInfo:
|
|
324
366
|
current_node = environ.get("PYTEST_CURRENT_TEST")
|
|
325
367
|
|
|
326
368
|
if current_node is None:
|
|
327
|
-
|
|
369
|
+
msg = "PYTEST_CURRENT_TEST variable is not set"
|
|
370
|
+
raise RuntimeError(msg)
|
|
328
371
|
|
|
329
372
|
module_delimiter = ".py::"
|
|
330
373
|
module_id_end_index = current_node.find(module_delimiter)
|
|
@@ -351,8 +394,10 @@ def _get_socket_raw_data() -> str:
|
|
|
351
394
|
|
|
352
395
|
try:
|
|
353
396
|
server.bind((con_data.socket_host, con_data.socket_port))
|
|
354
|
-
except
|
|
355
|
-
|
|
397
|
+
except OSError as exc:
|
|
398
|
+
msg = "Socket creating error"
|
|
399
|
+
server.close()
|
|
400
|
+
raise RuntimeError(msg) from exc
|
|
356
401
|
server.listen(1)
|
|
357
402
|
client, _ = server.accept()
|
|
358
403
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import signal
|
|
5
|
-
import subprocess
|
|
6
|
+
import subprocess
|
|
6
7
|
import sys
|
|
7
8
|
from platform import system
|
|
8
9
|
from socket import socket
|
|
@@ -13,7 +14,7 @@ from hardpy.common.config import ConfigManager
|
|
|
13
14
|
class PyTestWrapper:
|
|
14
15
|
"""Wrapper for pytest subprocess."""
|
|
15
16
|
|
|
16
|
-
def __init__(self):
|
|
17
|
+
def __init__(self) -> None:
|
|
17
18
|
self._proc = None
|
|
18
19
|
self.python_executable = sys.executable
|
|
19
20
|
|
|
@@ -65,7 +66,7 @@ class PyTestWrapper:
|
|
|
65
66
|
"--hardpy-pt",
|
|
66
67
|
],
|
|
67
68
|
cwd=ConfigManager().get_tests_path(),
|
|
68
|
-
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
|
|
69
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # type: ignore
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
return True
|
|
@@ -80,11 +81,11 @@ class PyTestWrapper:
|
|
|
80
81
|
if system() == "Linux":
|
|
81
82
|
self._proc.terminate()
|
|
82
83
|
elif system() == "Windows":
|
|
83
|
-
self._proc.send_signal(signal.CTRL_BREAK_EVENT)
|
|
84
|
+
self._proc.send_signal(signal.CTRL_BREAK_EVENT) # type: ignore
|
|
84
85
|
return True
|
|
85
86
|
return False
|
|
86
87
|
|
|
87
|
-
def collect(self, is_clear_database: bool = False) -> bool:
|
|
88
|
+
def collect(self, *, is_clear_database: bool = False) -> bool:
|
|
88
89
|
"""Perform pytest collection.
|
|
89
90
|
|
|
90
91
|
Args:
|
|
@@ -123,7 +124,7 @@ class PyTestWrapper:
|
|
|
123
124
|
)
|
|
124
125
|
return True
|
|
125
126
|
|
|
126
|
-
def send_data(self, data: str):
|
|
127
|
+
def send_data(self, data: str) -> bool:
|
|
127
128
|
"""Send data to pytest subprocess.
|
|
128
129
|
|
|
129
130
|
Args:
|
|
@@ -138,7 +139,7 @@ class PyTestWrapper:
|
|
|
138
139
|
client.connect((self.config.socket.host, self.config.socket.port))
|
|
139
140
|
client.sendall(data.encode("utf-8"))
|
|
140
141
|
client.close()
|
|
141
|
-
except Exception:
|
|
142
|
+
except Exception: # noqa: BLE001
|
|
142
143
|
return False
|
|
143
144
|
return True
|
|
144
145
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
3
|
|
|
4
|
-
from hardpy.pytest_hardpy.reporter.runner_reporter import RunnerReporter
|
|
5
4
|
from hardpy.pytest_hardpy.reporter.hook_reporter import HookReporter
|
|
5
|
+
from hardpy.pytest_hardpy.reporter.runner_reporter import RunnerReporter
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"RunnerReporter",
|