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.
- hardpy/__init__.py +10 -0
- hardpy/cli/__init__.py +0 -0
- hardpy/cli/cli.py +145 -0
- hardpy/cli/template.py +220 -0
- hardpy/common/__init__.py +0 -0
- hardpy/common/config.py +159 -0
- hardpy/hardpy_panel/api.py +49 -6
- 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.7c954faf.js +3 -0
- hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js.map +1 -0
- hardpy/pytest_hardpy/db/base_connector.py +7 -1
- hardpy/pytest_hardpy/db/base_server.py +4 -4
- hardpy/pytest_hardpy/db/base_store.py +13 -3
- hardpy/pytest_hardpy/db/const.py +3 -0
- hardpy/pytest_hardpy/db/schema.py +47 -11
- hardpy/pytest_hardpy/db/statestore.py +11 -0
- hardpy/pytest_hardpy/plugin.py +93 -33
- hardpy/pytest_hardpy/pytest_call.py +65 -5
- hardpy/pytest_hardpy/pytest_wrapper.py +62 -60
- hardpy/pytest_hardpy/reporter/base.py +1 -1
- hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -3
- hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +3 -2
- hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +2 -2
- hardpy/pytest_hardpy/utils/__init__.py +7 -4
- hardpy/pytest_hardpy/utils/connection_data.py +17 -0
- hardpy/pytest_hardpy/utils/const.py +4 -14
- hardpy/pytest_hardpy/utils/dialog_box.py +1 -1
- hardpy/pytest_hardpy/utils/exception.py +14 -0
- hardpy/pytest_hardpy/utils/node_info.py +1 -1
- hardpy/pytest_hardpy/utils/progress_calculator.py +1 -1
- hardpy/pytest_hardpy/utils/singleton.py +1 -1
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/METADATA +25 -57
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/RECORD +38 -34
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/entry_points.txt +1 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js.map +0 -1
- hardpy/hardpy_panel/runner.py +0 -54
- hardpy/pytest_hardpy/utils/config_data.py +0 -35
- /hardpy/hardpy_panel/frontend/dist/static/js/{main.da686f40.js.LICENSE.txt → main.7c954faf.js.LICENSE.txt} +0 -0
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/WHEEL +0 -0
- {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
|
|
6
|
+
from hardpy.pytest_hardpy.utils import ConnectionData
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class BaseServer
|
|
9
|
+
class BaseServer:
|
|
10
10
|
"""Base class for CouchDB server."""
|
|
11
11
|
|
|
12
12
|
def __init__(self):
|
|
13
|
-
|
|
14
|
-
self._db_srv = DbServer(
|
|
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
|
-
|
|
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
|
|
hardpy/pytest_hardpy/db/const.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
|
|
132
|
-
|
|
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:
|
|
254
|
+
test_stand: TestStand
|
|
224
255
|
dut: Dut
|
|
225
256
|
modules: dict[str, ModuleStateStore] = {}
|
|
226
|
-
drivers:
|
|
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:
|
|
345
|
+
test_stand: TestStand
|
|
310
346
|
dut: Dut
|
|
311
347
|
modules: dict[str, ModuleRunStore] = {}
|
|
312
|
-
drivers:
|
|
313
|
-
artifact:
|
|
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()
|
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
parser.addoption(
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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) ->
|
|
293
|
+
def _get_run_status(self, exitstatus: int) -> TestStatus:
|
|
267
294
|
match exitstatus:
|
|
268
295
|
case ExitCode.OK:
|
|
269
|
-
return
|
|
296
|
+
return TestStatus.PASSED
|
|
270
297
|
case ExitCode.TESTS_FAILED:
|
|
271
|
-
return
|
|
298
|
+
return TestStatus.FAILED
|
|
272
299
|
case ExitCode.INTERRUPTED:
|
|
273
|
-
|
|
300
|
+
self._stop_tests()
|
|
301
|
+
return TestStatus.STOPPED
|
|
274
302
|
case _:
|
|
275
|
-
return
|
|
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
|
|
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
|
-
|
|
350
|
+
con_data = ConnectionData()
|
|
351
|
+
|
|
292
352
|
try:
|
|
293
|
-
server.bind((
|
|
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)
|