hardpy 0.10.1__py3-none-any.whl → 0.11.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.
- hardpy/__init__.py +10 -2
- hardpy/cli/cli.py +91 -13
- hardpy/cli/template.py +0 -4
- hardpy/common/config.py +17 -21
- hardpy/common/stand_cloud/__init__.py +2 -1
- hardpy/common/stand_cloud/connector.py +32 -38
- hardpy/hardpy_panel/api.py +24 -2
- 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.fb8b84a3.js +3 -0
- hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.map +1 -0
- hardpy/pytest_hardpy/db/base_store.py +7 -2
- hardpy/pytest_hardpy/db/const.py +3 -0
- hardpy/pytest_hardpy/db/schema/v1.py +22 -0
- hardpy/pytest_hardpy/plugin.py +27 -20
- hardpy/pytest_hardpy/pytest_call.py +30 -41
- hardpy/pytest_hardpy/pytest_wrapper.py +21 -17
- hardpy/pytest_hardpy/reporter/base.py +9 -4
- hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -0
- hardpy/pytest_hardpy/result/__init__.py +4 -0
- hardpy/pytest_hardpy/result/couchdb_config.py +6 -8
- hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py +6 -9
- hardpy/pytest_hardpy/result/report_reader/stand_cloud_reader.py +84 -0
- hardpy/pytest_hardpy/utils/__init__.py +2 -0
- hardpy/pytest_hardpy/utils/connection_data.py +0 -4
- hardpy/pytest_hardpy/utils/dialog_box.py +61 -8
- hardpy/pytest_hardpy/utils/exception.py +1 -0
- {hardpy-0.10.1.dist-info → hardpy-0.11.1.dist-info}/METADATA +1 -1
- {hardpy-0.10.1.dist-info → hardpy-0.11.1.dist-info}/RECORD +33 -32
- hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js.map +0 -1
- /hardpy/hardpy_panel/frontend/dist/static/js/{main.8a7d8f7d.js.LICENSE.txt → main.fb8b84a3.js.LICENSE.txt} +0 -0
- {hardpy-0.10.1.dist-info → hardpy-0.11.1.dist-info}/WHEEL +0 -0
- {hardpy-0.10.1.dist-info → hardpy-0.11.1.dist-info}/entry_points.txt +0 -0
- {hardpy-0.10.1.dist-info → hardpy-0.11.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -36,8 +36,8 @@ class BaseStore(BaseConnector):
|
|
|
36
36
|
"""
|
|
37
37
|
return glom(self._doc, key)
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
"""Update document.
|
|
39
|
+
def update_doc_value(self, key: str, value: Any) -> None: # noqa: ANN401
|
|
40
|
+
"""Update document value.
|
|
41
41
|
|
|
42
42
|
HardPy collecting uses a simple key without dots.
|
|
43
43
|
Assign is used to update a document.
|
|
@@ -60,6 +60,11 @@ class BaseStore(BaseConnector):
|
|
|
60
60
|
self._doc["_rev"] = self._db.get(self._doc_id)["_rev"]
|
|
61
61
|
self._doc = self._db.save(self._doc)
|
|
62
62
|
|
|
63
|
+
def update_doc(self) -> None:
|
|
64
|
+
"""Update current document by database."""
|
|
65
|
+
self._doc["_rev"] = self._db.get(self._doc_id)["_rev"]
|
|
66
|
+
self._doc = self._db.get(self._doc_id)
|
|
67
|
+
|
|
63
68
|
def get_document(self) -> ModelMetaclass:
|
|
64
69
|
"""Get document by schema.
|
|
65
70
|
|
hardpy/pytest_hardpy/db/const.py
CHANGED
|
@@ -204,6 +204,24 @@ class TestStand(BaseModel):
|
|
|
204
204
|
location: str | None = None
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
class OperatorData(BaseModel):
|
|
208
|
+
"""Operator data from operator panel.
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
```
|
|
212
|
+
{
|
|
213
|
+
"operator_data": {
|
|
214
|
+
"dialog": "hello",
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
model_config = ConfigDict(extra="forbid")
|
|
221
|
+
|
|
222
|
+
dialog: str | None = None
|
|
223
|
+
|
|
224
|
+
|
|
207
225
|
class ResultStateStore(IBaseResult):
|
|
208
226
|
"""Test run description.
|
|
209
227
|
|
|
@@ -218,6 +236,9 @@ class ResultStateStore(IBaseResult):
|
|
|
218
236
|
"status": "failed",
|
|
219
237
|
"name": "hardpy-stand",
|
|
220
238
|
"alert": "",
|
|
239
|
+
"operator_data": {
|
|
240
|
+
"dialog": ""
|
|
241
|
+
},
|
|
221
242
|
"dut": {
|
|
222
243
|
"serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
|
|
223
244
|
"part_number": "part_1",
|
|
@@ -305,6 +326,7 @@ class ResultStateStore(IBaseResult):
|
|
|
305
326
|
modules: dict[str, ModuleStateStore] = {}
|
|
306
327
|
operator_msg: dict = {}
|
|
307
328
|
alert: str
|
|
329
|
+
operator_data: OperatorData
|
|
308
330
|
|
|
309
331
|
|
|
310
332
|
class ResultRunStore(IBaseResult):
|
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -56,17 +56,22 @@ def pytest_addoption(parser: Parser) -> None:
|
|
|
56
56
|
default=con_data.database_url,
|
|
57
57
|
help="database url",
|
|
58
58
|
)
|
|
59
|
+
parser.addoption(
|
|
60
|
+
"--hardpy-tests-name",
|
|
61
|
+
action="store",
|
|
62
|
+
help="HardPy tests suite name",
|
|
63
|
+
)
|
|
64
|
+
# TODO (xorialexandrov): Remove --hardpy-sp and --hardpy-sh in HardPy major version.
|
|
65
|
+
# Addoptions left for compatibility with version 0.10.1 and below
|
|
59
66
|
parser.addoption(
|
|
60
67
|
"--hardpy-sp",
|
|
61
68
|
action="store",
|
|
62
|
-
|
|
63
|
-
help="internal socket port",
|
|
69
|
+
help="DEPRECATED, UNUSED: internal socket port",
|
|
64
70
|
)
|
|
65
71
|
parser.addoption(
|
|
66
72
|
"--hardpy-sh",
|
|
67
73
|
action="store",
|
|
68
|
-
|
|
69
|
-
help="internal socket host",
|
|
74
|
+
help="DEPRECATED, UNUSED: internal socket host",
|
|
70
75
|
)
|
|
71
76
|
parser.addoption(
|
|
72
77
|
"--hardpy-clear-database",
|
|
@@ -117,6 +122,7 @@ class HardpyPlugin:
|
|
|
117
122
|
self._results = {}
|
|
118
123
|
self._post_run_functions: list[Callable] = []
|
|
119
124
|
self._dependencies = {}
|
|
125
|
+
self._tests_name: str = ""
|
|
120
126
|
|
|
121
127
|
if system() == "Linux":
|
|
122
128
|
signal.signal(signal.SIGTERM, self._stop_handler)
|
|
@@ -134,15 +140,13 @@ class HardpyPlugin:
|
|
|
134
140
|
if database_url:
|
|
135
141
|
con_data.database_url = str(database_url) # type: ignore
|
|
136
142
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
tests_name = config.getoption("--hardpy-tests-name")
|
|
144
|
+
if tests_name:
|
|
145
|
+
self._tests_name = str(tests_name)
|
|
146
|
+
else:
|
|
147
|
+
self._tests_name = str(PurePath(config.rootpath).name)
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
if socket_host:
|
|
145
|
-
con_data.socket_host = str(socket_host) # type: ignore
|
|
149
|
+
is_clear_database = config.getoption("--hardpy-clear-database")
|
|
146
150
|
|
|
147
151
|
sc_address = config.getoption("--sc-address")
|
|
148
152
|
if sc_address:
|
|
@@ -182,11 +186,11 @@ class HardpyPlugin:
|
|
|
182
186
|
def pytest_collection_modifyitems(
|
|
183
187
|
self,
|
|
184
188
|
session: Session,
|
|
185
|
-
config: Config,
|
|
189
|
+
config: Config, # noqa: ARG002
|
|
186
190
|
items: list[Item], # noqa: ARG002
|
|
187
191
|
) -> None:
|
|
188
192
|
"""Call after collection phase."""
|
|
189
|
-
self._reporter.init_doc(
|
|
193
|
+
self._reporter.init_doc(self._tests_name)
|
|
190
194
|
|
|
191
195
|
nodes = {}
|
|
192
196
|
modules = set()
|
|
@@ -264,8 +268,8 @@ class HardpyPlugin:
|
|
|
264
268
|
|
|
265
269
|
status = TestStatus.RUN
|
|
266
270
|
is_skip_test = self._is_skip_test(node_info)
|
|
271
|
+
self._reporter.set_module_start_time(node_info.module_id)
|
|
267
272
|
if not is_skip_test:
|
|
268
|
-
self._reporter.set_module_start_time(node_info.module_id)
|
|
269
273
|
self._reporter.set_case_start_time(node_info.module_id, node_info.case_id)
|
|
270
274
|
else:
|
|
271
275
|
status = TestStatus.SKIPPED
|
|
@@ -324,7 +328,11 @@ class HardpyPlugin:
|
|
|
324
328
|
|
|
325
329
|
def pytest_runtest_logreport(self, report: TestReport) -> bool | None:
|
|
326
330
|
"""Call after call of each test item."""
|
|
327
|
-
|
|
331
|
+
is_skipped_by_plugin: bool = False
|
|
332
|
+
if report.when == "setup" and report.skipped is True:
|
|
333
|
+
# plugin-skipped tests should not have start and stop times
|
|
334
|
+
is_skipped_by_plugin = True
|
|
335
|
+
elif report.when != "call" and report.failed is False:
|
|
328
336
|
# ignore setup and teardown phase or continue processing setup
|
|
329
337
|
# and teardown failure (fixture exception handler)
|
|
330
338
|
return True
|
|
@@ -337,10 +345,9 @@ class HardpyPlugin:
|
|
|
337
345
|
case_id,
|
|
338
346
|
TestStatus(report.outcome),
|
|
339
347
|
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
case_id
|
|
343
|
-
)
|
|
348
|
+
# update case stop_time in non-skipped tests or user-skipped tests
|
|
349
|
+
if report.skipped is False or is_skipped_by_plugin is False:
|
|
350
|
+
self._reporter.set_case_stop_time(module_id, case_id)
|
|
344
351
|
|
|
345
352
|
assertion_msg = self._decode_assertion_msg(report.longrepr)
|
|
346
353
|
self._reporter.set_assertion_msg(module_id, case_id, assertion_msg)
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import socket
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from os import environ
|
|
8
|
-
from
|
|
7
|
+
from time import sleep
|
|
9
8
|
from typing import Any
|
|
10
9
|
from uuid import uuid4
|
|
11
10
|
|
|
@@ -19,12 +18,12 @@ from hardpy.pytest_hardpy.db import (
|
|
|
19
18
|
)
|
|
20
19
|
from hardpy.pytest_hardpy.reporter import RunnerReporter
|
|
21
20
|
from hardpy.pytest_hardpy.utils import (
|
|
22
|
-
ConnectionData,
|
|
23
21
|
DialogBox,
|
|
24
22
|
DuplicatePartNumberError,
|
|
25
23
|
DuplicateSerialNumberError,
|
|
26
24
|
DuplicateTestStandLocationError,
|
|
27
25
|
DuplicateTestStandNameError,
|
|
26
|
+
HTMLComponent,
|
|
28
27
|
ImageComponent,
|
|
29
28
|
)
|
|
30
29
|
|
|
@@ -279,6 +278,7 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any: # noqa: ANN401
|
|
|
279
278
|
If the title_bar field is missing, it is the case name.
|
|
280
279
|
- widget (DialogBoxWidget | None): Widget information.
|
|
281
280
|
- image (ImageComponent | None): Image information.
|
|
281
|
+
- html (HTMLComponent | None): HTML information.
|
|
282
282
|
|
|
283
283
|
Returns:
|
|
284
284
|
Any: An object containing the user's response.
|
|
@@ -312,18 +312,18 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any: # noqa: ANN401
|
|
|
312
312
|
reporter.set_doc_value(key, dialog_box_data.to_dict(), statestore_only=True)
|
|
313
313
|
reporter.update_db_by_doc()
|
|
314
314
|
|
|
315
|
-
|
|
316
|
-
input_dbx_data = _get_socket_raw_data()
|
|
315
|
+
input_dbx_data = _get_operator_data()
|
|
317
316
|
|
|
318
317
|
_cleanup_widget(reporter, key)
|
|
319
318
|
return dialog_box_data.widget.convert_data(input_dbx_data)
|
|
320
319
|
|
|
321
320
|
|
|
322
|
-
def set_operator_message(
|
|
321
|
+
def set_operator_message( # noqa: PLR0913
|
|
323
322
|
msg: str,
|
|
324
323
|
title: str | None = None,
|
|
325
324
|
block: bool = True,
|
|
326
325
|
image: ImageComponent | None = None,
|
|
326
|
+
html: HTMLComponent | None = None,
|
|
327
327
|
font_size: int = 14,
|
|
328
328
|
) -> None:
|
|
329
329
|
"""Set operator message.
|
|
@@ -334,7 +334,8 @@ def set_operator_message(
|
|
|
334
334
|
Args:
|
|
335
335
|
msg (str): message
|
|
336
336
|
title (str | None): title
|
|
337
|
-
image (ImageComponent | None): operator message
|
|
337
|
+
image (ImageComponent | None): operator message image
|
|
338
|
+
html (HTMLComponent | None): operator message html page
|
|
338
339
|
block (bool): if True, the function will block until the message is closed
|
|
339
340
|
font_size (int): font size
|
|
340
341
|
"""
|
|
@@ -351,6 +352,7 @@ def set_operator_message(
|
|
|
351
352
|
DF.TITLE: title,
|
|
352
353
|
DF.VISIBLE: True,
|
|
353
354
|
DF.IMAGE: image.to_dict() if image else None,
|
|
355
|
+
DF.HTML: html.to_dict() if html else None,
|
|
354
356
|
DF.ID: str(uuid4()),
|
|
355
357
|
DF.FONT_SIZE: int(font_size),
|
|
356
358
|
}
|
|
@@ -358,10 +360,9 @@ def set_operator_message(
|
|
|
358
360
|
reporter.update_db_by_doc()
|
|
359
361
|
|
|
360
362
|
if block:
|
|
361
|
-
|
|
362
|
-
is_msg_visible = _get_socket_raw_data()
|
|
363
|
-
|
|
363
|
+
is_msg_visible = _get_operator_data()
|
|
364
364
|
msg_data[DF.VISIBLE] = is_msg_visible
|
|
365
|
+
|
|
365
366
|
reporter.set_doc_value(key, msg_data, statestore_only=True)
|
|
366
367
|
reporter.update_db_by_doc()
|
|
367
368
|
|
|
@@ -414,37 +415,25 @@ def _get_current_test() -> CurrentTestInfo:
|
|
|
414
415
|
return CurrentTestInfo(module_id=module_id, case_id=case_id)
|
|
415
416
|
|
|
416
417
|
|
|
417
|
-
def
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
client, _ = server.accept()
|
|
437
|
-
break
|
|
438
|
-
|
|
439
|
-
# receive data
|
|
440
|
-
max_input_data_len = 1024
|
|
441
|
-
socket_data = client.recv(max_input_data_len).decode("utf-8")
|
|
442
|
-
|
|
443
|
-
# close connection
|
|
444
|
-
client.close()
|
|
445
|
-
server.close()
|
|
446
|
-
|
|
447
|
-
return socket_data
|
|
418
|
+
def _get_operator_data() -> str:
|
|
419
|
+
"""Get operator panel data.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
str: operator panel data
|
|
423
|
+
"""
|
|
424
|
+
reporter = RunnerReporter()
|
|
425
|
+
|
|
426
|
+
data = ""
|
|
427
|
+
key = reporter.generate_key(DF.OPERATOR_DATA, DF.DIALOG)
|
|
428
|
+
while not data:
|
|
429
|
+
reporter.update_doc_by_db()
|
|
430
|
+
|
|
431
|
+
data = reporter.get_field(key)
|
|
432
|
+
if data:
|
|
433
|
+
reporter.set_doc_value(key, "", statestore_only=True)
|
|
434
|
+
break
|
|
435
|
+
sleep(0.1)
|
|
436
|
+
return data
|
|
448
437
|
|
|
449
438
|
|
|
450
439
|
def _cleanup_widget(reporter: RunnerReporter, key: str) -> None:
|
|
@@ -6,9 +6,10 @@ import signal
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
8
|
from platform import system
|
|
9
|
-
from socket import socket
|
|
10
9
|
|
|
11
10
|
from hardpy.common.config import ConfigManager
|
|
11
|
+
from hardpy.pytest_hardpy.db import DatabaseField as DF # noqa: N817
|
|
12
|
+
from hardpy.pytest_hardpy.reporter import RunnerReporter
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class PyTestWrapper:
|
|
@@ -16,6 +17,7 @@ class PyTestWrapper:
|
|
|
16
17
|
|
|
17
18
|
def __init__(self) -> None:
|
|
18
19
|
self._proc = None
|
|
20
|
+
self._reporter = RunnerReporter()
|
|
19
21
|
self.python_executable = sys.executable
|
|
20
22
|
|
|
21
23
|
# Make sure test structure is stored in DB
|
|
@@ -43,10 +45,8 @@ class PyTestWrapper:
|
|
|
43
45
|
"pytest",
|
|
44
46
|
"--hardpy-db-url",
|
|
45
47
|
self.config.database.connection_url(),
|
|
46
|
-
"--hardpy-
|
|
47
|
-
|
|
48
|
-
"--hardpy-sh",
|
|
49
|
-
self.config.socket.host,
|
|
48
|
+
"--hardpy-tests-name",
|
|
49
|
+
self.config.tests_name,
|
|
50
50
|
"--sc-address",
|
|
51
51
|
self.config.stand_cloud.address,
|
|
52
52
|
"--sc-connection-only"
|
|
@@ -64,10 +64,8 @@ class PyTestWrapper:
|
|
|
64
64
|
"pytest",
|
|
65
65
|
"--hardpy-db-url",
|
|
66
66
|
self.config.database.connection_url(),
|
|
67
|
-
"--hardpy-
|
|
68
|
-
|
|
69
|
-
"--hardpy-sh",
|
|
70
|
-
self.config.socket.host,
|
|
67
|
+
"--hardpy-tests-name",
|
|
68
|
+
self.config.tests_name,
|
|
71
69
|
"--sc-address",
|
|
72
70
|
self.config.stand_cloud.address,
|
|
73
71
|
"--sc-connection-only"
|
|
@@ -117,10 +115,8 @@ class PyTestWrapper:
|
|
|
117
115
|
"--collect-only",
|
|
118
116
|
"--hardpy-db-url",
|
|
119
117
|
self.config.database.connection_url(),
|
|
120
|
-
"--hardpy-
|
|
121
|
-
|
|
122
|
-
"--hardpy-sh",
|
|
123
|
-
self.config.socket.host,
|
|
118
|
+
"--hardpy-tests-name",
|
|
119
|
+
self.config.tests_name,
|
|
124
120
|
"--hardpy-pt",
|
|
125
121
|
]
|
|
126
122
|
|
|
@@ -144,10 +140,10 @@ class PyTestWrapper:
|
|
|
144
140
|
bool: True if dialog box was confirmed/closed, else False
|
|
145
141
|
"""
|
|
146
142
|
try:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
143
|
+
self._reporter.update_doc_by_db()
|
|
144
|
+
key = self._reporter.generate_key(DF.OPERATOR_DATA, DF.DIALOG)
|
|
145
|
+
self._reporter.set_doc_value(key, data, statestore_only=True)
|
|
146
|
+
self._reporter.update_db_by_doc()
|
|
151
147
|
except Exception: # noqa: BLE001
|
|
152
148
|
return False
|
|
153
149
|
return True
|
|
@@ -159,3 +155,11 @@ class PyTestWrapper:
|
|
|
159
155
|
bool | None: True if self._proc is not None
|
|
160
156
|
"""
|
|
161
157
|
return self._proc and self._proc.poll() is None
|
|
158
|
+
|
|
159
|
+
def get_config(self) -> dict:
|
|
160
|
+
"""Get HardPy configuration.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
dict: HardPy configuration
|
|
164
|
+
"""
|
|
165
|
+
return ConfigManager().get_config().model_dump()
|
|
@@ -45,19 +45,24 @@ class BaseReporter:
|
|
|
45
45
|
msg = "Both runstore_only and statestore_only cannot be True"
|
|
46
46
|
raise ValueError(msg)
|
|
47
47
|
if runstore_only:
|
|
48
|
-
self._runstore.
|
|
48
|
+
self._runstore.update_doc_value(key, value)
|
|
49
49
|
return
|
|
50
50
|
if statestore_only:
|
|
51
|
-
self._statestore.
|
|
51
|
+
self._statestore.update_doc_value(key, value)
|
|
52
52
|
return
|
|
53
|
-
self._runstore.
|
|
54
|
-
self._statestore.
|
|
53
|
+
self._runstore.update_doc_value(key, value)
|
|
54
|
+
self._statestore.update_doc_value(key, value)
|
|
55
55
|
|
|
56
56
|
def update_db_by_doc(self) -> None:
|
|
57
57
|
"""Update database by current document."""
|
|
58
58
|
self._statestore.update_db()
|
|
59
59
|
self._runstore.update_db()
|
|
60
60
|
|
|
61
|
+
def update_doc_by_db(self) -> None:
|
|
62
|
+
"""Update document by current database."""
|
|
63
|
+
self._statestore.update_doc()
|
|
64
|
+
self._runstore.update_doc()
|
|
65
|
+
|
|
61
66
|
def generate_key(self, *args: Any) -> str: # noqa: ANN401
|
|
62
67
|
"""Generate key for database.
|
|
63
68
|
|
|
@@ -38,6 +38,7 @@ class HookReporter(BaseReporter):
|
|
|
38
38
|
self.set_doc_value(DF.ARTIFACT, {}, runstore_only=True)
|
|
39
39
|
self.set_doc_value(DF.OPERATOR_MSG, {}, statestore_only=True)
|
|
40
40
|
self.set_doc_value(DF.ALERT, "", statestore_only=True)
|
|
41
|
+
self.set_doc_value(DF.OPERATOR_DATA, {}, statestore_only=True)
|
|
41
42
|
|
|
42
43
|
test_stand_tz = self.generate_key(DF.TEST_STAND, DF.TIMEZONE)
|
|
43
44
|
self.set_doc_value(test_stand_tz, str(get_localzone().key))
|
|
@@ -45,6 +46,9 @@ class HookReporter(BaseReporter):
|
|
|
45
46
|
test_stand_id_key = self.generate_key(DF.TEST_STAND, DF.HW_ID)
|
|
46
47
|
self.set_doc_value(test_stand_id_key, machine_id())
|
|
47
48
|
|
|
49
|
+
operator_data_key = self.generate_key(DF.OPERATOR_DATA, DF.DIALOG)
|
|
50
|
+
self.set_doc_value(operator_data_key, "", statestore_only=True)
|
|
51
|
+
|
|
48
52
|
def start(self) -> None:
|
|
49
53
|
"""Start test."""
|
|
50
54
|
self._log.debug("Starting test run.")
|
|
@@ -54,6 +58,9 @@ class HookReporter(BaseReporter):
|
|
|
54
58
|
self.set_doc_value(DF.PROGRESS, 0, statestore_only=True)
|
|
55
59
|
self.set_doc_value(DF.ALERT, "", statestore_only=True)
|
|
56
60
|
|
|
61
|
+
operator_data_key = self.generate_key(DF.OPERATOR_DATA, DF.DIALOG)
|
|
62
|
+
self.set_doc_value(operator_data_key, "", statestore_only=True)
|
|
63
|
+
|
|
57
64
|
def finish(self, status: TestStatus) -> None:
|
|
58
65
|
"""Finish test.
|
|
59
66
|
|
|
@@ -6,9 +6,13 @@ from hardpy.pytest_hardpy.result.report_loader.stand_cloud_loader import (
|
|
|
6
6
|
StandCloudLoader,
|
|
7
7
|
)
|
|
8
8
|
from hardpy.pytest_hardpy.result.report_reader.couchdb_reader import CouchdbReader
|
|
9
|
+
from hardpy.pytest_hardpy.result.report_reader.stand_cloud_reader import (
|
|
10
|
+
StandCloudReader,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
__all__ = [
|
|
11
14
|
"CouchdbLoader",
|
|
12
15
|
"CouchdbReader",
|
|
13
16
|
"StandCloudLoader",
|
|
17
|
+
"StandCloudReader",
|
|
14
18
|
]
|
|
@@ -58,17 +58,17 @@ class CouchdbConfig:
|
|
|
58
58
|
|
|
59
59
|
try:
|
|
60
60
|
response = requests.get(host_url, timeout=5)
|
|
61
|
-
except requests.exceptions.RequestException:
|
|
61
|
+
except requests.exceptions.RequestException as exc:
|
|
62
62
|
msg = f"Error CouchDB connecting to {host_url}."
|
|
63
|
-
raise RuntimeError(msg)
|
|
63
|
+
raise RuntimeError(msg) from exc
|
|
64
64
|
|
|
65
65
|
# fmt: off
|
|
66
66
|
try:
|
|
67
67
|
couchdb_dict = ast.literal_eval(response._content.decode("utf-8")) # type: ignore # noqa: SLF001
|
|
68
68
|
couchdb_dict.get("couchdb", False)
|
|
69
|
-
except Exception
|
|
69
|
+
except Exception as exc:
|
|
70
70
|
msg = f"Address {host_url} does not provide CouchDB attributes."
|
|
71
|
-
raise RuntimeError(msg)
|
|
71
|
+
raise RuntimeError(msg) from exc
|
|
72
72
|
# fmt: on
|
|
73
73
|
|
|
74
74
|
credentials = f"{self.user}:{self.password}"
|
|
@@ -93,8 +93,6 @@ class CouchdbConfig:
|
|
|
93
93
|
if requests.get(request, timeout=5).status_code == success:
|
|
94
94
|
return "http"
|
|
95
95
|
raise OSError # noqa: TRY301
|
|
96
|
-
except OSError:
|
|
96
|
+
except OSError as exc:
|
|
97
97
|
msg = f"Error connecting to couchdb server {self.host}:{self.port}."
|
|
98
|
-
raise RuntimeError(
|
|
99
|
-
msg,
|
|
100
|
-
)
|
|
98
|
+
raise RuntimeError(msg) from exc
|
|
@@ -4,9 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from oauthlib.oauth2.rfc6749.errors import
|
|
7
|
+
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
|
|
8
8
|
from requests.exceptions import HTTPError
|
|
9
|
-
from requests_oauth2client.tokens import ExpiredAccessToken
|
|
10
9
|
|
|
11
10
|
from hardpy.common.stand_cloud.connector import StandCloudConnector, StandCloudError
|
|
12
11
|
from hardpy.pytest_hardpy.utils import ConnectionData
|
|
@@ -45,16 +44,14 @@ class StandCloudLoader:
|
|
|
45
44
|
Raises:
|
|
46
45
|
StandCloudError: if report not uploaded to StandCloud
|
|
47
46
|
"""
|
|
48
|
-
api = self._sc_connector.get_api("
|
|
47
|
+
api = self._sc_connector.get_api("test_report")
|
|
49
48
|
|
|
50
49
|
try:
|
|
51
50
|
resp = api.post(verify=self._verify_ssl, json=report.model_dump())
|
|
52
|
-
except
|
|
53
|
-
raise StandCloudError(str(exc))
|
|
54
|
-
except
|
|
55
|
-
raise StandCloudError(exc.description)
|
|
56
|
-
except InvalidGrantError as exc:
|
|
57
|
-
raise StandCloudError(exc.description)
|
|
51
|
+
except RuntimeError as exc:
|
|
52
|
+
raise StandCloudError(str(exc)) from exc
|
|
53
|
+
except OAuth2Error as exc:
|
|
54
|
+
raise StandCloudError(exc.description) from exc
|
|
58
55
|
except HTTPError as exc:
|
|
59
56
|
return exc.response # type: ignore
|
|
60
57
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Copyright (c) 2025 Everypin
|
|
2
|
+
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
|
|
8
|
+
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
|
|
9
|
+
from requests.exceptions import RequestException
|
|
10
|
+
|
|
11
|
+
from hardpy.common.stand_cloud.connector import StandCloudConnector, StandCloudError
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from requests import Response
|
|
17
|
+
from requests_oauth2client import ApiClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StandCloudReader:
|
|
21
|
+
"""StandCloud data reader.
|
|
22
|
+
|
|
23
|
+
The link to the documentation can be obtained by address:
|
|
24
|
+
https://service_name/integration/api/v1/docs
|
|
25
|
+
|
|
26
|
+
For example:
|
|
27
|
+
https://demo.standcloud.io/integration/api/v1/docs
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, sc_connector: StandCloudConnector) -> None:
|
|
31
|
+
"""Create StandCloud reader.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
sc_connector (StandCloudConnector): StandCloud connector
|
|
35
|
+
"""
|
|
36
|
+
self._verify_ssl = not __debug__
|
|
37
|
+
self._sc_connector = sc_connector
|
|
38
|
+
|
|
39
|
+
def test_run(self, run_id: str) -> Response:
|
|
40
|
+
"""Get run data from '/test_run' endpoint.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
run_id (str): UUIDv4 test run identifier.
|
|
44
|
+
Example: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Response: test run data.
|
|
48
|
+
"""
|
|
49
|
+
return self._request(endpoint=f"test_run/{run_id}")
|
|
50
|
+
|
|
51
|
+
def tested_dut(self, params: dict[str, Any]) -> Response:
|
|
52
|
+
"""Get tested DUT's data from '/tested_dut' endpoint.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
params (dict[str, Any]): tested DUT filters:
|
|
56
|
+
Examples: {
|
|
57
|
+
"test_stand_name": "Stand 1",
|
|
58
|
+
"part_number": "part_number_1",
|
|
59
|
+
"firmware_version": "1.2.3",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Response: tested dut data.
|
|
64
|
+
"""
|
|
65
|
+
return self._request(endpoint="tested_dut", params=params)
|
|
66
|
+
|
|
67
|
+
def _request(self, endpoint: str, params: dict[str, Any] | None = None) -> Response:
|
|
68
|
+
api = self._build_api(endpoint=endpoint, params=params)
|
|
69
|
+
try:
|
|
70
|
+
resp = api.get(verify=self._verify_ssl)
|
|
71
|
+
except RuntimeError as exc:
|
|
72
|
+
raise StandCloudError(str(exc)) from exc
|
|
73
|
+
except OAuth2Error as exc:
|
|
74
|
+
raise StandCloudError(exc.description) from exc
|
|
75
|
+
except RequestException as exc:
|
|
76
|
+
return exc.response # type: ignore
|
|
77
|
+
|
|
78
|
+
return resp
|
|
79
|
+
|
|
80
|
+
def _build_api(self, endpoint: str, params: dict | None = None) -> ApiClient:
|
|
81
|
+
if params is None:
|
|
82
|
+
return self._sc_connector.get_api(f"{endpoint}")
|
|
83
|
+
encoded_params = urlencode(params)
|
|
84
|
+
return self._sc_connector.get_api(f"{endpoint}?{encoded_params}")
|
|
@@ -7,6 +7,7 @@ from hardpy.pytest_hardpy.utils.dialog_box import (
|
|
|
7
7
|
BaseWidget,
|
|
8
8
|
CheckboxWidget,
|
|
9
9
|
DialogBox,
|
|
10
|
+
HTMLComponent,
|
|
10
11
|
ImageComponent,
|
|
11
12
|
MultistepWidget,
|
|
12
13
|
NumericInputWidget,
|
|
@@ -36,6 +37,7 @@ __all__ = [
|
|
|
36
37
|
"DuplicateSerialNumberError",
|
|
37
38
|
"DuplicateTestStandLocationError",
|
|
38
39
|
"DuplicateTestStandNameError",
|
|
40
|
+
"HTMLComponent",
|
|
39
41
|
"ImageComponent",
|
|
40
42
|
"ImageError",
|
|
41
43
|
"MultistepWidget",
|
|
@@ -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 socket import gethostname
|
|
5
|
-
|
|
6
4
|
from hardpy.pytest_hardpy.utils.singleton import SingletonMeta
|
|
7
5
|
|
|
8
6
|
|
|
@@ -11,7 +9,5 @@ class ConnectionData(metaclass=SingletonMeta):
|
|
|
11
9
|
|
|
12
10
|
def __init__(self) -> None:
|
|
13
11
|
self.database_url: str = "http://dev:dev@localhost:5984/"
|
|
14
|
-
self.socket_host: str = gethostname()
|
|
15
|
-
self.socket_port: int = 6525
|
|
16
12
|
self.sc_address: str = ""
|
|
17
13
|
self.sc_connection_only: bool = False
|