hardpy 0.5.1__py3-none-any.whl → 0.6.1__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 (42) hide show
  1. hardpy/__init__.py +10 -0
  2. hardpy/cli/__init__.py +0 -0
  3. hardpy/cli/cli.py +145 -0
  4. hardpy/cli/template.py +220 -0
  5. hardpy/common/__init__.py +0 -0
  6. hardpy/common/config.py +159 -0
  7. hardpy/hardpy_panel/api.py +49 -6
  8. hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
  9. hardpy/hardpy_panel/frontend/dist/index.html +1 -1
  10. hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js +3 -0
  11. hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js.map +1 -0
  12. hardpy/pytest_hardpy/db/base_connector.py +7 -1
  13. hardpy/pytest_hardpy/db/base_server.py +4 -4
  14. hardpy/pytest_hardpy/db/base_store.py +13 -3
  15. hardpy/pytest_hardpy/db/const.py +3 -0
  16. hardpy/pytest_hardpy/db/schema.py +47 -11
  17. hardpy/pytest_hardpy/db/statestore.py +11 -0
  18. hardpy/pytest_hardpy/plugin.py +93 -33
  19. hardpy/pytest_hardpy/pytest_call.py +65 -5
  20. hardpy/pytest_hardpy/pytest_wrapper.py +62 -60
  21. hardpy/pytest_hardpy/reporter/base.py +1 -1
  22. hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -3
  23. hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +3 -2
  24. hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +2 -2
  25. hardpy/pytest_hardpy/utils/__init__.py +7 -4
  26. hardpy/pytest_hardpy/utils/connection_data.py +17 -0
  27. hardpy/pytest_hardpy/utils/const.py +4 -14
  28. hardpy/pytest_hardpy/utils/dialog_box.py +1 -1
  29. hardpy/pytest_hardpy/utils/exception.py +14 -0
  30. hardpy/pytest_hardpy/utils/node_info.py +1 -1
  31. hardpy/pytest_hardpy/utils/progress_calculator.py +1 -1
  32. hardpy/pytest_hardpy/utils/singleton.py +1 -1
  33. {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/METADATA +25 -57
  34. {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/RECORD +38 -34
  35. {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/entry_points.txt +1 -1
  36. hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js +0 -3
  37. hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js.map +0 -1
  38. hardpy/hardpy_panel/runner.py +0 -54
  39. hardpy/pytest_hardpy/utils/config_data.py +0 -35
  40. /hardpy/hardpy_panel/frontend/dist/static/js/{main.da686f40.js.LICENSE.txt → main.7c954faf.js.LICENSE.txt} +0 -0
  41. {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/WHEEL +0 -0
  42. {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,10 @@
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 requests.exceptions import ConnectionError
5
+
4
6
  from pycouchdb.client import Database
5
- from pycouchdb.exceptions import Conflict
7
+ from pycouchdb.exceptions import Conflict, GenericError
6
8
 
7
9
  from hardpy.pytest_hardpy.db.base_server import BaseServer
8
10
 
@@ -22,3 +24,7 @@ class BaseConnector(BaseServer):
22
24
  except Conflict:
23
25
  # database is already created
24
26
  return self._db_srv.database(self._db_name)
27
+ except GenericError as exc:
28
+ raise RuntimeError(f"Error initializing database {exc}") from exc
29
+ except ConnectionError as exc:
30
+ raise RuntimeError(f"Error initializing database: {exc}") from exc
@@ -3,12 +3,12 @@
3
3
 
4
4
  from pycouchdb import Server as DbServer
5
5
 
6
- from hardpy.pytest_hardpy.utils import ConfigData
6
+ from hardpy.pytest_hardpy.utils import ConnectionData
7
7
 
8
8
 
9
- class BaseServer(object):
9
+ class BaseServer:
10
10
  """Base class for CouchDB server."""
11
11
 
12
12
  def __init__(self):
13
- config = ConfigData()
14
- self._db_srv = DbServer(config.connection_string)
13
+ con_data = ConnectionData()
14
+ self._db_srv = DbServer(con_data.database_url)
@@ -78,20 +78,30 @@ class BaseStore(BaseConnector):
78
78
  DF.MODULES: {},
79
79
  DF.DUT: {
80
80
  DF.SERIAL_NUMBER: None,
81
+ DF.PART_NUMBER: None,
82
+ DF.INFO: {},
83
+ },
84
+ DF.TEST_STAND: {
85
+ DF.NAME: None,
81
86
  DF.INFO: {},
82
87
  },
83
- DF.TEST_STAND: {},
84
88
  DF.DRIVERS: {},
85
89
  }
86
90
 
91
+ # init document
87
92
  if DF.MODULES not in doc:
88
93
  doc[DF.MODULES] = {}
89
94
 
90
- for item in (DF.TEST_STAND, DF.DRIVERS):
91
- doc[item] = {}
95
+ doc[DF.DRIVERS] = {}
92
96
 
93
97
  doc[DF.DUT] = {
94
98
  DF.SERIAL_NUMBER: None,
99
+ DF.PART_NUMBER: None,
100
+ DF.INFO: {},
101
+ }
102
+
103
+ doc[DF.TEST_STAND] = {
104
+ DF.NAME: None,
95
105
  DF.INFO: {},
96
106
  }
97
107
 
@@ -19,8 +19,11 @@ class DatabaseField(str, Enum): # noqa: WPS600
19
19
  PROGRESS = "progress"
20
20
  ARTIFACT = "artifact"
21
21
  DUT = "dut"
22
+ PART_NUMBER = "part_number"
22
23
  INFO = "info"
23
24
  TEST_STAND = "test_stand"
24
25
  SERIAL_NUMBER = "serial_number"
25
26
  DRIVERS = "drivers"
26
27
  DIALOG_BOX = "dialog_box"
28
+ OPERATOR_MSG = "operator_msg"
29
+ ATTEMPT = "attempt"
@@ -1,8 +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
3
 
4
- from typing import Optional
5
-
6
4
  from pydantic import BaseModel, Field, ConfigDict
7
5
 
8
6
  from hardpy.pytest_hardpy.utils import TestStatus as Status
@@ -30,6 +28,7 @@ class CaseStateStore(IBaseResult):
30
28
  "stop_time": 1695817189,
31
29
  "assertion_msg": null,
32
30
  "msg": null,
31
+ "attempt": 1,
33
32
  "dialog_box": {
34
33
  "title_bar": "Example of text input",
35
34
  "dialog_text": "Type some text and press the Confirm button",
@@ -46,6 +45,7 @@ class CaseStateStore(IBaseResult):
46
45
  assertion_msg: str | None = None
47
46
  msg: dict | None = None
48
47
  dialog_box: dict = {}
48
+ attempt: int = 0
49
49
 
50
50
 
51
51
  class CaseRunStore(IBaseResult):
@@ -59,13 +59,15 @@ class CaseRunStore(IBaseResult):
59
59
  "stop_time": 1695817189,
60
60
  "assertion_msg": null,
61
61
  "msg": null,
62
+ "attempt": 1,
62
63
  "artifact": {}
63
64
  }
64
65
  """
65
66
 
66
67
  assertion_msg: str | None = None
67
68
  msg: dict | None = None
68
- artifact: Optional[dict] = {}
69
+ artifact: dict = {}
70
+ attempt: int = 0
69
71
 
70
72
 
71
73
  class ModuleStateStore(IBaseResult):
@@ -118,7 +120,7 @@ class ModuleRunStore(IBaseResult):
118
120
  """
119
121
 
120
122
  cases: dict[str, CaseRunStore] = {}
121
- artifact: Optional[dict] = {}
123
+ artifact: dict = {}
122
124
 
123
125
 
124
126
  class Dut(BaseModel):
@@ -127,9 +129,10 @@ class Dut(BaseModel):
127
129
  Example:
128
130
  "dut": {
129
131
  "serial_number": "a9ad8dca-2c64-4df8-a358-c21e832a32e4",
132
+ "part_number": "part_number_1",
130
133
  "info": {
131
- "batch": "test_batch",
132
- "board_rev": "rev_1"
134
+ "batch": "test_batch",
135
+ "board_rev": "rev_1"
133
136
  }
134
137
  },
135
138
  """
@@ -137,6 +140,25 @@ class Dut(BaseModel):
137
140
  model_config = ConfigDict(extra="forbid")
138
141
 
139
142
  serial_number: str | None
143
+ part_number: str | None
144
+ info: dict = {}
145
+
146
+
147
+ class TestStand(BaseModel):
148
+ """Test stand description.
149
+
150
+ Example:
151
+ "test_stand": {
152
+ "name": "test_stand_1",
153
+ "info": {
154
+ "geo": "Belgrade",
155
+ }
156
+ },
157
+ """
158
+
159
+ model_config = ConfigDict(extra="forbid")
160
+
161
+ name: str | None
140
162
  info: dict = {}
141
163
 
142
164
 
@@ -158,6 +180,7 @@ class ResultStateStore(IBaseResult):
158
180
  "name": "hardpy-stand",
159
181
  "dut": {
160
182
  "serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
183
+ "part_number": "part_1",
161
184
  "info": {
162
185
  "batch": "test_batch",
163
186
  "board_rev": "rev_1"
@@ -165,6 +188,7 @@ class ResultStateStore(IBaseResult):
165
188
  },
166
189
  "test_stand": {
167
190
  "name": "Test stand 1"
191
+ "info": {}
168
192
  },
169
193
  "drivers": {
170
194
  "driver_1": "driver info",
@@ -173,6 +197,11 @@ class ResultStateStore(IBaseResult):
173
197
  "port": 8000
174
198
  }
175
199
  },
200
+ "operator_msg": {
201
+ "msg": "Operator message",
202
+ "title": "Message",
203
+ "visible": "True"
204
+ },
176
205
  "modules": {
177
206
  "test_1_a": {
178
207
  "status": "failed",
@@ -187,6 +216,7 @@ class ResultStateStore(IBaseResult):
187
216
  "stop_time": 1695817264,
188
217
  "assertion_msg": null,
189
218
  "msg": null,
219
+ "attempt": 1,
190
220
  "dialog_box": {
191
221
  "title_bar": "Example of text input",
192
222
  "dialog_text": "Type some text and press the Confirm button",
@@ -204,6 +234,7 @@ class ResultStateStore(IBaseResult):
204
234
  "start_time": 1695817264,
205
235
  "stop_time": 1695817264,
206
236
  "assertion_msg": "The test failed because minute 21 is odd! Try again!",
237
+ "attempt": 1,
207
238
  "msg": [
208
239
  "Current minute 21"
209
240
  ]
@@ -220,10 +251,11 @@ class ResultStateStore(IBaseResult):
220
251
  id: str = Field(..., alias="_id")
221
252
  progress: int
222
253
  timezone: tuple[str, str] | None = None
223
- test_stand: dict = {}
254
+ test_stand: TestStand
224
255
  dut: Dut
225
256
  modules: dict[str, ModuleStateStore] = {}
226
- drivers: Optional[dict] = {}
257
+ drivers: dict = {}
258
+ operator_msg: dict = {}
227
259
 
228
260
 
229
261
  class ResultRunStore(IBaseResult):
@@ -244,6 +276,7 @@ class ResultRunStore(IBaseResult):
244
276
  "name": "hardpy-stand",
245
277
  "dut": {
246
278
  "serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
279
+ "part_number": "part_1",
247
280
  "info": {
248
281
  "batch": "test_batch",
249
282
  "board_rev": "rev_1"
@@ -251,6 +284,7 @@ class ResultRunStore(IBaseResult):
251
284
  },
252
285
  "test_stand": {
253
286
  "name": "Test stand 1"
287
+ "info": {}
254
288
  },
255
289
  "drivers": {
256
290
  "driver_1": "driver info",
@@ -275,6 +309,7 @@ class ResultRunStore(IBaseResult):
275
309
  "stop_time": 1695817264,
276
310
  "assertion_msg": null,
277
311
  "msg": null,
312
+ "attempt": 1,
278
313
  "artifact": {}
279
314
  },
280
315
  "test_minute_parity": {
@@ -286,6 +321,7 @@ class ResultRunStore(IBaseResult):
286
321
  "msg": [
287
322
  "Current minute 21"
288
323
  ],
324
+ "attempt": 1,
289
325
  "artifact": {
290
326
  "data_str": "123DATA",
291
327
  "data_int": 12345,
@@ -306,8 +342,8 @@ class ResultRunStore(IBaseResult):
306
342
  id: str = Field(..., alias="_id")
307
343
  progress: int
308
344
  timezone: tuple[str, str] | None = None
309
- test_stand: dict = {}
345
+ test_stand: TestStand
310
346
  dut: Dut
311
347
  modules: dict[str, ModuleRunStore] = {}
312
- drivers: Optional[dict] = {}
313
- artifact: Optional[dict] = {}
348
+ drivers: dict = {}
349
+ artifact: dict = {}
@@ -3,6 +3,8 @@
3
3
 
4
4
  from logging import getLogger
5
5
 
6
+ from pycouchdb.exceptions import Conflict, NotFound
7
+
6
8
  from hardpy.pytest_hardpy.db.base_store import BaseStore
7
9
  from hardpy.pytest_hardpy.db import ResultStateStore
8
10
  from hardpy.pytest_hardpy.utils import Singleton
@@ -17,3 +19,12 @@ class StateStore(Singleton, BaseStore):
17
19
  self._log = getLogger(__name__)
18
20
  self._schema = ResultStateStore
19
21
  self._initialized = True
22
+
23
+ def clear(self):
24
+ """Clear database."""
25
+ try:
26
+ # Clear the statestore database before each launch
27
+ self._db.delete(self._doc_id)
28
+ except (Conflict, NotFound):
29
+ self._log.debug("Statestore database will be created for the first time")
30
+ self._doc: dict = self._init_doc()
@@ -2,11 +2,11 @@
2
2
  # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
 
4
4
  import signal
5
- from typing import Any, Callable
6
5
  from logging import getLogger
7
6
  from pathlib import Path, PurePath
8
7
  from platform import system
9
8
  from re import compile as re_compile
9
+ from typing import Any, Callable
10
10
 
11
11
  from natsort import natsorted
12
12
  from pytest import (
@@ -31,26 +31,46 @@ from _pytest._code.code import (
31
31
  from hardpy.pytest_hardpy.reporter import HookReporter
32
32
  from hardpy.pytest_hardpy.utils import (
33
33
  TestStatus,
34
- RunStatus,
35
34
  NodeInfo,
36
35
  ProgressCalculator,
37
- ConfigData,
36
+ ConnectionData,
38
37
  )
39
38
  from hardpy.pytest_hardpy.utils.node_info import TestDependencyInfo
40
39
 
41
40
 
42
41
  def pytest_addoption(parser: Parser):
43
42
  """Register argparse-style options."""
44
- config_data = ConfigData()
45
- # fmt: off
46
- parser.addoption("--hardpy-dbu", action="store", default=config_data.db_user, help="database user") # noqa: E501
47
- parser.addoption("--hardpy-dbpw", action="store", default=config_data.db_pswd, help="database user password") # noqa: E501
48
- parser.addoption("--hardpy-dbp", action="store", default=config_data.db_port, help="database port number") # noqa: E501
49
- parser.addoption("--hardpy-dbh", action="store", default=config_data.db_host, help="database hostname") # noqa: E501
50
- parser.addoption("--hardpy-pt", action="store_true", default=False, help="enable pytest-hardpy plugin") # noqa: E501
51
- parser.addoption("--hardpy-sp", action="store", default=config_data.socket_port, help="internal socket port") # noqa: E501
52
- parser.addoption("--hardpy-sa", action="store", default=config_data.socket_addr, help="internal socket address") # noqa: E501
53
- # fmt: on
43
+ con_data = ConnectionData()
44
+ parser.addoption(
45
+ "--hardpy-db-url",
46
+ action="store",
47
+ default=con_data.database_url,
48
+ help="database url",
49
+ )
50
+ parser.addoption(
51
+ "--hardpy-sp",
52
+ action="store",
53
+ default=con_data.socket_port,
54
+ help="internal socket port",
55
+ )
56
+ parser.addoption(
57
+ "--hardpy-sh",
58
+ action="store",
59
+ default=con_data.socket_host,
60
+ help="internal socket host",
61
+ )
62
+ parser.addoption(
63
+ "--hardpy-clear-database",
64
+ action="store",
65
+ default=False,
66
+ help="clear hardpy local database",
67
+ )
68
+ parser.addoption(
69
+ "--hardpy-pt",
70
+ action="store_true",
71
+ default=False,
72
+ help="enable pytest-hardpy plugin",
73
+ )
54
74
 
55
75
 
56
76
  # Bootstrapping hooks
@@ -60,7 +80,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
60
80
  early_config.pluginmanager.register(plugin)
61
81
 
62
82
 
63
- class HardpyPlugin(object):
83
+ class HardpyPlugin:
64
84
  """HardPy integration plugin for pytest.
65
85
 
66
86
  Extends hook functions from pytest API.
@@ -82,28 +102,35 @@ class HardpyPlugin(object):
82
102
 
83
103
  def pytest_configure(self, config: Config):
84
104
  """Configure pytest."""
85
- config_data = ConfigData()
86
- config_data.db_user = config.getoption("--hardpy-dbu")
87
- config_data.db_host = config.getoption("--hardpy-dbh")
88
- config_data.db_pswd = config.getoption("--hardpy-dbpw")
89
- config_data.db_port = config.getoption("--hardpy-dbp")
90
- config_data.socket_port = int(config.getoption("--hardpy-sp"))
91
- config_data.socket_addr = config.getoption("--hardpy-sa")
105
+ con_data = ConnectionData()
106
+
107
+ database_url = config.getoption("--hardpy-db-url")
108
+ if database_url:
109
+ con_data.database_url = str(database_url)
110
+
111
+ is_clear_database = config.getoption("--hardpy-clear-database")
112
+ is_clear_statestore = is_clear_database == str(True)
113
+
114
+ socket_port = config.getoption("--hardpy-sp")
115
+ if socket_port:
116
+ con_data.socket_port = int(socket_port) # type: ignore
117
+
118
+ socket_host = config.getoption("--hardpy-sh")
119
+ if socket_host:
120
+ con_data.socket_host = str(socket_host)
92
121
 
93
122
  config.addinivalue_line("markers", "case_name")
94
123
  config.addinivalue_line("markers", "module_name")
95
124
  config.addinivalue_line("markers", "dependency")
96
125
 
97
126
  # must be init after config data is set
98
- self._reporter = HookReporter()
127
+ try:
128
+ self._reporter = HookReporter(is_clear_statestore)
129
+ except RuntimeError as exc:
130
+ exit(str(exc), 1)
99
131
 
100
132
  def pytest_sessionfinish(self, session: Session, exitstatus: int):
101
- """Call at the end of test session.
102
-
103
- Args:
104
- session (Session): session description
105
- exitstatus (int): exit test status
106
- """
133
+ """Call at the end of test session."""
107
134
  if "--collect-only" in session.config.invocation_params.args:
108
135
  return
109
136
  status = self._get_run_status(exitstatus)
@@ -263,16 +290,46 @@ class HardpyPlugin(object):
263
290
  self._reporter.set_module_status(module_id, status)
264
291
  self._reporter.set_module_stop_time(module_id)
265
292
 
266
- def _get_run_status(self, exitstatus: int) -> RunStatus:
293
+ def _get_run_status(self, exitstatus: int) -> TestStatus:
267
294
  match exitstatus:
268
295
  case ExitCode.OK:
269
- return RunStatus.PASSED
296
+ return TestStatus.PASSED
270
297
  case ExitCode.TESTS_FAILED:
271
- return RunStatus.FAILED
298
+ return TestStatus.FAILED
272
299
  case ExitCode.INTERRUPTED:
273
- return RunStatus.STOPPED
300
+ self._stop_tests()
301
+ return TestStatus.STOPPED
274
302
  case _:
275
- return RunStatus.ERROR
303
+ return TestStatus.ERROR
304
+
305
+ def _stop_tests(self): # noqa: WPS231
306
+ """Update module and case statuses from READY or RUN to STOPPED."""
307
+ for module_id, module_data in self._results.items():
308
+ module_status = module_data["module_status"]
309
+
310
+ # skip not ready and running modules
311
+ if module_status not in {TestStatus.READY, TestStatus.RUN}:
312
+ continue
313
+
314
+ # update module statuses
315
+ self._results[module_id]["module_status"] = TestStatus.STOPPED
316
+ self._reporter.set_module_status(module_id, TestStatus.STOPPED)
317
+
318
+ # update case statuses
319
+ for module_key, module_value in module_data.items():
320
+ # module status is not a case_id
321
+ if module_key == "module_status":
322
+ continue
323
+ # case value is empty - case is not finished
324
+ if module_value is None:
325
+ case_id = module_key
326
+ self._results[module_id][case_id] = TestStatus.STOPPED
327
+ self._reporter.set_case_status(
328
+ module_id,
329
+ case_id,
330
+ TestStatus.STOPPED,
331
+ )
332
+ self._reporter.update_db_by_doc()
276
333
 
277
334
  def _decode_assertion_msg(
278
335
  self,
@@ -320,6 +377,9 @@ class HardpyPlugin(object):
320
377
  if dependency and self._is_dependency_failed(dependency):
321
378
  self._log.debug(f"Skipping test due to dependency: {dependency}")
322
379
  self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
380
+ self._reporter.set_progress(
381
+ self._progress.calculate(f"{node_info.module_id}::{node_info.case_id}")
382
+ )
323
383
  skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
324
384
 
325
385
  def _is_dependency_failed(self, dependency) -> bool:
@@ -16,16 +16,18 @@ from hardpy.pytest_hardpy.db import (
16
16
  RunStore,
17
17
  )
18
18
  from hardpy.pytest_hardpy.utils import (
19
+ ConnectionData,
19
20
  DuplicateSerialNumberError,
21
+ DuplicatePartNumberError,
22
+ DuplicateTestStandNameError,
20
23
  DuplicateDialogBoxError,
21
24
  DialogBox,
22
- ConfigData,
23
25
  )
24
26
  from hardpy.pytest_hardpy.reporter import RunnerReporter
25
27
 
26
28
 
27
29
  @dataclass
28
- class CurrentTestInfo(object):
30
+ class CurrentTestInfo:
29
31
  """Current test info."""
30
32
 
31
33
  module_id: str
@@ -79,6 +81,40 @@ def set_dut_serial_number(serial_number: str):
79
81
  reporter.update_db_by_doc()
80
82
 
81
83
 
84
+ def set_dut_part_number(part_number: str):
85
+ """Add DUT part number to document.
86
+
87
+ Args:
88
+ part_number (str): DUT part number
89
+
90
+ Raises:
91
+ DuplicatePartNumberError: if part number is already set
92
+ """
93
+ reporter = RunnerReporter()
94
+ key = reporter.generate_key(DF.DUT, DF.PART_NUMBER)
95
+ if reporter.get_field(key):
96
+ raise DuplicatePartNumberError
97
+ reporter.set_doc_value(key, part_number)
98
+ reporter.update_db_by_doc()
99
+
100
+
101
+ def set_stand_name(name: str):
102
+ """Add test stand name to document.
103
+
104
+ Args:
105
+ name (str): test stand name
106
+
107
+ Raises:
108
+ DuplicateTestStandNameError: if test stand name is already set
109
+ """
110
+ reporter = RunnerReporter()
111
+ key = reporter.generate_key(DF.TEST_STAND, DF.NAME)
112
+ if reporter.get_field(key):
113
+ raise DuplicateTestStandNameError
114
+ reporter.set_doc_value(key, name)
115
+ reporter.update_db_by_doc()
116
+
117
+
82
118
  def set_stand_info(info: dict):
83
119
  """Add test stand info to document.
84
120
 
@@ -87,7 +123,7 @@ def set_stand_info(info: dict):
87
123
  """
88
124
  reporter = RunnerReporter()
89
125
  for stand_key, stand_value in info.items():
90
- key = reporter.generate_key(DF.TEST_STAND, stand_key)
126
+ key = reporter.generate_key(DF.TEST_STAND, DF.INFO, stand_key)
91
127
  reporter.set_doc_value(key, stand_value)
92
128
  reporter.update_db_by_doc()
93
129
 
@@ -261,6 +297,29 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any:
261
297
  return dialog_box_data.widget.convert_data(input_dbx_data)
262
298
 
263
299
 
300
+ def set_operator_message(msg: str, title: str | None = None) -> None:
301
+ """Set operator message.
302
+
303
+ The function should be used to handle events outside of testing.
304
+ For messages to the operator during testing, there is the function `run_dialog_box`.
305
+
306
+ Args:
307
+ msg (str): Message
308
+ title (str | None): Title
309
+ """
310
+ reporter = RunnerReporter()
311
+ key = reporter.generate_key(
312
+ DF.OPERATOR_MSG,
313
+ )
314
+ msg_data = {"msg": msg, "title": title, "visible": True}
315
+ reporter.set_doc_value(key, msg_data, statestore_only=True)
316
+ reporter.update_db_by_doc()
317
+ is_msg_visible = _get_socket_raw_data()
318
+ msg_data["visible"] = is_msg_visible
319
+ reporter.set_doc_value(key, msg_data, statestore_only=True)
320
+ reporter.update_db_by_doc()
321
+
322
+
264
323
  def _get_current_test() -> CurrentTestInfo:
265
324
  current_node = environ.get("PYTEST_CURRENT_TEST")
266
325
 
@@ -288,9 +347,10 @@ def _get_socket_raw_data() -> str:
288
347
  # create socket connection
289
348
  server = socket.socket()
290
349
  server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
291
- config_data = ConfigData()
350
+ con_data = ConnectionData()
351
+
292
352
  try:
293
- server.bind((config_data.socket_addr, config_data.socket_port))
353
+ server.bind((con_data.socket_host, con_data.socket_port))
294
354
  except socket.error as exc:
295
355
  raise RuntimeError(f"Error creating socket: {exc}")
296
356
  server.listen(1)