hardpy 0.1.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.
Files changed (71) hide show
  1. hardpy/__init__.py +34 -0
  2. hardpy/hardpy_panel/__init__.py +0 -0
  3. hardpy/hardpy_panel/api.py +71 -0
  4. hardpy/hardpy_panel/frontend/dist/asset-manifest.json +36 -0
  5. hardpy/hardpy_panel/frontend/dist/favicon.ico +0 -0
  6. hardpy/hardpy_panel/frontend/dist/index.html +1 -0
  7. hardpy/hardpy_panel/frontend/dist/logo512.png +0 -0
  8. hardpy/hardpy_panel/frontend/dist/manifest.json +25 -0
  9. hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css +2 -0
  10. hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css.map +1 -0
  11. hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js +2 -0
  12. hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js.map +1 -0
  13. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js +2 -0
  14. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js.map +1 -0
  15. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js +2 -0
  16. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js.map +1 -0
  17. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js +2 -0
  18. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map +1 -0
  19. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js +2 -0
  20. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js.map +1 -0
  21. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js +2 -0
  22. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map +1 -0
  23. hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js +3 -0
  24. hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js.LICENSE.txt +90 -0
  25. hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js.map +1 -0
  26. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.520846c6beb41df528c8.eot +0 -0
  27. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.5c52b39c697f2323ce8b.svg +1806 -0
  28. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.84db1772f4bfb529f64f.woff +0 -0
  29. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.b67ee1736e20e37a3225.woff2 +0 -0
  30. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.e02ecf515378db143652.ttf +0 -0
  31. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.429cacb8accf72488451.ttf +0 -0
  32. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.6ae3791ee2d86fc228a6.svg +1806 -0
  33. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.8cecf62de42997e4d82f.woff2 +0 -0
  34. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.afbadb627d43b7857223.eot +0 -0
  35. hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff +0 -0
  36. hardpy/hardpy_panel/frontend/dist/static/media/logo_smol.5b16f92447a4a9e80331.png +0 -0
  37. hardpy/hardpy_panel/runner.py +52 -0
  38. hardpy/pytest_hardpy/__init__.py +0 -0
  39. hardpy/pytest_hardpy/db/__init__.py +18 -0
  40. hardpy/pytest_hardpy/db/base_connector.py +24 -0
  41. hardpy/pytest_hardpy/db/base_server.py +14 -0
  42. hardpy/pytest_hardpy/db/base_store.py +88 -0
  43. hardpy/pytest_hardpy/db/const.py +25 -0
  44. hardpy/pytest_hardpy/db/runstore.py +30 -0
  45. hardpy/pytest_hardpy/db/schema.py +292 -0
  46. hardpy/pytest_hardpy/db/statestore.py +19 -0
  47. hardpy/pytest_hardpy/plugin.py +244 -0
  48. hardpy/pytest_hardpy/pytest_call.py +218 -0
  49. hardpy/pytest_hardpy/pytest_wrapper.py +117 -0
  50. hardpy/pytest_hardpy/reporter/__init__.py +10 -0
  51. hardpy/pytest_hardpy/reporter/base.py +42 -0
  52. hardpy/pytest_hardpy/reporter/hook_reporter.py +307 -0
  53. hardpy/pytest_hardpy/reporter/runner_reporter.py +29 -0
  54. hardpy/pytest_hardpy/result/__init__.py +10 -0
  55. hardpy/pytest_hardpy/result/couchdb_config.py +22 -0
  56. hardpy/pytest_hardpy/result/report_loader/__init__.py +10 -0
  57. hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +62 -0
  58. hardpy/pytest_hardpy/result/report_reader/__init__.py +0 -0
  59. hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +164 -0
  60. hardpy/pytest_hardpy/utils/__init__.py +19 -0
  61. hardpy/pytest_hardpy/utils/config_data.py +31 -0
  62. hardpy/pytest_hardpy/utils/const.py +29 -0
  63. hardpy/pytest_hardpy/utils/exception.py +16 -0
  64. hardpy/pytest_hardpy/utils/node_info.py +59 -0
  65. hardpy/pytest_hardpy/utils/progress_calculator.py +38 -0
  66. hardpy/pytest_hardpy/utils/singleton.py +23 -0
  67. hardpy-0.1.0.dist-info/METADATA +129 -0
  68. hardpy-0.1.0.dist-info/RECORD +71 -0
  69. hardpy-0.1.0.dist-info/WHEEL +4 -0
  70. hardpy-0.1.0.dist-info/entry_points.txt +5 -0
  71. hardpy-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ import sys
5
+ from argparse import ArgumentParser
6
+ from pathlib import Path
7
+
8
+ from uvicorn import run as uvicorn_run
9
+
10
+ from hardpy.pytest_hardpy.utils import ConfigData
11
+
12
+
13
+ def run():
14
+ """Start server for frontend."""
15
+ config = ConfigData()
16
+ parser = ArgumentParser(description="Usage: hardpy-panel [OPTION]... [PATH]")
17
+ # fmt: off
18
+ parser.add_argument("-dbu", "--db_user", default=config.db_user, help="database user")
19
+ parser.add_argument("-dbpw", "--db_pswd", default=config.db_pswd, help="database user password") # noqa: E501
20
+ parser.add_argument("-dbp", "--db_port", type=int, default=config.db_port, help="database port number") # noqa: E501
21
+ parser.add_argument("-dbh", "--db_host", type=str, default=config.db_host, help="database hostname") # noqa: E501
22
+ parser.add_argument("-wh", "--web_host", type=str, default=config.web_host, help="web operator panel hostname") # noqa: E501
23
+ parser.add_argument("-wp", "--web_port", type=str, default=config.web_port, help="web operator panel port") # noqa: E501
24
+ parser.add_argument("path", type=str, nargs='?', help="path to test directory")
25
+ # fmt: on
26
+
27
+ args = parser.parse_args()
28
+
29
+ config.db_user = args.db_user
30
+ config.db_pswd = args.db_pswd
31
+ config.db_port = args.db_port
32
+ config.db_host = args.db_host
33
+ config.web_host = args.web_host
34
+ config.web_port = args.web_port
35
+
36
+ path = Path(args.path) if args.path else Path.cwd()
37
+
38
+ config.tests_dir = path
39
+
40
+ if not config.tests_dir.exists():
41
+ print(f"Directory not found: {path}")
42
+ sys.exit()
43
+
44
+ uvicorn_run(
45
+ "hardpy.hardpy_panel.api:app",
46
+ host=config.web_host,
47
+ port=config.web_port,
48
+ log_level="critical",
49
+ )
50
+
51
+
52
+ run()
File without changes
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from hardpy.pytest_hardpy.db.schema import ResultRunStore, ResultStateStore
5
+ from hardpy.pytest_hardpy.db.statestore import StateStore
6
+ from hardpy.pytest_hardpy.db.runstore import RunStore
7
+ from hardpy.pytest_hardpy.db.base_store import BaseStore
8
+ from hardpy.pytest_hardpy.db.const import DatabaseField
9
+
10
+
11
+ __all__ = [
12
+ "BaseStore",
13
+ "DatabaseField",
14
+ "ResultRunStore",
15
+ "ResultStateStore",
16
+ "StateStore",
17
+ "RunStore",
18
+ ]
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from pycouchdb.client import Database
5
+ from pycouchdb.exceptions import Conflict
6
+
7
+ from hardpy.pytest_hardpy.db.base_server import BaseServer
8
+
9
+
10
+ class BaseConnector(BaseServer):
11
+ """Base class for CouchDB connector."""
12
+
13
+ def __init__(self, db_name: str):
14
+ super().__init__()
15
+ self._db_name = db_name
16
+ self._db = self._init_db()
17
+ self._doc_id = "current"
18
+
19
+ def _init_db(self) -> Database:
20
+ try:
21
+ return self._db_srv.create(self._db_name) # type: ignore
22
+ except Conflict:
23
+ # database is already created
24
+ return self._db_srv.database(self._db_name)
@@ -0,0 +1,14 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from pycouchdb import Server as DbServer
5
+
6
+ from hardpy.pytest_hardpy.utils import ConfigData
7
+
8
+
9
+ class BaseServer(object):
10
+ """Base class for CouchDB server."""
11
+
12
+ def __init__(self):
13
+ config = ConfigData()
14
+ self._db_srv = DbServer(config.connection_string)
@@ -0,0 +1,88 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from logging import getLogger
5
+ from typing import Any
6
+
7
+ from glom import assign, glom
8
+ from pycouchdb.exceptions import Conflict, NotFound
9
+ from pydantic._internal._model_construction import ModelMetaclass
10
+
11
+ from hardpy.pytest_hardpy.db.base_connector import BaseConnector
12
+ from hardpy.pytest_hardpy.db.const import DatabaseField as DF
13
+
14
+
15
+ class BaseStore(BaseConnector):
16
+ """HardPy base storage interface for CouchDB."""
17
+
18
+ def __init__(self, db_name: str):
19
+ super().__init__(db_name)
20
+ self._log = getLogger(__name__)
21
+ self._doc: dict = self._init_doc()
22
+ self._schema: ModelMetaclass
23
+
24
+ def compact(self):
25
+ """Compact database."""
26
+ self._db.compact()
27
+
28
+ def get_field(self, key: str) -> Any:
29
+ """Get field from the state store.
30
+
31
+ Args:
32
+ key (str): field name
33
+
34
+ Returns:
35
+ Any: field value
36
+ """
37
+ return glom(self._doc, key)
38
+
39
+ def set_value(self, key: str, value):
40
+ """Set a value in the state store."""
41
+ assign(self._doc, key, value)
42
+ try:
43
+ self._doc = self._db.save(self._doc)
44
+ except Conflict as exc:
45
+ self._log.error(
46
+ f"Error while saving runner document: {exc} "
47
+ f"when trying to save key={key}, value={value}. "
48
+ "Current document will be changed by "
49
+ "document from database."
50
+ )
51
+ self._doc = self._db.get(self._doc_id)
52
+
53
+ def get_document(self) -> ModelMetaclass:
54
+ """Get document by schema.
55
+
56
+ Returns:
57
+ ModelMetaclass: document by schema
58
+ """
59
+ self._doc = self._db.get(self._doc_id)
60
+ return self._schema(**self._doc)
61
+
62
+ def _init_doc(self) -> dict:
63
+ try:
64
+ doc = self._db.get(self._doc_id)
65
+ except NotFound:
66
+ return {
67
+ "_id": self._doc_id,
68
+ DF.MODULES: {},
69
+ DF.DUT: {
70
+ DF.SERIAL_NUMBER: None,
71
+ DF.INFO: {},
72
+ },
73
+ DF.TEST_STAND: {},
74
+ DF.DRIVERS: {},
75
+ }
76
+
77
+ if DF.MODULES not in doc:
78
+ doc[DF.MODULES] = {}
79
+
80
+ for item in (DF.TEST_STAND, DF.DRIVERS):
81
+ doc[item] = {}
82
+
83
+ doc[DF.DUT] = {
84
+ DF.SERIAL_NUMBER: None,
85
+ DF.INFO: {},
86
+ }
87
+
88
+ return doc
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from enum import Enum
5
+
6
+
7
+ class DatabaseField(str, Enum): # noqa: WPS600
8
+ """Database field."""
9
+
10
+ NAME = "name"
11
+ STATUS = "status"
12
+ START_TIME = "start_time"
13
+ STOP_TIME = "stop_time"
14
+ ASSERTION_MSG = "assertion_msg"
15
+ MSG = "msg"
16
+ MODULES = "modules"
17
+ CASES = "cases"
18
+ TIMEZONE = "timezone"
19
+ PROGRESS = "progress"
20
+ ARTIFACT = "artifact"
21
+ DUT = "dut"
22
+ INFO = "info"
23
+ TEST_STAND = "test_stand"
24
+ SERIAL_NUMBER = "serial_number"
25
+ DRIVERS = "drivers"
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from logging import getLogger
5
+
6
+ from pycouchdb.exceptions import Conflict, NotFound
7
+
8
+ from hardpy.pytest_hardpy.db.base_store import BaseStore
9
+ from hardpy.pytest_hardpy.db import ResultRunStore
10
+ from hardpy.pytest_hardpy.utils import Singleton
11
+
12
+
13
+ class RunStore(Singleton, BaseStore):
14
+ """HardPy run storage interface for CouchDB.
15
+
16
+ Save state and case artifact.
17
+ """
18
+
19
+ def __init__(self):
20
+ if not self._initialized:
21
+ super().__init__("runstore")
22
+ self._log = getLogger(__name__)
23
+ try:
24
+ # Clear the runstore database before each launch
25
+ self._db.delete(self._doc_id)
26
+ except (Conflict, NotFound):
27
+ self._log.debug("Runstore database will be created for the first time")
28
+ self._doc: dict = self._init_doc()
29
+ self._schema = ResultRunStore
30
+ self._initialized = True
@@ -0,0 +1,292 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel, Field, ConfigDict
7
+
8
+ from hardpy.pytest_hardpy.utils import TestStatus as Status
9
+
10
+
11
+ class IBaseResult(BaseModel):
12
+ """Base class for all result models."""
13
+
14
+ model_config = ConfigDict(extra="forbid")
15
+
16
+ status: Status
17
+ stop_time: int | None
18
+ start_time: int | None
19
+ name: str
20
+
21
+
22
+ class CaseStateStore(IBaseResult):
23
+ """Test case description.
24
+
25
+ Example:
26
+ "test_one": {
27
+ "status": "passed",
28
+ "name": "Test 2",
29
+ "start_time": 1695817188,
30
+ "stop_time": 1695817189,
31
+ "assertion_msg": null,
32
+ "msg": null
33
+ }
34
+ """
35
+
36
+ assertion_msg: str | None = None
37
+ msg: dict | None = None
38
+
39
+
40
+ class CaseRunStore(IBaseResult):
41
+ """Test case description with artifact.
42
+
43
+ Example:
44
+ "test_one": {
45
+ "status": "passed",
46
+ "name": "Test 2",
47
+ "start_time": 1695817188,
48
+ "stop_time": 1695817189,
49
+ "assertion_msg": null,
50
+ "msg": null,
51
+ "artifact": {}
52
+ }
53
+ """
54
+
55
+ assertion_msg: str | None = None
56
+ msg: dict | None = None
57
+ artifact: Optional[dict] = {}
58
+
59
+
60
+ class ModuleStateStore(IBaseResult):
61
+ """Test module description.
62
+
63
+ Example:
64
+ "test_2_b": {
65
+ "status": "passed",
66
+ "name": "Module 2",
67
+ "start_time": 1695816886,
68
+ "stop_time": 1695817016,
69
+ "cases": {
70
+ "test_one": {
71
+ "status": "passed",
72
+ "name": "Test 1",
73
+ "start_time": 1695817015,
74
+ "stop_time": 1695817016,
75
+ "assertion_msg": null,
76
+ "msg": null
77
+ }
78
+ }
79
+ }
80
+ """
81
+
82
+ cases: dict[str, CaseStateStore] = {}
83
+
84
+
85
+ class ModuleRunStore(IBaseResult):
86
+ """Test module description.
87
+
88
+ Example:
89
+ "test_2_b": {
90
+ "status": "passed",
91
+ "name": "Module 2",
92
+ "start_time": 1695816886,
93
+ "stop_time": 1695817016,
94
+ "artifact": {},
95
+ "cases": {
96
+ "test_one": {
97
+ "status": "passed",
98
+ "name": "Test 1",
99
+ "start_time": 1695817015,
100
+ "stop_time": 1695817016,
101
+ "assertion_msg": null,
102
+ "msg": null,
103
+ "artifact": {}
104
+ }
105
+ }
106
+ }
107
+ """
108
+
109
+ cases: dict[str, CaseRunStore] = {}
110
+ artifact: Optional[dict] = {}
111
+
112
+
113
+ class Dut(BaseModel):
114
+ """Device under test description.
115
+
116
+ Example:
117
+ "dut": {
118
+ "serial_number": "a9ad8dca-2c64-4df8-a358-c21e832a32e4",
119
+ "info": {
120
+ "batch": "test_batch",
121
+ "board_rev": "rev_1"
122
+ }
123
+ },
124
+ """
125
+
126
+ model_config = ConfigDict(extra="forbid")
127
+
128
+ serial_number: str | None
129
+ info: dict = {}
130
+
131
+
132
+ class ResultStateStore(IBaseResult):
133
+ """Test run description.
134
+
135
+ Example:
136
+ {
137
+ "_rev": "44867-3888ae85c19c428cc46685845953b483",
138
+ "_id": "current",
139
+ "progress": 100,
140
+ "stop_time": 1695817266,
141
+ "timezone": [
142
+ "CET",
143
+ "CET"
144
+ ],
145
+ "start_time": 1695817263,
146
+ "status": "failed",
147
+ "name": "hardpy-stand",
148
+ "dut": {
149
+ "serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
150
+ "info": {
151
+ "batch": "test_batch",
152
+ "board_rev": "rev_1"
153
+ }
154
+ },
155
+ "test_stand": {
156
+ "name": "Test stand 1"
157
+ },
158
+ "drivers": {
159
+ "driver_1": "driver info",
160
+ "driver_2": {
161
+ "state": "active",
162
+ "port": 8000
163
+ }
164
+ },
165
+ "modules": {
166
+ "test_1_a": {
167
+ "status": "failed",
168
+ "name": "Module 1",
169
+ "start_time": 1695816884,
170
+ "stop_time": 1695817265,
171
+ "cases": {
172
+ "test_dut_info": {
173
+ "status": "passed",
174
+ "name": "DUT info ",
175
+ "start_time": 1695817263,
176
+ "stop_time": 1695817264,
177
+ "assertion_msg": null,
178
+ "msg": null
179
+ },
180
+ "test_minute_parity": {
181
+ "status": "failed",
182
+ "name": "Test 1",
183
+ "start_time": 1695817264,
184
+ "stop_time": 1695817264,
185
+ "assertion_msg": "The test failed because minute 21 is odd! Try again!",
186
+ "msg": [
187
+ "Current minute 21"
188
+ ]
189
+ },
190
+ }
191
+ },
192
+ }
193
+ }
194
+ """
195
+
196
+ model_config = ConfigDict(extra="forbid")
197
+
198
+ rev: str = Field(..., alias="_rev")
199
+ id: str = Field(..., alias="_id")
200
+ progress: int
201
+ timezone: tuple[str, str] | None = None
202
+ test_stand: dict = {}
203
+ dut: Dut
204
+ modules: dict[str, ModuleStateStore] = {}
205
+ drivers: Optional[dict] = {}
206
+
207
+
208
+ class ResultRunStore(IBaseResult):
209
+ """Test run description.
210
+
211
+ Example:
212
+ {
213
+ "_rev": "44867-3888ae85c19c428cc46685845953b483",
214
+ "_id": "current",
215
+ "progress": 100,
216
+ "stop_time": 1695817266,
217
+ "timezone": [
218
+ "CET",
219
+ "CET"
220
+ ],
221
+ "start_time": 1695817263,
222
+ "status": "failed",
223
+ "name": "hardpy-stand",
224
+ "dut": {
225
+ "serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
226
+ "info": {
227
+ "batch": "test_batch",
228
+ "board_rev": "rev_1"
229
+ }
230
+ },
231
+ "test_stand": {
232
+ "name": "Test stand 1"
233
+ },
234
+ "drivers": {
235
+ "driver_1": "driver info",
236
+ "driver_2": {
237
+ "state": "active",
238
+ "port": 8000
239
+ }
240
+ },
241
+ "artifact": {},
242
+ "modules": {
243
+ "test_1_a": {
244
+ "status": "failed",
245
+ "name": "Module 1",
246
+ "start_time": 1695816884,
247
+ "stop_time": 1695817265,
248
+ "artifact": {},
249
+ "cases": {
250
+ "test_dut_info": {
251
+ "status": "passed",
252
+ "name": "DUT info",
253
+ "start_time": 1695817263,
254
+ "stop_time": 1695817264,
255
+ "assertion_msg": null,
256
+ "msg": null,
257
+ "artifact": {}
258
+ },
259
+ "test_minute_parity": {
260
+ "status": "failed",
261
+ "name": "Test 1",
262
+ "start_time": 1695817264,
263
+ "stop_time": 1695817264,
264
+ "assertion_msg": "The test failed because minute 21 is odd! Try again!",
265
+ "msg": [
266
+ "Current minute 21"
267
+ ],
268
+ "artifact": {
269
+ "data_str": "123DATA",
270
+ "data_int": 12345,
271
+ "data_dict": {
272
+ "test_key": "456DATA"
273
+ }
274
+ }
275
+ },
276
+ }
277
+ },
278
+ }
279
+ }
280
+ """
281
+
282
+ model_config = ConfigDict(extra="forbid")
283
+
284
+ rev: str = Field(..., alias="_rev")
285
+ id: str = Field(..., alias="_id")
286
+ progress: int
287
+ timezone: tuple[str, str] | None = None
288
+ test_stand: dict = {}
289
+ dut: Dut
290
+ modules: dict[str, ModuleRunStore] = {}
291
+ drivers: Optional[dict] = {}
292
+ artifact: Optional[dict] = {}
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+ from logging import getLogger
5
+
6
+ from hardpy.pytest_hardpy.db.base_store import BaseStore
7
+ from hardpy.pytest_hardpy.db import ResultStateStore
8
+ from hardpy.pytest_hardpy.utils import Singleton
9
+
10
+
11
+ class StateStore(Singleton, BaseStore):
12
+ """HardPy state storage interface for CouchDB."""
13
+
14
+ def __init__(self):
15
+ if not self._initialized:
16
+ super().__init__("statestore")
17
+ self._log = getLogger(__name__)
18
+ self._schema = ResultStateStore
19
+ self._initialized = True