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.
Files changed (46) hide show
  1. hardpy/__init__.py +28 -26
  2. hardpy/cli/cli.py +8 -8
  3. hardpy/cli/template.py +6 -6
  4. hardpy/common/config.py +15 -14
  5. hardpy/hardpy_panel/api.py +9 -9
  6. hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
  7. hardpy/hardpy_panel/frontend/dist/index.html +1 -1
  8. hardpy/hardpy_panel/frontend/dist/static/js/main.942e57d4.js +3 -0
  9. hardpy/hardpy_panel/frontend/dist/static/js/main.942e57d4.js.map +1 -0
  10. hardpy/pytest_hardpy/db/__init__.py +3 -4
  11. hardpy/pytest_hardpy/db/base_connector.py +6 -5
  12. hardpy/pytest_hardpy/db/base_server.py +1 -1
  13. hardpy/pytest_hardpy/db/base_store.py +14 -9
  14. hardpy/pytest_hardpy/db/const.py +3 -1
  15. hardpy/pytest_hardpy/db/runstore.py +13 -15
  16. hardpy/pytest_hardpy/db/schema/__init__.py +9 -0
  17. hardpy/pytest_hardpy/db/{schema.py → schema/v1.py} +120 -79
  18. hardpy/pytest_hardpy/db/statestore.py +8 -10
  19. hardpy/pytest_hardpy/plugin.py +103 -48
  20. hardpy/pytest_hardpy/pytest_call.py +75 -30
  21. hardpy/pytest_hardpy/pytest_wrapper.py +8 -7
  22. hardpy/pytest_hardpy/reporter/__init__.py +1 -1
  23. hardpy/pytest_hardpy/reporter/base.py +32 -7
  24. hardpy/pytest_hardpy/reporter/hook_reporter.py +65 -37
  25. hardpy/pytest_hardpy/reporter/runner_reporter.py +6 -8
  26. hardpy/pytest_hardpy/result/__init__.py +1 -1
  27. hardpy/pytest_hardpy/result/couchdb_config.py +20 -16
  28. hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +2 -2
  29. hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +36 -20
  30. hardpy/pytest_hardpy/utils/__init__.py +20 -19
  31. hardpy/pytest_hardpy/utils/connection_data.py +6 -8
  32. hardpy/pytest_hardpy/utils/const.py +1 -1
  33. hardpy/pytest_hardpy/utils/dialog_box.py +34 -22
  34. hardpy/pytest_hardpy/utils/exception.py +8 -8
  35. hardpy/pytest_hardpy/utils/machineid.py +15 -0
  36. hardpy/pytest_hardpy/utils/node_info.py +45 -16
  37. hardpy/pytest_hardpy/utils/progress_calculator.py +4 -3
  38. hardpy/pytest_hardpy/utils/singleton.py +23 -16
  39. {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/METADATA +19 -28
  40. {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/RECORD +44 -42
  41. hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js +0 -3
  42. hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js.map +0 -1
  43. /hardpy/hardpy_panel/frontend/dist/static/js/{main.7c954faf.js.LICENSE.txt → main.942e57d4.js.LICENSE.txt} +0 -0
  44. {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/WHEEL +0 -0
  45. {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/entry_points.txt +0 -0
  46. {hardpy-0.6.1.dist-info → hardpy-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- skip,
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
- ExitCode,
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
- TestStatus,
35
+ ConnectionData,
34
36
  NodeInfo,
35
37
  ProgressCalculator,
36
- ConnectionData,
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(early_config, parser, args):
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, session: Session, config: Config, items: list[Item]
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}\n"
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
- # or continue processing setup and teardown failure (fixture exception handler)
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 # noqa: WPS204
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): # noqa: WPS231
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: ( # noqa: WPS320
337
- ExceptionInfo[BaseException] # noqa: DAR101,DAR201
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( # noqa: WPS337
359
- error.reprcrash, ReprFileLocation
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 typing import Optional, Any
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
- DuplicateSerialNumberError,
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 set_message(msg: str, msg_key: Optional[str] = None) -> None:
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
- raise ValueError("The 'dialog_text' argument cannot be empty.")
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
- if reporter.get_field(key):
291
- raise DuplicateDialogBoxError
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
- DF.OPERATOR_MSG,
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
- raise RuntimeError("PYTEST_CURRENT_TEST variable is not set")
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 socket.error as exc:
355
- raise RuntimeError(f"Error creating socket: {exc}")
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 # noqa: S404
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",