hardpy 0.5.1__py3-none-any.whl → 0.6.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 (41) 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 +210 -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_server.py +4 -4
  13. hardpy/pytest_hardpy/db/base_store.py +13 -3
  14. hardpy/pytest_hardpy/db/const.py +3 -0
  15. hardpy/pytest_hardpy/db/schema.py +47 -11
  16. hardpy/pytest_hardpy/db/statestore.py +11 -0
  17. hardpy/pytest_hardpy/plugin.py +90 -33
  18. hardpy/pytest_hardpy/pytest_call.py +65 -5
  19. hardpy/pytest_hardpy/pytest_wrapper.py +62 -60
  20. hardpy/pytest_hardpy/reporter/base.py +1 -1
  21. hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -3
  22. hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +3 -2
  23. hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +2 -2
  24. hardpy/pytest_hardpy/utils/__init__.py +7 -4
  25. hardpy/pytest_hardpy/utils/connection_data.py +17 -0
  26. hardpy/pytest_hardpy/utils/const.py +4 -14
  27. hardpy/pytest_hardpy/utils/dialog_box.py +1 -1
  28. hardpy/pytest_hardpy/utils/exception.py +14 -0
  29. hardpy/pytest_hardpy/utils/node_info.py +1 -1
  30. hardpy/pytest_hardpy/utils/progress_calculator.py +1 -1
  31. hardpy/pytest_hardpy/utils/singleton.py +1 -1
  32. {hardpy-0.5.1.dist-info → hardpy-0.6.0.dist-info}/METADATA +25 -57
  33. {hardpy-0.5.1.dist-info → hardpy-0.6.0.dist-info}/RECORD +37 -33
  34. {hardpy-0.5.1.dist-info → hardpy-0.6.0.dist-info}/entry_points.txt +1 -1
  35. hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js +0 -3
  36. hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js.map +0 -1
  37. hardpy/hardpy_panel/runner.py +0 -54
  38. hardpy/pytest_hardpy/utils/config_data.py +0 -35
  39. /hardpy/hardpy_panel/frontend/dist/static/js/{main.da686f40.js.LICENSE.txt → main.7c954faf.js.LICENSE.txt} +0 -0
  40. {hardpy-0.5.1.dist-info → hardpy-0.6.0.dist-info}/WHEEL +0 -0
  41. {hardpy-0.5.1.dist-info → hardpy-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,32 @@ 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
+ self._reporter = HookReporter(is_clear_statestore)
99
128
 
100
129
  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
- """
130
+ """Call at the end of test session."""
107
131
  if "--collect-only" in session.config.invocation_params.args:
108
132
  return
109
133
  status = self._get_run_status(exitstatus)
@@ -263,16 +287,46 @@ class HardpyPlugin(object):
263
287
  self._reporter.set_module_status(module_id, status)
264
288
  self._reporter.set_module_stop_time(module_id)
265
289
 
266
- def _get_run_status(self, exitstatus: int) -> RunStatus:
290
+ def _get_run_status(self, exitstatus: int) -> TestStatus:
267
291
  match exitstatus:
268
292
  case ExitCode.OK:
269
- return RunStatus.PASSED
293
+ return TestStatus.PASSED
270
294
  case ExitCode.TESTS_FAILED:
271
- return RunStatus.FAILED
295
+ return TestStatus.FAILED
272
296
  case ExitCode.INTERRUPTED:
273
- return RunStatus.STOPPED
297
+ self._stop_tests()
298
+ return TestStatus.STOPPED
274
299
  case _:
275
- return RunStatus.ERROR
300
+ return TestStatus.ERROR
301
+
302
+ def _stop_tests(self): # noqa: WPS231
303
+ """Update module and case statuses from READY or RUN to STOPPED."""
304
+ for module_id, module_data in self._results.items():
305
+ module_status = module_data["module_status"]
306
+
307
+ # skip not ready and running modules
308
+ if module_status not in {TestStatus.READY, TestStatus.RUN}:
309
+ continue
310
+
311
+ # update module statuses
312
+ self._results[module_id]["module_status"] = TestStatus.STOPPED
313
+ self._reporter.set_module_status(module_id, TestStatus.STOPPED)
314
+
315
+ # update case statuses
316
+ for module_key, module_value in module_data.items():
317
+ # module status is not a case_id
318
+ if module_key == "module_status":
319
+ continue
320
+ # case value is empty - case is not finished
321
+ if module_value is None:
322
+ case_id = module_key
323
+ self._results[module_id][case_id] = TestStatus.STOPPED
324
+ self._reporter.set_case_status(
325
+ module_id,
326
+ case_id,
327
+ TestStatus.STOPPED,
328
+ )
329
+ self._reporter.update_db_by_doc()
276
330
 
277
331
  def _decode_assertion_msg(
278
332
  self,
@@ -320,6 +374,9 @@ class HardpyPlugin(object):
320
374
  if dependency and self._is_dependency_failed(dependency):
321
375
  self._log.debug(f"Skipping test due to dependency: {dependency}")
322
376
  self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
377
+ self._reporter.set_progress(
378
+ self._progress.calculate(f"{node_info.module_id}::{node_info.case_id}")
379
+ )
323
380
  skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
324
381
 
325
382
  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)