hardpy 0.14.0__py3-none-any.whl → 0.15.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 +45 -10
- hardpy/cli/cli.py +30 -28
- hardpy/common/config.py +0 -4
- hardpy/common/stand_cloud/connector.py +2 -2
- hardpy/hardpy_panel/api.py +13 -3
- hardpy/pytest_hardpy/db/base_store.py +23 -0
- hardpy/pytest_hardpy/db/const.py +40 -19
- hardpy/pytest_hardpy/db/schema/v1.py +139 -326
- hardpy/pytest_hardpy/plugin.py +32 -1
- hardpy/pytest_hardpy/pytest_call.py +329 -21
- hardpy/pytest_hardpy/pytest_wrapper.py +25 -34
- hardpy/pytest_hardpy/reporter/hook_reporter.py +53 -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.14.0.dist-info → hardpy-0.15.1.dist-info}/METADATA +2 -1
- {hardpy-0.14.0.dist-info → hardpy-0.15.1.dist-info}/RECORD +23 -22
- {hardpy-0.14.0.dist-info → hardpy-0.15.1.dist-info}/WHEEL +0 -0
- {hardpy-0.14.0.dist-info → hardpy-0.15.1.dist-info}/entry_points.txt +0 -0
- {hardpy-0.14.0.dist-info → hardpy-0.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,11 +2,19 @@
|
|
|
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
|
|
5
7
|
from typing import ClassVar
|
|
6
8
|
|
|
7
9
|
from pydantic import BaseModel, ConfigDict, Field
|
|
8
10
|
|
|
9
|
-
from hardpy.pytest_hardpy.utils import
|
|
11
|
+
from hardpy.pytest_hardpy.utils import (
|
|
12
|
+
ChartType,
|
|
13
|
+
ComparisonOperation as CompOp,
|
|
14
|
+
Group,
|
|
15
|
+
MeasurementType,
|
|
16
|
+
TestStatus as Status,
|
|
17
|
+
)
|
|
10
18
|
|
|
11
19
|
|
|
12
20
|
class IBaseResult(BaseModel):
|
|
@@ -21,203 +29,165 @@ class IBaseResult(BaseModel):
|
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
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
|
-
"""
|
|
32
|
+
"""Test case description."""
|
|
54
33
|
|
|
55
34
|
assertion_msg: str | None = None
|
|
56
35
|
msg: dict | None = None
|
|
57
|
-
|
|
36
|
+
measurements: list[NumericMeasurement | StringMeasurement] = []
|
|
37
|
+
chart: Chart | None = None
|
|
58
38
|
attempt: int = 0
|
|
39
|
+
group: Group
|
|
40
|
+
dialog_box: dict = {}
|
|
59
41
|
|
|
60
42
|
|
|
61
43
|
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
|
-
"""
|
|
44
|
+
"""Test case description with artifact."""
|
|
79
45
|
|
|
80
46
|
assertion_msg: str | None = None
|
|
81
47
|
msg: dict | None = None
|
|
48
|
+
measurements: list[NumericMeasurement | StringMeasurement] = []
|
|
49
|
+
chart: Chart | None = None
|
|
50
|
+
attempt: int = 0
|
|
51
|
+
group: Group
|
|
82
52
|
artifact: dict = {}
|
|
83
53
|
|
|
84
54
|
|
|
85
55
|
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
|
-
"""
|
|
56
|
+
"""Test module description."""
|
|
110
57
|
|
|
111
58
|
cases: dict[str, CaseStateStore] = {}
|
|
59
|
+
group: Group
|
|
112
60
|
|
|
113
61
|
|
|
114
62
|
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
|
-
"""
|
|
63
|
+
"""Test module description."""
|
|
141
64
|
|
|
142
65
|
cases: dict[str, CaseRunStore] = {}
|
|
66
|
+
group: Group
|
|
143
67
|
artifact: dict = {}
|
|
144
68
|
|
|
145
69
|
|
|
146
70
|
class Dut(BaseModel):
|
|
147
|
-
"""Device under test description.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
71
|
+
"""Device under test description."""
|
|
72
|
+
|
|
73
|
+
model_config = ConfigDict(extra="forbid")
|
|
74
|
+
|
|
75
|
+
name: str | None = None
|
|
76
|
+
type: str | None = None
|
|
77
|
+
serial_number: str | None = None
|
|
78
|
+
part_number: str | None = None
|
|
79
|
+
revision: str | None = None
|
|
80
|
+
sub_units: list[SubUnit] = []
|
|
81
|
+
info: Mapping[str, str | int | float] = {}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SubUnit(BaseModel):
|
|
85
|
+
"""Sub unit of DUT description."""
|
|
86
|
+
|
|
87
|
+
model_config = ConfigDict(extra="forbid")
|
|
88
|
+
|
|
89
|
+
name: str | None = None
|
|
90
|
+
type: str | None = None
|
|
91
|
+
serial_number: str | None = None
|
|
92
|
+
part_number: str | None = None
|
|
93
|
+
revision: str | None = None
|
|
94
|
+
info: Mapping[str, str | int | float] = {}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Instrument(BaseModel):
|
|
98
|
+
"""Instrument (power supply, oscilloscope and others) description."""
|
|
163
99
|
|
|
164
100
|
model_config = ConfigDict(extra="forbid")
|
|
165
101
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
102
|
+
name: str | None = None
|
|
103
|
+
revision: str | None = None
|
|
104
|
+
number: int | None = None
|
|
105
|
+
comment: str | None = None
|
|
106
|
+
info: Mapping[str, str | int | float] = {}
|
|
169
107
|
|
|
170
108
|
|
|
171
109
|
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
|
-
"""
|
|
110
|
+
"""Test stand description."""
|
|
197
111
|
|
|
198
112
|
model_config = ConfigDict(extra="forbid")
|
|
199
113
|
|
|
200
114
|
hw_id: str | None = None
|
|
201
115
|
name: str | None = None
|
|
116
|
+
revision: str | None = None
|
|
202
117
|
timezone: str | None = None
|
|
203
|
-
drivers: dict = {}
|
|
204
|
-
info: dict = {}
|
|
205
118
|
location: str | None = None
|
|
206
119
|
number: int | None = None
|
|
120
|
+
drivers: dict = {} # deprecated, remove in v2
|
|
121
|
+
instruments: list[Instrument] = []
|
|
122
|
+
info: Mapping[str, str | int | float] = {}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Process(BaseModel):
|
|
126
|
+
"""Production process description."""
|
|
127
|
+
|
|
128
|
+
model_config = ConfigDict(extra="forbid")
|
|
129
|
+
|
|
130
|
+
name: str | None = None
|
|
131
|
+
number: int | None = None
|
|
132
|
+
info: Mapping[str, str | int | float] = {}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class IBaseMeasurement(BaseModel, ABC):
|
|
136
|
+
"""Base class for all measurement models."""
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(extra="allow")
|
|
139
|
+
|
|
140
|
+
type: MeasurementType
|
|
141
|
+
name: str | None = Field(default=None)
|
|
142
|
+
operation: CompOp | None = Field(default=None)
|
|
143
|
+
result: bool | None = Field(default_factory=lambda: None)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NumericMeasurement(IBaseMeasurement):
|
|
147
|
+
"""Numeric measurement description."""
|
|
148
|
+
|
|
149
|
+
model_config = ConfigDict(extra="forbid")
|
|
150
|
+
|
|
151
|
+
type: MeasurementType = Field(default=MeasurementType.NUMERIC)
|
|
152
|
+
value: int | float
|
|
153
|
+
name: str | None = Field(default=None)
|
|
154
|
+
unit: str | None = Field(default=None)
|
|
155
|
+
|
|
156
|
+
comparison_value: float | int | None = Field(default=None)
|
|
157
|
+
|
|
158
|
+
lower_limit: float | int | None = Field(default=None)
|
|
159
|
+
upper_limit: float | int | None = Field(default=None)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class StringMeasurement(IBaseMeasurement):
|
|
163
|
+
"""String measurement description."""
|
|
164
|
+
|
|
165
|
+
model_config = ConfigDict(extra="forbid")
|
|
166
|
+
|
|
167
|
+
type: MeasurementType = Field(default=MeasurementType.STRING)
|
|
168
|
+
value: str
|
|
169
|
+
name: str | None = Field(default=None)
|
|
170
|
+
casesensitive: bool = Field(default=True)
|
|
171
|
+
|
|
172
|
+
comparison_value: str | None = Field(default=None)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Chart(BaseModel):
|
|
176
|
+
"""Chart description."""
|
|
177
|
+
|
|
178
|
+
model_config = ConfigDict(extra="forbid")
|
|
179
|
+
|
|
180
|
+
type: ChartType = Field(default=ChartType.LINE)
|
|
181
|
+
title: str | None = Field(default=None)
|
|
182
|
+
x_label: str | None = Field(default=None)
|
|
183
|
+
y_label: str | None = Field(default=None)
|
|
184
|
+
marker_name: list[str | None] = Field(default=[])
|
|
185
|
+
x_data: list[list[int | float]] = Field(default_factory=lambda: []) # noqa: PIE807
|
|
186
|
+
y_data: list[list[int | float]] = Field(default_factory=lambda: []) # noqa: PIE807
|
|
207
187
|
|
|
208
188
|
|
|
209
189
|
class OperatorData(BaseModel):
|
|
210
|
-
"""Operator data from operator panel.
|
|
211
|
-
|
|
212
|
-
Example:
|
|
213
|
-
```
|
|
214
|
-
{
|
|
215
|
-
"operator_data": {
|
|
216
|
-
"dialog": "hello",
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
"""
|
|
190
|
+
"""Operator data from operator panel."""
|
|
221
191
|
|
|
222
192
|
model_config = ConfigDict(extra="forbid")
|
|
223
193
|
|
|
@@ -225,191 +195,29 @@ class OperatorData(BaseModel):
|
|
|
225
195
|
|
|
226
196
|
|
|
227
197
|
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
|
-
"""
|
|
198
|
+
"""Test run description."""
|
|
321
199
|
|
|
322
200
|
model_config = ConfigDict(extra="forbid")
|
|
323
201
|
|
|
324
202
|
rev: str = Field(..., alias="_rev")
|
|
325
203
|
id: str = Field(..., alias="_id")
|
|
204
|
+
|
|
326
205
|
progress: int
|
|
327
206
|
test_stand: TestStand
|
|
328
207
|
dut: Dut
|
|
208
|
+
process: Process
|
|
329
209
|
modules: dict[str, ModuleStateStore] = {}
|
|
210
|
+
user: str | None = None
|
|
211
|
+
batch_serial_number: str | None = None
|
|
212
|
+
caused_dut_failure_id: str | None = None
|
|
213
|
+
error_code: int | None = None
|
|
330
214
|
operator_msg: dict = {}
|
|
331
215
|
alert: str
|
|
332
216
|
operator_data: OperatorData
|
|
333
217
|
|
|
334
218
|
|
|
335
219
|
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
|
-
"""
|
|
220
|
+
"""Test run description."""
|
|
413
221
|
|
|
414
222
|
model_config = ConfigDict(extra="forbid")
|
|
415
223
|
# Create the new schema class with version update
|
|
@@ -421,5 +229,10 @@ class ResultRunStore(IBaseResult):
|
|
|
421
229
|
|
|
422
230
|
test_stand: TestStand
|
|
423
231
|
dut: Dut
|
|
232
|
+
process: Process
|
|
424
233
|
modules: dict[str, ModuleRunStore] = {}
|
|
234
|
+
user: str | None = None
|
|
235
|
+
batch_serial_number: str | None = None
|
|
236
|
+
caused_dut_failure_id: str | None = None
|
|
237
|
+
error_code: int | None = None
|
|
425
238
|
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
|