hardpy 0.6.1__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 +6 -6
- 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 +6 -5
- 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 +103 -48
- 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.1.dist-info → hardpy-0.7.0.dist-info}/METADATA +19 -28
- {hardpy-0.6.1.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.1.dist-info → hardpy-0.7.0.dist-info}/WHEEL +0 -0
- {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/entry_points.txt +0 -0
- {hardpy-0.6.1.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,11 +124,12 @@ 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:
|
|
@@ -129,7 +137,7 @@ class HardpyPlugin:
|
|
|
129
137
|
except RuntimeError as exc:
|
|
130
138
|
exit(str(exc), 1)
|
|
131
139
|
|
|
132
|
-
def pytest_sessionfinish(self, session: Session, exitstatus: int):
|
|
140
|
+
def pytest_sessionfinish(self, session: Session, exitstatus: int) -> None:
|
|
133
141
|
"""Call at the end of test session."""
|
|
134
142
|
if "--collect-only" in session.config.invocation_params.args:
|
|
135
143
|
return
|
|
@@ -146,8 +154,11 @@ class HardpyPlugin:
|
|
|
146
154
|
# Collection hooks
|
|
147
155
|
|
|
148
156
|
def pytest_collection_modifyitems(
|
|
149
|
-
self,
|
|
150
|
-
|
|
157
|
+
self,
|
|
158
|
+
session: Session,
|
|
159
|
+
config: Config,
|
|
160
|
+
items: list[Item], # noqa: ARG002
|
|
161
|
+
) -> None:
|
|
151
162
|
"""Call after collection phase."""
|
|
152
163
|
self._reporter.init_doc(str(PurePath(config.rootpath).name))
|
|
153
164
|
|
|
@@ -163,8 +174,8 @@ class HardpyPlugin:
|
|
|
163
174
|
continue
|
|
164
175
|
try:
|
|
165
176
|
node_info = NodeInfo(item)
|
|
166
|
-
except ValueError:
|
|
167
|
-
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}"
|
|
168
179
|
exit(error_msg, 1)
|
|
169
180
|
|
|
170
181
|
self._init_case_result(node_info.module_id, node_info.case_id)
|
|
@@ -184,7 +195,7 @@ class HardpyPlugin:
|
|
|
184
195
|
|
|
185
196
|
# Test running (runtest) hooks
|
|
186
197
|
|
|
187
|
-
def pytest_runtestloop(self, session: Session):
|
|
198
|
+
def pytest_runtestloop(self, session: Session) -> bool | None:
|
|
188
199
|
"""Call at the start of test run."""
|
|
189
200
|
self._progress.set_test_amount(session.testscollected)
|
|
190
201
|
if session.config.option.collectonly:
|
|
@@ -194,8 +205,9 @@ class HardpyPlugin:
|
|
|
194
205
|
# testrun entrypoint
|
|
195
206
|
self._reporter.start()
|
|
196
207
|
self._reporter.update_db_by_doc()
|
|
208
|
+
return None
|
|
197
209
|
|
|
198
|
-
def pytest_runtest_setup(self, item: Item):
|
|
210
|
+
def pytest_runtest_setup(self, item: Item) -> None:
|
|
199
211
|
"""Call before each test setup phase."""
|
|
200
212
|
if item.parent is None:
|
|
201
213
|
self._log.error(f"Test module name for test {item.name} not found.")
|
|
@@ -218,13 +230,53 @@ class HardpyPlugin:
|
|
|
218
230
|
)
|
|
219
231
|
self._reporter.update_db_by_doc()
|
|
220
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
|
+
|
|
221
273
|
# Reporting hooks
|
|
222
274
|
|
|
223
|
-
def pytest_runtest_logreport(self, report: TestReport):
|
|
275
|
+
def pytest_runtest_logreport(self, report: TestReport) -> bool | None:
|
|
224
276
|
"""Call after call of each test item."""
|
|
225
277
|
if report.when != "call" and report.failed is False:
|
|
226
|
-
# ignore setup and teardown phase
|
|
227
|
-
#
|
|
278
|
+
# ignore setup and teardown phase or continue processing setup
|
|
279
|
+
# and teardown failure (fixture exception handler)
|
|
228
280
|
return True
|
|
229
281
|
|
|
230
282
|
module_id = Path(report.fspath).stem
|
|
@@ -243,11 +295,12 @@ class HardpyPlugin:
|
|
|
243
295
|
assertion_msg = self._decode_assertion_msg(report.longrepr)
|
|
244
296
|
self._reporter.set_assertion_msg(module_id, case_id, assertion_msg)
|
|
245
297
|
self._reporter.set_progress(self._progress.calculate(report.nodeid))
|
|
246
|
-
self._results[module_id][case_id] = report.outcome
|
|
298
|
+
self._results[module_id][case_id] = report.outcome
|
|
247
299
|
|
|
248
300
|
if None not in self._results[module_id].values():
|
|
249
301
|
self._collect_module_result(module_id)
|
|
250
302
|
self._reporter.update_db_by_doc()
|
|
303
|
+
return None
|
|
251
304
|
|
|
252
305
|
# Fixture
|
|
253
306
|
|
|
@@ -265,10 +318,10 @@ class HardpyPlugin:
|
|
|
265
318
|
|
|
266
319
|
# Not hooks
|
|
267
320
|
|
|
268
|
-
def _stop_handler(self, signum: int, frame: Any):
|
|
321
|
+
def _stop_handler(self, signum: int, frame: Any) -> None: # noqa: ANN401, ARG002
|
|
269
322
|
exit("Tests stopped by user")
|
|
270
323
|
|
|
271
|
-
def _init_case_result(self, module_id: str, case_id: str):
|
|
324
|
+
def _init_case_result(self, module_id: str, case_id: str) -> None:
|
|
272
325
|
if self._results.get(module_id) is None:
|
|
273
326
|
self._results[module_id] = {
|
|
274
327
|
"module_status": TestStatus.READY,
|
|
@@ -277,7 +330,7 @@ class HardpyPlugin:
|
|
|
277
330
|
else:
|
|
278
331
|
self._results[module_id][case_id] = None
|
|
279
332
|
|
|
280
|
-
def _collect_module_result(self, module_id: str):
|
|
333
|
+
def _collect_module_result(self, module_id: str) -> None:
|
|
281
334
|
if TestStatus.ERROR in self._results[module_id].values():
|
|
282
335
|
status = TestStatus.ERROR
|
|
283
336
|
elif TestStatus.FAILED in self._results[module_id].values():
|
|
@@ -302,7 +355,7 @@ class HardpyPlugin:
|
|
|
302
355
|
case _:
|
|
303
356
|
return TestStatus.ERROR
|
|
304
357
|
|
|
305
|
-
def _stop_tests(self)
|
|
358
|
+
def _stop_tests(self) -> None:
|
|
306
359
|
"""Update module and case statuses from READY or RUN to STOPPED."""
|
|
307
360
|
for module_id, module_data in self._results.items():
|
|
308
361
|
module_status = module_data["module_status"]
|
|
@@ -333,8 +386,8 @@ class HardpyPlugin:
|
|
|
333
386
|
|
|
334
387
|
def _decode_assertion_msg(
|
|
335
388
|
self,
|
|
336
|
-
error: (
|
|
337
|
-
ExceptionInfo[BaseException]
|
|
389
|
+
error: (
|
|
390
|
+
ExceptionInfo[BaseException]
|
|
338
391
|
| tuple[str, int, str]
|
|
339
392
|
| str
|
|
340
393
|
| TerminalRepr
|
|
@@ -348,41 +401,43 @@ class HardpyPlugin:
|
|
|
348
401
|
match error:
|
|
349
402
|
case str():
|
|
350
403
|
return error
|
|
351
|
-
case tuple() if len(error) == 3:
|
|
404
|
+
case tuple() if len(error) == 3: # noqa: PLR2004
|
|
352
405
|
return error[2]
|
|
353
406
|
case ExceptionInfo():
|
|
354
407
|
error_repr = error.getrepr()
|
|
355
408
|
if isinstance(error_repr, ReprExceptionInfo) and error_repr.reprcrash:
|
|
356
409
|
return error_repr.reprcrash.message
|
|
410
|
+
return None
|
|
357
411
|
case TerminalRepr():
|
|
358
|
-
if isinstance(error, ExceptionRepr) and isinstance(
|
|
359
|
-
error.reprcrash,
|
|
412
|
+
if isinstance(error, ExceptionRepr) and isinstance(
|
|
413
|
+
error.reprcrash,
|
|
414
|
+
ReprFileLocation,
|
|
360
415
|
):
|
|
361
416
|
# remove ansi codes
|
|
362
417
|
ansi_pattern = re_compile(
|
|
363
|
-
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
|
|
364
419
|
)
|
|
365
420
|
return ansi_pattern.sub("", error.reprcrash.message)
|
|
366
421
|
return str(error)
|
|
367
422
|
case _:
|
|
368
423
|
return None
|
|
369
424
|
|
|
370
|
-
def _handle_dependency(self, node_info: NodeInfo):
|
|
425
|
+
def _handle_dependency(self, node_info: NodeInfo) -> None:
|
|
371
426
|
dependency = self._dependencies.get(
|
|
372
427
|
TestDependencyInfo(
|
|
373
428
|
node_info.module_id,
|
|
374
429
|
node_info.case_id,
|
|
375
|
-
)
|
|
430
|
+
),
|
|
376
431
|
)
|
|
377
432
|
if dependency and self._is_dependency_failed(dependency):
|
|
378
433
|
self._log.debug(f"Skipping test due to dependency: {dependency}")
|
|
379
434
|
self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
|
|
380
435
|
self._reporter.set_progress(
|
|
381
|
-
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}"),
|
|
382
437
|
)
|
|
383
438
|
skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
|
|
384
439
|
|
|
385
|
-
def _is_dependency_failed(self, dependency) -> bool:
|
|
440
|
+
def _is_dependency_failed(self, dependency: TestDependencyInfo) -> bool:
|
|
386
441
|
if isinstance(dependency, TestDependencyInfo):
|
|
387
442
|
incorrect_status = {
|
|
388
443
|
TestStatus.FAILED,
|
|
@@ -398,7 +453,7 @@ class HardpyPlugin:
|
|
|
398
453
|
)
|
|
399
454
|
return False
|
|
400
455
|
|
|
401
|
-
def _add_dependency(self, node_info, nodes):
|
|
456
|
+
def _add_dependency(self, node_info: NodeInfo, nodes: dict) -> None:
|
|
402
457
|
dependency = node_info.dependency
|
|
403
458
|
if dependency is None or dependency == "":
|
|
404
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",
|