hardpy 0.13.0__py3-none-any.whl → 0.15.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 +45 -10
- hardpy/cli/cli.py +32 -28
- hardpy/common/config.py +5 -4
- hardpy/common/stand_cloud/connector.py +2 -2
- hardpy/hardpy_panel/api.py +13 -3
- hardpy/hardpy_panel/frontend/dist/assets/{allPaths-Cg7WZDXy.js → allPaths-CV5wjLMB.js} +1 -1
- hardpy/hardpy_panel/frontend/dist/assets/{allPathsLoader-C79wUwqR.js → allPathsLoader-JIzW_pSb.js} +2 -2
- hardpy/hardpy_panel/frontend/dist/assets/browser-ponyfill-CccdstaD.js +2 -0
- hardpy/hardpy_panel/frontend/dist/assets/index-6RIgWzcZ.js +790 -0
- hardpy/hardpy_panel/frontend/dist/assets/{splitPathsBySizeLoader-hWuLTMwD.js → splitPathsBySizeLoader-DkZadBcn.js} +1 -1
- hardpy/hardpy_panel/frontend/dist/index.html +1 -1
- hardpy/hardpy_panel/frontend/dist/locales/de/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/en/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/es/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/fr/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/ja/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/ru/translation.json +60 -0
- hardpy/hardpy_panel/frontend/dist/locales/zh/translation.json +60 -0
- hardpy/pytest_hardpy/db/base_store.py +23 -0
- hardpy/pytest_hardpy/db/const.py +40 -19
- hardpy/pytest_hardpy/db/schema/v1.py +140 -326
- hardpy/pytest_hardpy/plugin.py +32 -1
- hardpy/pytest_hardpy/pytest_call.py +331 -22
- hardpy/pytest_hardpy/pytest_wrapper.py +25 -34
- hardpy/pytest_hardpy/reporter/hook_reporter.py +53 -2
- hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py +8 -2
- hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +1 -5
- hardpy/pytest_hardpy/utils/__init__.py +25 -11
- hardpy/pytest_hardpy/utils/const.py +72 -0
- hardpy/pytest_hardpy/utils/exception.py +7 -32
- hardpy/pytest_hardpy/utils/node_info.py +55 -1
- hardpy/pytest_hardpy/utils/stand_type.py +198 -0
- {hardpy-0.13.0.dist-info → hardpy-0.15.0.dist-info}/METADATA +2 -1
- {hardpy-0.13.0.dist-info → hardpy-0.15.0.dist-info}/RECORD +37 -28
- hardpy/hardpy_panel/frontend/dist/assets/index-De5CJ3kt.js +0 -790
- {hardpy-0.13.0.dist-info → hardpy-0.15.0.dist-info}/WHEEL +0 -0
- {hardpy-0.13.0.dist-info → hardpy-0.15.0.dist-info}/entry_points.txt +0 -0
- {hardpy-0.13.0.dist-info → hardpy-0.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,11 +2,20 @@
|
|
|
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
|
+
from abc import ABC
|
|
6
|
+
from collections.abc import Mapping # noqa: TC003
|
|
7
|
+
from datetime import datetime # noqa: TC003
|
|
5
8
|
from typing import ClassVar
|
|
6
9
|
|
|
7
10
|
from pydantic import BaseModel, ConfigDict, Field
|
|
8
11
|
|
|
9
|
-
from hardpy.pytest_hardpy.utils import
|
|
12
|
+
from hardpy.pytest_hardpy.utils import (
|
|
13
|
+
ChartType,
|
|
14
|
+
ComparisonOperation as CompOp,
|
|
15
|
+
Group,
|
|
16
|
+
MeasurementType,
|
|
17
|
+
TestStatus as Status,
|
|
18
|
+
)
|
|
10
19
|
|
|
11
20
|
|
|
12
21
|
class IBaseResult(BaseModel):
|
|
@@ -21,203 +30,165 @@ class IBaseResult(BaseModel):
|
|
|
21
30
|
|
|
22
31
|
|
|
23
32
|
class CaseStateStore(IBaseResult):
|
|
24
|
-
"""Test case description.
|
|
25
|
-
|
|
26
|
-
Example:
|
|
27
|
-
```
|
|
28
|
-
{
|
|
29
|
-
"test_one": {
|
|
30
|
-
"status": "passed",
|
|
31
|
-
"name": "Test 2",
|
|
32
|
-
"start_time": 1695817188,
|
|
33
|
-
"stop_time": 1695817189,
|
|
34
|
-
"assertion_msg": null,
|
|
35
|
-
"msg": null,
|
|
36
|
-
"attempt": 1,
|
|
37
|
-
"dialog_box": {
|
|
38
|
-
"title_bar": "Example of text input",
|
|
39
|
-
"dialog_text": "Type some text and press the Confirm button",
|
|
40
|
-
"widget": {
|
|
41
|
-
"info": {
|
|
42
|
-
"text": "some text"
|
|
43
|
-
},
|
|
44
|
-
"type": "textinput"
|
|
45
|
-
},
|
|
46
|
-
visible: true,
|
|
47
|
-
id: "af6ac3e7-7ce8-4a6b-bb9d-88c3e10b5c7a",
|
|
48
|
-
font_size: 14
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
"""
|
|
33
|
+
"""Test case description."""
|
|
54
34
|
|
|
55
35
|
assertion_msg: str | None = None
|
|
56
36
|
msg: dict | None = None
|
|
57
|
-
|
|
37
|
+
measurements: list[NumericMeasurement | StringMeasurement] = []
|
|
38
|
+
chart: Chart | None = None
|
|
58
39
|
attempt: int = 0
|
|
40
|
+
group: Group
|
|
41
|
+
dialog_box: dict = {}
|
|
59
42
|
|
|
60
43
|
|
|
61
44
|
class CaseRunStore(IBaseResult):
|
|
62
|
-
"""Test case description with artifact.
|
|
63
|
-
|
|
64
|
-
Example:
|
|
65
|
-
```
|
|
66
|
-
{
|
|
67
|
-
"test_one": {
|
|
68
|
-
"status": "passed",
|
|
69
|
-
"name": "Test 2",
|
|
70
|
-
"start_time": 1695817188,
|
|
71
|
-
"stop_time": 1695817189,
|
|
72
|
-
"assertion_msg": null,
|
|
73
|
-
"msg": null,
|
|
74
|
-
"artifact": {}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
"""
|
|
45
|
+
"""Test case description with artifact."""
|
|
79
46
|
|
|
80
47
|
assertion_msg: str | None = None
|
|
81
48
|
msg: dict | None = None
|
|
49
|
+
measurements: list[NumericMeasurement | StringMeasurement] = []
|
|
50
|
+
chart: Chart | None = None
|
|
51
|
+
attempt: int = 0
|
|
52
|
+
group: Group
|
|
82
53
|
artifact: dict = {}
|
|
83
54
|
|
|
84
55
|
|
|
85
56
|
class ModuleStateStore(IBaseResult):
|
|
86
|
-
"""Test module description.
|
|
87
|
-
|
|
88
|
-
Example:
|
|
89
|
-
```
|
|
90
|
-
{
|
|
91
|
-
"test_2_b": {
|
|
92
|
-
"status": "passed",
|
|
93
|
-
"name": "Module 2",
|
|
94
|
-
"start_time": 1695816886,
|
|
95
|
-
"stop_time": 1695817016,
|
|
96
|
-
"cases": {
|
|
97
|
-
"test_one": {
|
|
98
|
-
"status": "passed",
|
|
99
|
-
"name": "Test 1",
|
|
100
|
-
"start_time": 1695817015,
|
|
101
|
-
"stop_time": 1695817016,
|
|
102
|
-
"assertion_msg": null,
|
|
103
|
-
"msg": null
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
"""
|
|
57
|
+
"""Test module description."""
|
|
110
58
|
|
|
111
59
|
cases: dict[str, CaseStateStore] = {}
|
|
60
|
+
group: Group
|
|
112
61
|
|
|
113
62
|
|
|
114
63
|
class ModuleRunStore(IBaseResult):
|
|
115
|
-
"""Test module description.
|
|
116
|
-
|
|
117
|
-
Example:
|
|
118
|
-
```
|
|
119
|
-
{
|
|
120
|
-
"test_2_b": {
|
|
121
|
-
"status": "passed",
|
|
122
|
-
"name": "Module 2",
|
|
123
|
-
"start_time": 1695816886,
|
|
124
|
-
"stop_time": 1695817016,
|
|
125
|
-
"artifact": {},
|
|
126
|
-
"cases": {
|
|
127
|
-
"test_one": {
|
|
128
|
-
"status": "passed",
|
|
129
|
-
"name": "Test 1",
|
|
130
|
-
"start_time": 1695817015,
|
|
131
|
-
"stop_time": 1695817016,
|
|
132
|
-
"assertion_msg": null,
|
|
133
|
-
"msg": null,
|
|
134
|
-
"artifact": {}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
"""
|
|
64
|
+
"""Test module description."""
|
|
141
65
|
|
|
142
66
|
cases: dict[str, CaseRunStore] = {}
|
|
67
|
+
group: Group
|
|
143
68
|
artifact: dict = {}
|
|
144
69
|
|
|
145
70
|
|
|
146
71
|
class Dut(BaseModel):
|
|
147
|
-
"""Device under test description.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
72
|
+
"""Device under test description."""
|
|
73
|
+
|
|
74
|
+
model_config = ConfigDict(extra="forbid")
|
|
75
|
+
|
|
76
|
+
name: str | None = None
|
|
77
|
+
type: str | None = None
|
|
78
|
+
serial_number: str | None = None
|
|
79
|
+
part_number: str | None = None
|
|
80
|
+
revision: str | None = None
|
|
81
|
+
sub_units: list[SubUnit] = []
|
|
82
|
+
info: Mapping[str, str | int | float | datetime] = {}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SubUnit(BaseModel):
|
|
86
|
+
"""Sub unit of DUT description."""
|
|
87
|
+
|
|
88
|
+
model_config = ConfigDict(extra="forbid")
|
|
89
|
+
|
|
90
|
+
name: str | None = None
|
|
91
|
+
type: str | None = None
|
|
92
|
+
serial_number: str | None = None
|
|
93
|
+
part_number: str | None = None
|
|
94
|
+
revision: str | None = None
|
|
95
|
+
info: Mapping[str, str | int | float | datetime] = {}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Instrument(BaseModel):
|
|
99
|
+
"""Instrument (power supply, oscilloscope and others) description."""
|
|
163
100
|
|
|
164
101
|
model_config = ConfigDict(extra="forbid")
|
|
165
102
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
103
|
+
name: str | None = None
|
|
104
|
+
revision: str | None = None
|
|
105
|
+
number: int | None = None
|
|
106
|
+
comment: str | None = None
|
|
107
|
+
info: Mapping[str, str | int | float | datetime] = {}
|
|
169
108
|
|
|
170
109
|
|
|
171
110
|
class TestStand(BaseModel):
|
|
172
|
-
"""Test stand description.
|
|
173
|
-
|
|
174
|
-
Example:
|
|
175
|
-
```
|
|
176
|
-
{
|
|
177
|
-
"test_stand": {
|
|
178
|
-
"hw_id": "840982098ca2459a7b22cc608eff65d4",
|
|
179
|
-
"name": "test_stand_1",
|
|
180
|
-
"info": {
|
|
181
|
-
"geo": "Belgrade"
|
|
182
|
-
},
|
|
183
|
-
"timezone": "Europe/Belgrade",
|
|
184
|
-
"drivers": {
|
|
185
|
-
"driver_1": "driver info",
|
|
186
|
-
"driver_2": {
|
|
187
|
-
"state": "active",
|
|
188
|
-
"port": 8000
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
"location": "Belgrade_1",
|
|
192
|
-
"number": 2
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
"""
|
|
111
|
+
"""Test stand description."""
|
|
197
112
|
|
|
198
113
|
model_config = ConfigDict(extra="forbid")
|
|
199
114
|
|
|
200
115
|
hw_id: str | None = None
|
|
201
116
|
name: str | None = None
|
|
117
|
+
revision: str | None = None
|
|
202
118
|
timezone: str | None = None
|
|
203
|
-
drivers: dict = {}
|
|
204
|
-
info: dict = {}
|
|
205
119
|
location: str | None = None
|
|
206
120
|
number: int | None = None
|
|
121
|
+
drivers: dict = {} # deprecated, remove in v2
|
|
122
|
+
instruments: list[Instrument] = []
|
|
123
|
+
info: Mapping[str, str | int | float | datetime] = {}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Process(BaseModel):
|
|
127
|
+
"""Production process description."""
|
|
128
|
+
|
|
129
|
+
model_config = ConfigDict(extra="forbid")
|
|
130
|
+
|
|
131
|
+
name: str | None = None
|
|
132
|
+
number: int | None = None
|
|
133
|
+
info: Mapping[str, str | int | float | datetime] = {}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class IBaseMeasurement(BaseModel, ABC):
|
|
137
|
+
"""Base class for all measurement models."""
|
|
138
|
+
|
|
139
|
+
model_config = ConfigDict(extra="allow")
|
|
140
|
+
|
|
141
|
+
type: MeasurementType
|
|
142
|
+
name: str | None = Field(default=None)
|
|
143
|
+
operation: CompOp | None = Field(default=None)
|
|
144
|
+
result: bool | None = Field(default_factory=lambda: None)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class NumericMeasurement(IBaseMeasurement):
|
|
148
|
+
"""Numeric measurement description."""
|
|
149
|
+
|
|
150
|
+
model_config = ConfigDict(extra="forbid")
|
|
151
|
+
|
|
152
|
+
type: MeasurementType = Field(default=MeasurementType.NUMERIC)
|
|
153
|
+
value: int | float
|
|
154
|
+
name: str | None = Field(default=None)
|
|
155
|
+
unit: str | None = Field(default=None)
|
|
156
|
+
|
|
157
|
+
comparison_value: float | int | None = Field(default=None)
|
|
158
|
+
|
|
159
|
+
lower_limit: float | int | None = Field(default=None)
|
|
160
|
+
upper_limit: float | int | None = Field(default=None)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class StringMeasurement(IBaseMeasurement):
|
|
164
|
+
"""String measurement description."""
|
|
165
|
+
|
|
166
|
+
model_config = ConfigDict(extra="forbid")
|
|
167
|
+
|
|
168
|
+
type: MeasurementType = Field(default=MeasurementType.STRING)
|
|
169
|
+
value: str
|
|
170
|
+
name: str | None = Field(default=None)
|
|
171
|
+
casesensitive: bool = Field(default=True)
|
|
172
|
+
|
|
173
|
+
comparison_value: str | None = Field(default=None)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class Chart(BaseModel):
|
|
177
|
+
"""Chart description."""
|
|
178
|
+
|
|
179
|
+
model_config = ConfigDict(extra="forbid")
|
|
180
|
+
|
|
181
|
+
type: ChartType = Field(default=ChartType.LINE)
|
|
182
|
+
title: str | None = Field(default=None)
|
|
183
|
+
x_label: str | None = Field(default=None)
|
|
184
|
+
y_label: str | None = Field(default=None)
|
|
185
|
+
marker_name: list[str | None] = Field(default=[])
|
|
186
|
+
x_data: list[list[int | float]] = Field(default_factory=lambda: []) # noqa: PIE807
|
|
187
|
+
y_data: list[list[int | float]] = Field(default_factory=lambda: []) # noqa: PIE807
|
|
207
188
|
|
|
208
189
|
|
|
209
190
|
class OperatorData(BaseModel):
|
|
210
|
-
"""Operator data from operator panel.
|
|
211
|
-
|
|
212
|
-
Example:
|
|
213
|
-
```
|
|
214
|
-
{
|
|
215
|
-
"operator_data": {
|
|
216
|
-
"dialog": "hello",
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
"""
|
|
191
|
+
"""Operator data from operator panel."""
|
|
221
192
|
|
|
222
193
|
model_config = ConfigDict(extra="forbid")
|
|
223
194
|
|
|
@@ -225,191 +196,29 @@ class OperatorData(BaseModel):
|
|
|
225
196
|
|
|
226
197
|
|
|
227
198
|
class ResultStateStore(IBaseResult):
|
|
228
|
-
"""Test run description.
|
|
229
|
-
|
|
230
|
-
Example:
|
|
231
|
-
```
|
|
232
|
-
{
|
|
233
|
-
"_rev": "44867-3888ae85c19c428cc46685845953b483",
|
|
234
|
-
"_id": "current",
|
|
235
|
-
"progress": 100,
|
|
236
|
-
"stop_time": 1695817266,
|
|
237
|
-
"start_time": 1695817263,
|
|
238
|
-
"status": "failed",
|
|
239
|
-
"name": "hardpy-stand",
|
|
240
|
-
"alert": "",
|
|
241
|
-
"operator_data": {
|
|
242
|
-
"dialog": ""
|
|
243
|
-
},
|
|
244
|
-
"dut": {
|
|
245
|
-
"serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
|
|
246
|
-
"part_number": "part_1",
|
|
247
|
-
"info": {
|
|
248
|
-
"batch": "test_batch",
|
|
249
|
-
"board_rev": "rev_1"
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
"test_stand": {
|
|
253
|
-
"hw_id": "840982098ca2459a7b22cc608eff65d4",
|
|
254
|
-
"name": "test_stand_1",
|
|
255
|
-
"info": {
|
|
256
|
-
"geo": "Belgrade"
|
|
257
|
-
},
|
|
258
|
-
"timezone": "Europe/Belgrade",
|
|
259
|
-
"drivers": {
|
|
260
|
-
"driver_1": "driver info",
|
|
261
|
-
"driver_2": {
|
|
262
|
-
"state": "active",
|
|
263
|
-
"port": 8000
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
"location": "Belgrade_1",
|
|
267
|
-
"number": 2
|
|
268
|
-
},
|
|
269
|
-
"operator_msg": {
|
|
270
|
-
"msg": "Operator message",
|
|
271
|
-
"title": "Message",
|
|
272
|
-
"visible": true,
|
|
273
|
-
"id": "f45ac1e7-2ce8-4a6b-bb9d-8863e30bcc78"
|
|
274
|
-
},
|
|
275
|
-
"modules": {
|
|
276
|
-
"test_1_a": {
|
|
277
|
-
"status": "failed",
|
|
278
|
-
"name": "Module 1",
|
|
279
|
-
"start_time": 1695816884,
|
|
280
|
-
"stop_time": 1695817265,
|
|
281
|
-
"cases": {
|
|
282
|
-
"test_dut_info": {
|
|
283
|
-
"status": "passed",
|
|
284
|
-
"name": "DUT info ",
|
|
285
|
-
"start_time": 1695817263,
|
|
286
|
-
"stop_time": 1695817264,
|
|
287
|
-
"assertion_msg": null,
|
|
288
|
-
"msg": null,
|
|
289
|
-
"attempt": 1,
|
|
290
|
-
"dialog_box": {
|
|
291
|
-
"title_bar": "Example of text input",
|
|
292
|
-
"dialog_text": "Type some text and press the Confirm button",
|
|
293
|
-
"widget": {
|
|
294
|
-
"info": {
|
|
295
|
-
"text": "some text"
|
|
296
|
-
},
|
|
297
|
-
"type": "textinput"
|
|
298
|
-
},
|
|
299
|
-
visible: true,
|
|
300
|
-
id: "f45bc1e7-2c18-4a4b-2b9d-8863e30bcc78",
|
|
301
|
-
font_size: 14
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
"test_minute_parity": {
|
|
305
|
-
"status": "failed",
|
|
306
|
-
"name": "Test 1",
|
|
307
|
-
"start_time": 1695817264,
|
|
308
|
-
"stop_time": 1695817264,
|
|
309
|
-
"assertion_msg": "The test failed because minute 21 is odd! Try again!",
|
|
310
|
-
"attempt": 1,
|
|
311
|
-
"msg": [
|
|
312
|
-
"Current minute 21"
|
|
313
|
-
]
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
"""
|
|
199
|
+
"""Test run description."""
|
|
321
200
|
|
|
322
201
|
model_config = ConfigDict(extra="forbid")
|
|
323
202
|
|
|
324
203
|
rev: str = Field(..., alias="_rev")
|
|
325
204
|
id: str = Field(..., alias="_id")
|
|
205
|
+
|
|
326
206
|
progress: int
|
|
327
207
|
test_stand: TestStand
|
|
328
208
|
dut: Dut
|
|
209
|
+
process: Process
|
|
329
210
|
modules: dict[str, ModuleStateStore] = {}
|
|
211
|
+
user: str | None = None
|
|
212
|
+
batch_serial_number: str | None = None
|
|
213
|
+
caused_dut_failure_id: str | None = None
|
|
214
|
+
error_code: int | None = None
|
|
330
215
|
operator_msg: dict = {}
|
|
331
216
|
alert: str
|
|
332
217
|
operator_data: OperatorData
|
|
333
218
|
|
|
334
219
|
|
|
335
220
|
class ResultRunStore(IBaseResult):
|
|
336
|
-
"""Test run description.
|
|
337
|
-
|
|
338
|
-
Example:
|
|
339
|
-
```
|
|
340
|
-
{
|
|
341
|
-
"_rev": "44867-3888ae85c19c428cc46685845953b483",
|
|
342
|
-
"_id": "current",
|
|
343
|
-
"stop_time": 1695817266,
|
|
344
|
-
"start_time": 1695817263,
|
|
345
|
-
"status": "failed",
|
|
346
|
-
"name": "hardpy-stand",
|
|
347
|
-
"dut": {
|
|
348
|
-
"serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
|
|
349
|
-
"part_number": "part_1",
|
|
350
|
-
"info": {
|
|
351
|
-
"batch": "test_batch",
|
|
352
|
-
"board_rev": "rev_1"
|
|
353
|
-
}
|
|
354
|
-
},
|
|
355
|
-
"test_stand": {
|
|
356
|
-
"hw_id": "840982098ca2459a7b22cc608eff65d4",
|
|
357
|
-
"name": "test_stand_1",
|
|
358
|
-
"info": {
|
|
359
|
-
"geo": "Belgrade"
|
|
360
|
-
},
|
|
361
|
-
"timezone": "Europe/Belgrade",
|
|
362
|
-
"drivers": {
|
|
363
|
-
"driver_1": "driver info",
|
|
364
|
-
"driver_2": {
|
|
365
|
-
"state": "active",
|
|
366
|
-
"port": 8000
|
|
367
|
-
}
|
|
368
|
-
},
|
|
369
|
-
"location": "Belgrade_1",
|
|
370
|
-
"number": 2
|
|
371
|
-
},
|
|
372
|
-
"artifact": {},
|
|
373
|
-
"modules": {
|
|
374
|
-
"test_1_a": {
|
|
375
|
-
"status": "failed",
|
|
376
|
-
"name": "Module 1",
|
|
377
|
-
"start_time": 1695816884,
|
|
378
|
-
"stop_time": 1695817265,
|
|
379
|
-
"artifact": {},
|
|
380
|
-
"cases": {
|
|
381
|
-
"test_dut_info": {
|
|
382
|
-
"status": "passed",
|
|
383
|
-
"name": "DUT info",
|
|
384
|
-
"start_time": 1695817263,
|
|
385
|
-
"stop_time": 1695817264,
|
|
386
|
-
"assertion_msg": null,
|
|
387
|
-
"msg": null,
|
|
388
|
-
"artifact": {}
|
|
389
|
-
},
|
|
390
|
-
"test_minute_parity": {
|
|
391
|
-
"status": "failed",
|
|
392
|
-
"name": "Test 1",
|
|
393
|
-
"start_time": 1695817264,
|
|
394
|
-
"stop_time": 1695817264,
|
|
395
|
-
"assertion_msg": "The test failed because minute 21 is odd! Try again!",
|
|
396
|
-
"msg": [
|
|
397
|
-
"Current minute 21"
|
|
398
|
-
],
|
|
399
|
-
"artifact": {
|
|
400
|
-
"data_str": "123DATA",
|
|
401
|
-
"data_int": 12345,
|
|
402
|
-
"data_dict": {
|
|
403
|
-
"test_key": "456DATA"
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
```
|
|
412
|
-
"""
|
|
221
|
+
"""Test run description."""
|
|
413
222
|
|
|
414
223
|
model_config = ConfigDict(extra="forbid")
|
|
415
224
|
# Create the new schema class with version update
|
|
@@ -421,5 +230,10 @@ class ResultRunStore(IBaseResult):
|
|
|
421
230
|
|
|
422
231
|
test_stand: TestStand
|
|
423
232
|
dut: Dut
|
|
233
|
+
process: Process
|
|
424
234
|
modules: dict[str, ModuleRunStore] = {}
|
|
235
|
+
user: str | None = None
|
|
236
|
+
batch_serial_number: str | None = None
|
|
237
|
+
caused_dut_failure_id: str | None = None
|
|
238
|
+
error_code: int | None = None
|
|
425
239
|
artifact: dict = {}
|
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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 copy
|
|
5
6
|
import signal
|
|
6
7
|
from logging import getLogger
|
|
7
8
|
from pathlib import Path, PurePath
|
|
@@ -98,6 +99,12 @@ def pytest_addoption(parser: Parser) -> None:
|
|
|
98
99
|
default=con_data.sc_connection_only,
|
|
99
100
|
help="check StandCloud availability",
|
|
100
101
|
)
|
|
102
|
+
parser.addoption(
|
|
103
|
+
"--hardpy-start-arg",
|
|
104
|
+
action="append",
|
|
105
|
+
default=[],
|
|
106
|
+
help="Dynamic arguments for test execution (key=value format)",
|
|
107
|
+
)
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
# Bootstrapping hooks
|
|
@@ -125,6 +132,7 @@ class HardpyPlugin:
|
|
|
125
132
|
self._dependencies = {}
|
|
126
133
|
self._tests_name: str = ""
|
|
127
134
|
self._is_critical_not_passed = False
|
|
135
|
+
self._start_args = {}
|
|
128
136
|
|
|
129
137
|
if system() == "Linux":
|
|
130
138
|
signal.signal(signal.SIGTERM, self._stop_handler)
|
|
@@ -158,11 +166,17 @@ class HardpyPlugin:
|
|
|
158
166
|
if sc_connection_only:
|
|
159
167
|
con_data.sc_connection_only = bool(sc_connection_only) # type: ignore
|
|
160
168
|
|
|
169
|
+
_args = config.getoption("--hardpy-start-arg") or []
|
|
170
|
+
if _args:
|
|
171
|
+
self._start_args = dict(arg.split("=", 1) for arg in _args if "=" in arg)
|
|
172
|
+
|
|
161
173
|
config.addinivalue_line("markers", "case_name")
|
|
162
174
|
config.addinivalue_line("markers", "module_name")
|
|
163
175
|
config.addinivalue_line("markers", "dependency")
|
|
164
176
|
config.addinivalue_line("markers", "attempt")
|
|
165
177
|
config.addinivalue_line("markers", "critical")
|
|
178
|
+
config.addinivalue_line("markers", "case_group")
|
|
179
|
+
config.addinivalue_line("markers", "module_group")
|
|
166
180
|
|
|
167
181
|
# must be init after config data is set
|
|
168
182
|
try:
|
|
@@ -301,10 +315,13 @@ class HardpyPlugin:
|
|
|
301
315
|
if call.when != "call" or not call.excinfo:
|
|
302
316
|
return
|
|
303
317
|
|
|
318
|
+
# failure item
|
|
304
319
|
node_info = NodeInfo(item)
|
|
305
320
|
attempt = node_info.attempt
|
|
306
321
|
module_id = node_info.module_id
|
|
307
322
|
case_id = node_info.case_id
|
|
323
|
+
casusd_dut_failure_id = self._reporter.get_caused_dut_failure_id()
|
|
324
|
+
is_dut_failure = True
|
|
308
325
|
|
|
309
326
|
if node_info.critical:
|
|
310
327
|
self._is_critical_not_passed = True
|
|
@@ -323,12 +340,17 @@ class HardpyPlugin:
|
|
|
323
340
|
item.runtest()
|
|
324
341
|
call.excinfo = None
|
|
325
342
|
self._is_critical_not_passed = False
|
|
343
|
+
is_dut_failure = False
|
|
326
344
|
self._reporter.set_case_status(module_id, case_id, TestStatus.PASSED)
|
|
327
345
|
break
|
|
328
346
|
except AssertionError:
|
|
329
347
|
self._reporter.set_case_status(module_id, case_id, TestStatus.FAILED)
|
|
348
|
+
is_dut_failure = True
|
|
330
349
|
if current_attempt == attempt:
|
|
331
|
-
|
|
350
|
+
break
|
|
351
|
+
|
|
352
|
+
if is_dut_failure and casusd_dut_failure_id is None:
|
|
353
|
+
self._reporter.set_caused_dut_failure_id(module_id, case_id)
|
|
332
354
|
|
|
333
355
|
# Reporting hooks
|
|
334
356
|
|
|
@@ -375,6 +397,15 @@ class HardpyPlugin:
|
|
|
375
397
|
"""
|
|
376
398
|
return self._post_run_functions
|
|
377
399
|
|
|
400
|
+
@fixture(scope="session")
|
|
401
|
+
def hardpy_start_args(self) -> dict:
|
|
402
|
+
"""Get HardPy start arguments.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
dict: Parsed start arguments (key-value pairs)
|
|
406
|
+
"""
|
|
407
|
+
return copy.deepcopy(self._start_args)
|
|
408
|
+
|
|
378
409
|
# Not hooks
|
|
379
410
|
|
|
380
411
|
def _stop_handler(self, signum: int, frame: Any) -> None: # noqa: ANN401, ARG002
|