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.
Files changed (46) hide show
  1. hardpy/__init__.py +28 -26
  2. hardpy/cli/cli.py +8 -8
  3. hardpy/cli/template.py +17 -7
  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 +9 -2
  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 +107 -49
  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.0.dist-info → hardpy-0.7.0.dist-info}/METADATA +19 -28
  40. {hardpy-0.6.0.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.0.dist-info → hardpy-0.7.0.dist-info}/WHEEL +0 -0
  45. {hardpy-0.6.0.dist-info → hardpy-0.7.0.dist-info}/entry_points.txt +0 -0
  46. {hardpy-0.6.0.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,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
- self._reporter = HookReporter(is_clear_statestore)
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, session: Session, config: Config, items: list[Item]
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}\n"
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
- # 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)
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 # noqa: WPS204
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): # noqa: WPS231
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: ( # noqa: WPS320
334
- ExceptionInfo[BaseException] # noqa: DAR101,DAR201
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( # noqa: WPS337
356
- error.reprcrash, ReprFileLocation
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 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",