hardpy 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hardpy/__init__.py +9 -1
- hardpy/hardpy_panel/api.py +15 -0
- hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
- hardpy/hardpy_panel/frontend/dist/index.html +1 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.37744128.js +3 -0
- hardpy/hardpy_panel/frontend/dist/static/js/main.37744128.js.map +1 -0
- hardpy/hardpy_panel/runner.py +2 -0
- hardpy/pytest_hardpy/db/base_store.py +21 -11
- hardpy/pytest_hardpy/db/const.py +1 -0
- hardpy/pytest_hardpy/db/schema.py +23 -2
- hardpy/pytest_hardpy/plugin.py +72 -4
- hardpy/pytest_hardpy/pytest_call.py +100 -11
- hardpy/pytest_hardpy/pytest_wrapper.py +29 -0
- hardpy/pytest_hardpy/reporter/base.py +30 -12
- hardpy/pytest_hardpy/reporter/hook_reporter.py +38 -40
- hardpy/pytest_hardpy/utils/__init__.py +17 -1
- hardpy/pytest_hardpy/utils/config_data.py +5 -1
- hardpy/pytest_hardpy/utils/dialog_box.py +100 -0
- hardpy/pytest_hardpy/utils/exception.py +7 -0
- hardpy/pytest_hardpy/utils/node_info.py +66 -0
- {hardpy-0.2.0.dist-info → hardpy-0.4.0.dist-info}/METADATA +9 -2
- {hardpy-0.2.0.dist-info → hardpy-0.4.0.dist-info}/RECORD +26 -25
- hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js.map +0 -1
- /hardpy/hardpy_panel/frontend/dist/static/js/{main.8ef63e9b.js.LICENSE.txt → main.37744128.js.LICENSE.txt} +0 -0
- {hardpy-0.2.0.dist-info → hardpy-0.4.0.dist-info}/WHEEL +0 -0
- {hardpy-0.2.0.dist-info → hardpy-0.4.0.dist-info}/entry_points.txt +0 -0
- {hardpy-0.2.0.dist-info → hardpy-0.4.0.dist-info}/licenses/LICENSE +0 -0
hardpy/hardpy_panel/runner.py
CHANGED
|
@@ -21,6 +21,8 @@ def run():
|
|
|
21
21
|
parser.add_argument("-dbh", "--db_host", type=str, default=config.db_host, help="database hostname") # noqa: E501
|
|
22
22
|
parser.add_argument("-wh", "--web_host", type=str, default=config.web_host, help="web operator panel hostname") # noqa: E501
|
|
23
23
|
parser.add_argument("-wp", "--web_port", type=str, default=config.web_port, help="web operator panel port") # noqa: E501
|
|
24
|
+
parser.add_argument("-sp", "--sck_port", type=int, default=config.socket_port, help="internal socket port") # noqa: E501
|
|
25
|
+
parser.add_argument("-sa", "--sck_addr", type=int, default=config.socket_port, help="internal socket address") # noqa: E501
|
|
24
26
|
parser.add_argument("path", type=str, nargs='?', help="path to test directory")
|
|
25
27
|
# fmt: on
|
|
26
28
|
|
|
@@ -36,19 +36,29 @@ class BaseStore(BaseConnector):
|
|
|
36
36
|
"""
|
|
37
37
|
return glom(self._doc, key)
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
"""
|
|
41
|
-
|
|
39
|
+
def update_doc(self, key: str, value):
|
|
40
|
+
"""Update document.
|
|
41
|
+
|
|
42
|
+
HardPy collecting uses a simple key without dots.
|
|
43
|
+
Assign is used to update a document.
|
|
44
|
+
Assign is a longer function.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
key (str): document key
|
|
48
|
+
value: document value
|
|
49
|
+
"""
|
|
50
|
+
if "." in key:
|
|
51
|
+
assign(self._doc, key, value)
|
|
52
|
+
else:
|
|
53
|
+
self._doc[key] = value
|
|
54
|
+
|
|
55
|
+
def update_db(self):
|
|
56
|
+
"""Update database by current document."""
|
|
42
57
|
try:
|
|
43
58
|
self._doc = self._db.save(self._doc)
|
|
44
|
-
except Conflict
|
|
45
|
-
self.
|
|
46
|
-
|
|
47
|
-
f"when trying to save key={key}, value={value}. "
|
|
48
|
-
"Current document will be changed by "
|
|
49
|
-
"document from database."
|
|
50
|
-
)
|
|
51
|
-
self._doc = self._db.get(self._doc_id)
|
|
59
|
+
except Conflict:
|
|
60
|
+
self._doc["_rev"] = self._db.get(self._doc_id)["_rev"]
|
|
61
|
+
self._doc = self._db.save(self._doc)
|
|
52
62
|
|
|
53
63
|
def get_document(self) -> ModelMetaclass:
|
|
54
64
|
"""Get document by schema.
|
hardpy/pytest_hardpy/db/const.py
CHANGED
|
@@ -29,12 +29,23 @@ class CaseStateStore(IBaseResult):
|
|
|
29
29
|
"start_time": 1695817188,
|
|
30
30
|
"stop_time": 1695817189,
|
|
31
31
|
"assertion_msg": null,
|
|
32
|
-
"msg": null
|
|
32
|
+
"msg": null,
|
|
33
|
+
"dialog_box": {
|
|
34
|
+
"title_bar": "Example of text input",
|
|
35
|
+
"dialog_text": "Type some text and press the Confirm button",
|
|
36
|
+
"widget": {
|
|
37
|
+
"info": {
|
|
38
|
+
"text": "some text"
|
|
39
|
+
},
|
|
40
|
+
"type": "textinput"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
"""
|
|
35
45
|
|
|
36
46
|
assertion_msg: str | None = None
|
|
37
47
|
msg: dict | None = None
|
|
48
|
+
dialog_box: dict = {}
|
|
38
49
|
|
|
39
50
|
|
|
40
51
|
class CaseRunStore(IBaseResult):
|
|
@@ -175,7 +186,17 @@ class ResultStateStore(IBaseResult):
|
|
|
175
186
|
"start_time": 1695817263,
|
|
176
187
|
"stop_time": 1695817264,
|
|
177
188
|
"assertion_msg": null,
|
|
178
|
-
"msg": null
|
|
189
|
+
"msg": null,
|
|
190
|
+
"dialog_box": {
|
|
191
|
+
"title_bar": "Example of text input",
|
|
192
|
+
"dialog_text": "Type some text and press the Confirm button",
|
|
193
|
+
"widget": {
|
|
194
|
+
"info": {
|
|
195
|
+
"text": "some text"
|
|
196
|
+
},
|
|
197
|
+
"type": "textinput"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
179
200
|
},
|
|
180
201
|
"test_minute_parity": {
|
|
181
202
|
"status": "failed",
|
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -9,6 +9,7 @@ from platform import system
|
|
|
9
9
|
|
|
10
10
|
from natsort import natsorted
|
|
11
11
|
from pytest import (
|
|
12
|
+
skip,
|
|
12
13
|
exit,
|
|
13
14
|
TestReport,
|
|
14
15
|
Item,
|
|
@@ -27,6 +28,7 @@ from hardpy.pytest_hardpy.utils import (
|
|
|
27
28
|
ProgressCalculator,
|
|
28
29
|
ConfigData,
|
|
29
30
|
)
|
|
31
|
+
from hardpy.pytest_hardpy.utils.node_info import TestDependencyInfo
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def pytest_addoption(parser: Parser):
|
|
@@ -38,8 +40,11 @@ def pytest_addoption(parser: Parser):
|
|
|
38
40
|
parser.addoption("--hardpy-dbp", action="store", default=config_data.db_port, help="database port number") # noqa: E501
|
|
39
41
|
parser.addoption("--hardpy-dbh", action="store", default=config_data.db_host, help="database hostname") # noqa: E501
|
|
40
42
|
parser.addoption("--hardpy-pt", action="store_true", default=False, help="enable pytest-hardpy plugin") # noqa: E501
|
|
43
|
+
parser.addoption("--hardpy-sp", action="store", default=config_data.socket_port, help="internal socket port") # noqa: E501
|
|
44
|
+
parser.addoption("--hardpy-sa", action="store", default=config_data.socket_addr, help="internal socket address") # noqa: E501
|
|
41
45
|
# fmt: on
|
|
42
46
|
|
|
47
|
+
|
|
43
48
|
# Bootstrapping hooks
|
|
44
49
|
def pytest_load_initial_conftests(early_config, parser, args):
|
|
45
50
|
if "--hardpy-pt" in args:
|
|
@@ -57,6 +62,7 @@ class HardpyPlugin(object):
|
|
|
57
62
|
self._progress = ProgressCalculator()
|
|
58
63
|
self._results = {}
|
|
59
64
|
self._post_run_functions: list[Callable] = []
|
|
65
|
+
self._dependencies = {}
|
|
60
66
|
|
|
61
67
|
if system() == "Linux":
|
|
62
68
|
signal.signal(signal.SIGTERM, self._stop_handler)
|
|
@@ -64,7 +70,6 @@ class HardpyPlugin(object):
|
|
|
64
70
|
signal.signal(signal.SIGBREAK, self._stop_handler)
|
|
65
71
|
self._log = getLogger(__name__)
|
|
66
72
|
|
|
67
|
-
|
|
68
73
|
# Initialization hooks
|
|
69
74
|
|
|
70
75
|
def pytest_configure(self, config: Config):
|
|
@@ -74,9 +79,12 @@ class HardpyPlugin(object):
|
|
|
74
79
|
config_data.db_host = config.getoption("--hardpy-dbh")
|
|
75
80
|
config_data.db_pswd = config.getoption("--hardpy-dbpw")
|
|
76
81
|
config_data.db_port = config.getoption("--hardpy-dbp")
|
|
82
|
+
config_data.socket_port = int(config.getoption("--hardpy-sp"))
|
|
83
|
+
config_data.socket_addr = config.getoption("--hardpy-sa")
|
|
77
84
|
|
|
78
85
|
config.addinivalue_line("markers", "case_name")
|
|
79
86
|
config.addinivalue_line("markers", "module_name")
|
|
87
|
+
config.addinivalue_line("markers", "dependency")
|
|
80
88
|
|
|
81
89
|
# must be init after config data is set
|
|
82
90
|
self._reporter = HookReporter()
|
|
@@ -92,6 +100,8 @@ class HardpyPlugin(object):
|
|
|
92
100
|
return
|
|
93
101
|
status = self._get_run_status(exitstatus)
|
|
94
102
|
self._reporter.finish(status)
|
|
103
|
+
self._reporter.update_db_by_doc()
|
|
104
|
+
self._reporter.compact_all()
|
|
95
105
|
|
|
96
106
|
# call post run methods
|
|
97
107
|
if self._post_run_functions:
|
|
@@ -107,6 +117,7 @@ class HardpyPlugin(object):
|
|
|
107
117
|
self._reporter.init_doc(str(PurePath(config.rootpath).name))
|
|
108
118
|
|
|
109
119
|
nodes = {}
|
|
120
|
+
modules = set()
|
|
110
121
|
|
|
111
122
|
session.items = natsorted(
|
|
112
123
|
session.items,
|
|
@@ -115,18 +126,26 @@ class HardpyPlugin(object):
|
|
|
115
126
|
for item in session.items:
|
|
116
127
|
if item.parent is None:
|
|
117
128
|
continue
|
|
118
|
-
|
|
129
|
+
try:
|
|
130
|
+
node_info = NodeInfo(item)
|
|
131
|
+
except ValueError:
|
|
132
|
+
error_msg = f"Error creating NodeInfo for item: {item}\n"
|
|
133
|
+
exit(error_msg, 1)
|
|
119
134
|
|
|
120
135
|
self._init_case_result(node_info.module_id, node_info.case_id)
|
|
121
|
-
|
|
122
136
|
if node_info.module_id not in nodes:
|
|
123
137
|
nodes[node_info.module_id] = [node_info.case_id]
|
|
124
138
|
else:
|
|
125
139
|
nodes[node_info.module_id].append(node_info.case_id)
|
|
126
140
|
|
|
127
141
|
self._reporter.add_case(node_info)
|
|
128
|
-
|
|
142
|
+
|
|
143
|
+
self._add_dependency(node_info, nodes)
|
|
144
|
+
modules.add(node_info.module_id)
|
|
145
|
+
for module_id in modules:
|
|
146
|
+
self._reporter.set_module_status(module_id, TestStatus.READY)
|
|
129
147
|
self._reporter.update_node_order(nodes)
|
|
148
|
+
self._reporter.update_db_by_doc()
|
|
130
149
|
|
|
131
150
|
# Test running (runtest) hooks
|
|
132
151
|
|
|
@@ -139,6 +158,7 @@ class HardpyPlugin(object):
|
|
|
139
158
|
|
|
140
159
|
# testrun entrypoint
|
|
141
160
|
self._reporter.start()
|
|
161
|
+
self._reporter.update_db_by_doc()
|
|
142
162
|
|
|
143
163
|
def pytest_runtest_setup(self, item: Item):
|
|
144
164
|
"""Call before each test setup phase."""
|
|
@@ -148,6 +168,8 @@ class HardpyPlugin(object):
|
|
|
148
168
|
|
|
149
169
|
node_info = NodeInfo(item)
|
|
150
170
|
|
|
171
|
+
self._handle_dependency(node_info)
|
|
172
|
+
|
|
151
173
|
self._reporter.set_module_status(node_info.module_id, TestStatus.RUN)
|
|
152
174
|
self._reporter.set_module_start_time(node_info.module_id)
|
|
153
175
|
self._reporter.set_case_status(
|
|
@@ -159,6 +181,7 @@ class HardpyPlugin(object):
|
|
|
159
181
|
node_info.module_id,
|
|
160
182
|
node_info.case_id,
|
|
161
183
|
)
|
|
184
|
+
self._reporter.update_db_by_doc()
|
|
162
185
|
|
|
163
186
|
# Reporting hooks
|
|
164
187
|
|
|
@@ -188,6 +211,7 @@ class HardpyPlugin(object):
|
|
|
188
211
|
|
|
189
212
|
if None not in self._results[module_id].values():
|
|
190
213
|
self._collect_module_result(module_id)
|
|
214
|
+
self._reporter.update_db_by_doc()
|
|
191
215
|
|
|
192
216
|
# Fixture
|
|
193
217
|
|
|
@@ -250,3 +274,47 @@ class HardpyPlugin(object):
|
|
|
250
274
|
index = report.find("\nE")
|
|
251
275
|
return report[:index]
|
|
252
276
|
return None
|
|
277
|
+
|
|
278
|
+
def _handle_dependency(self, node_info: NodeInfo):
|
|
279
|
+
dependency = self._dependencies.get(
|
|
280
|
+
TestDependencyInfo(
|
|
281
|
+
node_info.module_id,
|
|
282
|
+
node_info.case_id,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
if dependency and self._is_dependency_failed(dependency):
|
|
286
|
+
self._log.debug(f"Skipping test due to dependency: {dependency}")
|
|
287
|
+
self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
|
|
288
|
+
skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
|
|
289
|
+
|
|
290
|
+
def _is_dependency_failed(self, dependency) -> bool:
|
|
291
|
+
if isinstance(dependency, TestDependencyInfo):
|
|
292
|
+
incorrect_status = {
|
|
293
|
+
TestStatus.FAILED,
|
|
294
|
+
TestStatus.SKIPPED,
|
|
295
|
+
TestStatus.ERROR,
|
|
296
|
+
}
|
|
297
|
+
module_id, case_id = dependency
|
|
298
|
+
if case_id is not None:
|
|
299
|
+
return self._results[module_id][case_id] in incorrect_status
|
|
300
|
+
return any(
|
|
301
|
+
status in incorrect_status
|
|
302
|
+
for status in set(self._results[module_id].values())
|
|
303
|
+
)
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
def _add_dependency(self, node_info, nodes):
|
|
307
|
+
dependency = node_info.dependency
|
|
308
|
+
if dependency is None or dependency == "":
|
|
309
|
+
return
|
|
310
|
+
module_id, case_id = dependency
|
|
311
|
+
if module_id not in nodes:
|
|
312
|
+
error_message = f"Error: Module dependency '{dependency}' not found."
|
|
313
|
+
exit(error_message, 1)
|
|
314
|
+
elif case_id not in nodes[module_id] and case_id is not None:
|
|
315
|
+
error_message = f"Error: Case dependency '{dependency}' not found."
|
|
316
|
+
exit(error_message, 1)
|
|
317
|
+
|
|
318
|
+
self._dependencies[
|
|
319
|
+
TestDependencyInfo(node_info.module_id, node_info.case_id)
|
|
320
|
+
] = dependency
|
|
@@ -1,9 +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
|
+
import socket
|
|
4
5
|
from os import environ
|
|
5
6
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Optional
|
|
7
|
+
from typing import Optional, Any
|
|
7
8
|
from uuid import uuid4
|
|
8
9
|
|
|
9
10
|
from pycouchdb.exceptions import NotFound
|
|
@@ -14,7 +15,14 @@ from hardpy.pytest_hardpy.db import (
|
|
|
14
15
|
ResultRunStore,
|
|
15
16
|
RunStore,
|
|
16
17
|
)
|
|
17
|
-
from hardpy.pytest_hardpy.utils import
|
|
18
|
+
from hardpy.pytest_hardpy.utils import (
|
|
19
|
+
DuplicateSerialNumberError,
|
|
20
|
+
DuplicateDialogBoxError,
|
|
21
|
+
DialogBox,
|
|
22
|
+
ConfigData,
|
|
23
|
+
generate_dialog_box_dict,
|
|
24
|
+
get_dialog_box_data,
|
|
25
|
+
)
|
|
18
26
|
from hardpy.pytest_hardpy.reporter import RunnerReporter
|
|
19
27
|
|
|
20
28
|
|
|
@@ -52,7 +60,8 @@ def set_dut_info(info: dict):
|
|
|
52
60
|
reporter = RunnerReporter()
|
|
53
61
|
for dut_key, dut_value in info.items():
|
|
54
62
|
key = reporter.generate_key(DF.DUT, DF.INFO, dut_key)
|
|
55
|
-
reporter.
|
|
63
|
+
reporter.set_doc_value(key, dut_value)
|
|
64
|
+
reporter.update_db_by_doc()
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
def set_dut_serial_number(serial_number: str):
|
|
@@ -68,7 +77,8 @@ def set_dut_serial_number(serial_number: str):
|
|
|
68
77
|
key = reporter.generate_key(DF.DUT, DF.SERIAL_NUMBER)
|
|
69
78
|
if reporter.get_field(key):
|
|
70
79
|
raise DuplicateSerialNumberError
|
|
71
|
-
reporter.
|
|
80
|
+
reporter.set_doc_value(key, serial_number)
|
|
81
|
+
reporter.update_db_by_doc()
|
|
72
82
|
|
|
73
83
|
|
|
74
84
|
def set_stand_info(info: dict):
|
|
@@ -80,7 +90,8 @@ def set_stand_info(info: dict):
|
|
|
80
90
|
reporter = RunnerReporter()
|
|
81
91
|
for stand_key, stand_value in info.items():
|
|
82
92
|
key = reporter.generate_key(DF.TEST_STAND, stand_key)
|
|
83
|
-
reporter.
|
|
93
|
+
reporter.set_doc_value(key, stand_value)
|
|
94
|
+
reporter.update_db_by_doc()
|
|
84
95
|
|
|
85
96
|
|
|
86
97
|
def set_message(msg: str, msg_key: Optional[str] = None) -> None:
|
|
@@ -111,7 +122,8 @@ def set_message(msg: str, msg_key: Optional[str] = None) -> None:
|
|
|
111
122
|
|
|
112
123
|
msgs[msg_key] = msg
|
|
113
124
|
|
|
114
|
-
reporter.
|
|
125
|
+
reporter.set_doc_value(key, msgs)
|
|
126
|
+
reporter.update_db_by_doc()
|
|
115
127
|
|
|
116
128
|
|
|
117
129
|
def set_case_artifact(data: dict):
|
|
@@ -134,7 +146,8 @@ def set_case_artifact(data: dict):
|
|
|
134
146
|
DF.ARTIFACT,
|
|
135
147
|
stand_key,
|
|
136
148
|
)
|
|
137
|
-
reporter.
|
|
149
|
+
reporter.set_doc_value(key, stand_value, runstore_only=True)
|
|
150
|
+
reporter.update_db_by_doc()
|
|
138
151
|
|
|
139
152
|
|
|
140
153
|
def set_module_artifact(data: dict):
|
|
@@ -155,7 +168,8 @@ def set_module_artifact(data: dict):
|
|
|
155
168
|
DF.ARTIFACT,
|
|
156
169
|
artifact_key,
|
|
157
170
|
)
|
|
158
|
-
reporter.
|
|
171
|
+
reporter.set_doc_value(key, artifact_value, runstore_only=True)
|
|
172
|
+
reporter.update_db_by_doc()
|
|
159
173
|
|
|
160
174
|
|
|
161
175
|
def set_run_artifact(data: dict):
|
|
@@ -173,11 +187,12 @@ def set_run_artifact(data: dict):
|
|
|
173
187
|
DF.ARTIFACT,
|
|
174
188
|
artifact_key,
|
|
175
189
|
)
|
|
176
|
-
reporter.
|
|
190
|
+
reporter.set_doc_value(key, artifact_value, runstore_only=True)
|
|
191
|
+
reporter.update_db_by_doc()
|
|
177
192
|
|
|
178
193
|
|
|
179
194
|
def set_driver_info(drivers: dict) -> None:
|
|
180
|
-
"""
|
|
195
|
+
"""Add or update drivers data.
|
|
181
196
|
|
|
182
197
|
Driver data is stored in both StateStore and RunStore databases.
|
|
183
198
|
|
|
@@ -192,7 +207,58 @@ def set_driver_info(drivers: dict) -> None:
|
|
|
192
207
|
DF.DRIVERS,
|
|
193
208
|
driver_name,
|
|
194
209
|
)
|
|
195
|
-
reporter.
|
|
210
|
+
reporter.set_doc_value(key, driver_data)
|
|
211
|
+
reporter.update_db_by_doc()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_dialog_box(dialog_box_data: DialogBox) -> Any:
|
|
215
|
+
"""Display a dialog box.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
dialog_box_data (DialogBox): Data for creating the dialog box.
|
|
219
|
+
|
|
220
|
+
DialogBox attributes:
|
|
221
|
+
|
|
222
|
+
- dialog_text (str): The text of the dialog box.
|
|
223
|
+
- title_bar (str | None): The title bar of the dialog box.
|
|
224
|
+
If the title_bar field is missing, it is the case name.
|
|
225
|
+
- widget (DialogBoxWidget | None): Widget information.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Any: An object containing the user's response.
|
|
229
|
+
|
|
230
|
+
The type of the return value depends on the widget type:
|
|
231
|
+
|
|
232
|
+
- Without widget: None.
|
|
233
|
+
- TEXT_INPUT: str.
|
|
234
|
+
- NUMERIC_INPUT: float.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
ValueError: If the 'message' argument is empty.
|
|
238
|
+
DuplicateDialogBoxError: If the dialog box is already caused.
|
|
239
|
+
"""
|
|
240
|
+
if not dialog_box_data.dialog_text:
|
|
241
|
+
raise ValueError("The 'dialog_text' argument cannot be empty.")
|
|
242
|
+
|
|
243
|
+
current_test = _get_current_test()
|
|
244
|
+
reporter = RunnerReporter()
|
|
245
|
+
key = reporter.generate_key(
|
|
246
|
+
DF.MODULES,
|
|
247
|
+
current_test.module_id,
|
|
248
|
+
DF.CASES,
|
|
249
|
+
current_test.case_id,
|
|
250
|
+
DF.DIALOG_BOX,
|
|
251
|
+
)
|
|
252
|
+
if reporter.get_field(key):
|
|
253
|
+
raise DuplicateDialogBoxError
|
|
254
|
+
data_dict = generate_dialog_box_dict(dialog_box_data)
|
|
255
|
+
|
|
256
|
+
reporter.set_doc_value(key, data_dict, statestore_only=True)
|
|
257
|
+
reporter.update_db_by_doc()
|
|
258
|
+
|
|
259
|
+
dialog_raw_data = _get_socket_raw_data()
|
|
260
|
+
|
|
261
|
+
return get_dialog_box_data(dialog_raw_data, dialog_box_data.widget)
|
|
196
262
|
|
|
197
263
|
|
|
198
264
|
def _get_current_test() -> CurrentTestInfo:
|
|
@@ -216,3 +282,26 @@ def _get_current_test() -> CurrentTestInfo:
|
|
|
216
282
|
case_id = case_with_stage[:case_id_end_index]
|
|
217
283
|
|
|
218
284
|
return CurrentTestInfo(module_id=module_id, case_id=case_id)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _get_socket_raw_data() -> str:
|
|
288
|
+
# create socket connection
|
|
289
|
+
server = socket.socket()
|
|
290
|
+
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
291
|
+
config_data = ConfigData()
|
|
292
|
+
try:
|
|
293
|
+
server.bind((config_data.socket_addr, config_data.socket_port))
|
|
294
|
+
except socket.error as exc:
|
|
295
|
+
raise RuntimeError(f"Error creating socket: {exc}")
|
|
296
|
+
server.listen(1)
|
|
297
|
+
client, _ = server.accept()
|
|
298
|
+
|
|
299
|
+
# receive data
|
|
300
|
+
max_input_data_len = 1024
|
|
301
|
+
socket_data = client.recv(max_input_data_len).decode("utf-8")
|
|
302
|
+
|
|
303
|
+
# close connection
|
|
304
|
+
client.close()
|
|
305
|
+
server.close()
|
|
306
|
+
|
|
307
|
+
return socket_data
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import sys
|
|
5
5
|
import signal
|
|
6
6
|
import subprocess
|
|
7
|
+
from socket import socket
|
|
7
8
|
from platform import system
|
|
8
9
|
|
|
9
10
|
from hardpy.pytest_hardpy.utils.config_data import ConfigData
|
|
@@ -46,6 +47,10 @@ class PyTestWrapper(object):
|
|
|
46
47
|
self.config.db_user,
|
|
47
48
|
"--hardpy-dbpw",
|
|
48
49
|
self.config.db_pswd,
|
|
50
|
+
"--hardpy-sp",
|
|
51
|
+
str(self.config.socket_port),
|
|
52
|
+
"--hardpy-sa",
|
|
53
|
+
self.config.socket_addr,
|
|
49
54
|
"--hardpy-pt",
|
|
50
55
|
],
|
|
51
56
|
cwd=self.config.tests_dir.absolute(),
|
|
@@ -64,6 +69,10 @@ class PyTestWrapper(object):
|
|
|
64
69
|
self.config.db_user,
|
|
65
70
|
"--hardpy-dbpw",
|
|
66
71
|
self.config.db_pswd,
|
|
72
|
+
"--hardpy-sp",
|
|
73
|
+
str(self.config.socket_port),
|
|
74
|
+
"--hardpy-sa",
|
|
75
|
+
self.config.socket_addr,
|
|
67
76
|
"--hardpy-pt",
|
|
68
77
|
],
|
|
69
78
|
cwd=self.config.tests_dir.absolute(),
|
|
@@ -115,6 +124,26 @@ class PyTestWrapper(object):
|
|
|
115
124
|
)
|
|
116
125
|
return True
|
|
117
126
|
|
|
127
|
+
def confirm_dialog_box(self, dialog_box_output: str):
|
|
128
|
+
"""Set dialog box data to pytest subprocess.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
dialog_box_output (str): dialog box output data
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
bool: True if dialog box was confirmed, else False
|
|
135
|
+
"""
|
|
136
|
+
config_data = ConfigData()
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
client = socket()
|
|
140
|
+
client.connect((config_data.socket_addr, config_data.socket_port))
|
|
141
|
+
client.sendall(dialog_box_output.encode("utf-8"))
|
|
142
|
+
client.close()
|
|
143
|
+
except Exception:
|
|
144
|
+
return False
|
|
145
|
+
return True
|
|
146
|
+
|
|
118
147
|
def is_running(self) -> bool | None:
|
|
119
148
|
"""Check if pytest is running."""
|
|
120
149
|
return self._proc and self._proc.poll() is None
|
|
@@ -14,21 +14,39 @@ class BaseReporter(object):
|
|
|
14
14
|
self._runstore = RunStore()
|
|
15
15
|
self._log = getLogger(__name__)
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
|
|
17
|
+
def set_doc_value(
|
|
18
|
+
self, key: str, value, runstore_only=False, statestore_only=False
|
|
19
|
+
):
|
|
20
|
+
"""Set value to the document.
|
|
21
|
+
|
|
22
|
+
Update a document without writing to the database.
|
|
19
23
|
|
|
20
24
|
Args:
|
|
21
|
-
key (str):
|
|
22
|
-
value
|
|
23
|
-
|
|
24
|
-
be written to
|
|
25
|
-
|
|
26
|
-
be written to
|
|
25
|
+
key (str): document key
|
|
26
|
+
value: document value
|
|
27
|
+
runstore_only (bool, optional): indicates whether data should
|
|
28
|
+
be written to RunStore. Defaults to True.
|
|
29
|
+
statestore_only (bool, optional): indicates whether data should
|
|
30
|
+
be written to StateStore. Defaults to True.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: if both runstore_only and statestore_only are True
|
|
27
34
|
"""
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
if
|
|
31
|
-
self._runstore.
|
|
35
|
+
if runstore_only and statestore_only:
|
|
36
|
+
raise ValueError("Both runstore_only and statestore_only cannot be True")
|
|
37
|
+
if runstore_only:
|
|
38
|
+
self._runstore.update_doc(key, value)
|
|
39
|
+
return
|
|
40
|
+
if statestore_only:
|
|
41
|
+
self._statestore.update_doc(key, value)
|
|
42
|
+
return
|
|
43
|
+
self._runstore.update_doc(key, value)
|
|
44
|
+
self._statestore.update_doc(key, value)
|
|
45
|
+
|
|
46
|
+
def update_db_by_doc(self):
|
|
47
|
+
"""Update database by current document."""
|
|
48
|
+
self._statestore.update_db()
|
|
49
|
+
self._runstore.update_db()
|
|
32
50
|
|
|
33
51
|
def generate_key(self, *args) -> str:
|
|
34
52
|
"""Generate key for database.
|