hardpy 0.2.0__tar.gz → 0.3.0__tar.gz

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 (73) hide show
  1. {hardpy-0.2.0 → hardpy-0.3.0}/PKG-INFO +8 -1
  2. {hardpy-0.2.0 → hardpy-0.3.0}/README.md +7 -0
  3. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/base_store.py +21 -11
  4. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/plugin.py +68 -4
  5. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/pytest_call.py +16 -8
  6. hardpy-0.3.0/hardpy/pytest_hardpy/reporter/base.py +60 -0
  7. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/reporter/hook_reporter.py +28 -37
  8. hardpy-0.3.0/hardpy/pytest_hardpy/utils/node_info.py +125 -0
  9. {hardpy-0.2.0 → hardpy-0.3.0}/pyproject.toml +1 -1
  10. hardpy-0.2.0/hardpy/pytest_hardpy/reporter/base.py +0 -42
  11. hardpy-0.2.0/hardpy/pytest_hardpy/utils/node_info.py +0 -59
  12. {hardpy-0.2.0 → hardpy-0.3.0}/.gitignore +0 -0
  13. {hardpy-0.2.0 → hardpy-0.3.0}/LICENSE +0 -0
  14. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/__init__.py +0 -0
  15. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/__init__.py +0 -0
  16. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/api.py +0 -0
  17. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/asset-manifest.json +0 -0
  18. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/favicon.ico +0 -0
  19. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/index.html +0 -0
  20. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/logo512.png +0 -0
  21. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/manifest.json +0 -0
  22. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css +0 -0
  23. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css.map +0 -0
  24. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js +0 -0
  25. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js.map +0 -0
  26. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js +0 -0
  27. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js.map +0 -0
  28. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js +0 -0
  29. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js.map +0 -0
  30. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js +0 -0
  31. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map +0 -0
  32. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js +0 -0
  33. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js.map +0 -0
  34. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js +0 -0
  35. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map +0 -0
  36. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js +0 -0
  37. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js.LICENSE.txt +0 -0
  38. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/js/main.8ef63e9b.js.map +0 -0
  39. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.520846c6beb41df528c8.eot +0 -0
  40. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.5c52b39c697f2323ce8b.svg +0 -0
  41. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.84db1772f4bfb529f64f.woff +0 -0
  42. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.b67ee1736e20e37a3225.woff2 +0 -0
  43. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.e02ecf515378db143652.ttf +0 -0
  44. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.429cacb8accf72488451.ttf +0 -0
  45. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.6ae3791ee2d86fc228a6.svg +0 -0
  46. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.8cecf62de42997e4d82f.woff2 +0 -0
  47. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.afbadb627d43b7857223.eot +0 -0
  48. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff +0 -0
  49. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/frontend/dist/static/media/logo_smol.5b16f92447a4a9e80331.png +0 -0
  50. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/hardpy_panel/runner.py +0 -0
  51. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/__init__.py +0 -0
  52. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/__init__.py +0 -0
  53. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/base_connector.py +0 -0
  54. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/base_server.py +0 -0
  55. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/const.py +0 -0
  56. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/runstore.py +0 -0
  57. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/schema.py +0 -0
  58. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/db/statestore.py +0 -0
  59. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/pytest_wrapper.py +0 -0
  60. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/reporter/__init__.py +0 -0
  61. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/reporter/runner_reporter.py +0 -0
  62. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/__init__.py +0 -0
  63. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/couchdb_config.py +0 -0
  64. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/report_loader/__init__.py +0 -0
  65. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +0 -0
  66. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/report_reader/__init__.py +0 -0
  67. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +0 -0
  68. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/__init__.py +0 -0
  69. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/config_data.py +0 -0
  70. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/const.py +0 -0
  71. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/exception.py +0 -0
  72. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/progress_calculator.py +0 -0
  73. {hardpy-0.2.0 → hardpy-0.3.0}/hardpy/pytest_hardpy/utils/singleton.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hardpy
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: HardPy library for device testing
5
5
  Project-URL: repository, https://github.com/everypindevices/hardpy
6
6
  Author: Everypin
@@ -84,6 +84,7 @@ For Windows, follow the instructions from the
84
84
  [documentation](https://everypinio.github.io/hardpy/documentation/database/#couchdb-instance).
85
85
 
86
86
  Launch CouchDB with Docker.
87
+ The Docker version must be 24.0.0 or higher.
87
88
  Create `couchdb.ini` file:
88
89
 
89
90
  ```ini
@@ -103,6 +104,12 @@ Run the Docker container from folder with couchdb.ini file:
103
104
  docker run --rm --name couchdb -p 5984:5984 -e COUCHDB_USER=dev -e COUCHDB_PASSWORD=dev -v ./couchdb.ini:/opt/couchdb/etc/local.ini couchdb:3.3
104
105
  ```
105
106
 
107
+ Command for Windows:
108
+
109
+ ```bash
110
+ docker run --rm --name couchdb -p 5984:5984 -e COUCHDB_USER=dev -e COUCHDB_PASSWORD=dev -v .\couchdb.ini:/opt/couchdb/etc/local.ini couchdb:3.3.2
111
+ ```
112
+
106
113
  #### Test steps
107
114
 
108
115
  Add simple test to `tests` folder
@@ -47,6 +47,7 @@ For Windows, follow the instructions from the
47
47
  [documentation](https://everypinio.github.io/hardpy/documentation/database/#couchdb-instance).
48
48
 
49
49
  Launch CouchDB with Docker.
50
+ The Docker version must be 24.0.0 or higher.
50
51
  Create `couchdb.ini` file:
51
52
 
52
53
  ```ini
@@ -66,6 +67,12 @@ Run the Docker container from folder with couchdb.ini file:
66
67
  docker run --rm --name couchdb -p 5984:5984 -e COUCHDB_USER=dev -e COUCHDB_PASSWORD=dev -v ./couchdb.ini:/opt/couchdb/etc/local.ini couchdb:3.3
67
68
  ```
68
69
 
70
+ Command for Windows:
71
+
72
+ ```bash
73
+ docker run --rm --name couchdb -p 5984:5984 -e COUCHDB_USER=dev -e COUCHDB_PASSWORD=dev -v .\couchdb.ini:/opt/couchdb/etc/local.ini couchdb:3.3.2
74
+ ```
75
+
69
76
  #### Test steps
70
77
 
71
78
  Add simple test to `tests` folder
@@ -36,19 +36,29 @@ class BaseStore(BaseConnector):
36
36
  """
37
37
  return glom(self._doc, key)
38
38
 
39
- def set_value(self, key: str, value):
40
- """Set a value in the state store."""
41
- assign(self._doc, key, value)
39
+ def update_doc(self, key: str, value):
40
+ """Update document.
41
+
42
+ HardPy collecting uses a simple key without dots.
43
+ Assign is used to update a document.
44
+ Assign is a longer function.
45
+
46
+ Args:
47
+ key (str): document key
48
+ value: document value
49
+ """
50
+ if "." in key:
51
+ assign(self._doc, key, value)
52
+ else:
53
+ self._doc[key] = value
54
+
55
+ def update_db(self):
56
+ """Update database by current document."""
42
57
  try:
43
58
  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)
59
+ except Conflict:
60
+ self._doc["_rev"] = self._db.get(self._doc_id)["_rev"]
61
+ self._doc = self._db.save(self._doc)
52
62
 
53
63
  def get_document(self) -> ModelMetaclass:
54
64
  """Get document by schema.
@@ -9,6 +9,7 @@ from platform import system
9
9
 
10
10
  from natsort import natsorted
11
11
  from pytest import (
12
+ skip,
12
13
  exit,
13
14
  TestReport,
14
15
  Item,
@@ -27,6 +28,7 @@ from hardpy.pytest_hardpy.utils import (
27
28
  ProgressCalculator,
28
29
  ConfigData,
29
30
  )
31
+ from hardpy.pytest_hardpy.utils.node_info import TestDependencyInfo
30
32
 
31
33
 
32
34
  def pytest_addoption(parser: Parser):
@@ -40,6 +42,7 @@ def pytest_addoption(parser: Parser):
40
42
  parser.addoption("--hardpy-pt", action="store_true", default=False, help="enable pytest-hardpy plugin") # noqa: E501
41
43
  # fmt: on
42
44
 
45
+
43
46
  # Bootstrapping hooks
44
47
  def pytest_load_initial_conftests(early_config, parser, args):
45
48
  if "--hardpy-pt" in args:
@@ -57,6 +60,7 @@ class HardpyPlugin(object):
57
60
  self._progress = ProgressCalculator()
58
61
  self._results = {}
59
62
  self._post_run_functions: list[Callable] = []
63
+ self._dependencies = {}
60
64
 
61
65
  if system() == "Linux":
62
66
  signal.signal(signal.SIGTERM, self._stop_handler)
@@ -64,7 +68,6 @@ class HardpyPlugin(object):
64
68
  signal.signal(signal.SIGBREAK, self._stop_handler)
65
69
  self._log = getLogger(__name__)
66
70
 
67
-
68
71
  # Initialization hooks
69
72
 
70
73
  def pytest_configure(self, config: Config):
@@ -77,6 +80,7 @@ class HardpyPlugin(object):
77
80
 
78
81
  config.addinivalue_line("markers", "case_name")
79
82
  config.addinivalue_line("markers", "module_name")
83
+ config.addinivalue_line("markers", "dependency")
80
84
 
81
85
  # must be init after config data is set
82
86
  self._reporter = HookReporter()
@@ -92,6 +96,8 @@ class HardpyPlugin(object):
92
96
  return
93
97
  status = self._get_run_status(exitstatus)
94
98
  self._reporter.finish(status)
99
+ self._reporter.update_db_by_doc()
100
+ self._reporter.compact_all()
95
101
 
96
102
  # call post run methods
97
103
  if self._post_run_functions:
@@ -107,6 +113,7 @@ class HardpyPlugin(object):
107
113
  self._reporter.init_doc(str(PurePath(config.rootpath).name))
108
114
 
109
115
  nodes = {}
116
+ modules = set()
110
117
 
111
118
  session.items = natsorted(
112
119
  session.items,
@@ -115,18 +122,26 @@ class HardpyPlugin(object):
115
122
  for item in session.items:
116
123
  if item.parent is None:
117
124
  continue
118
- node_info = NodeInfo(item)
125
+ try:
126
+ node_info = NodeInfo(item)
127
+ except ValueError:
128
+ error_msg = f"Error creating NodeInfo for item: {item}\n"
129
+ exit(error_msg, 1)
119
130
 
120
131
  self._init_case_result(node_info.module_id, node_info.case_id)
121
-
122
132
  if node_info.module_id not in nodes:
123
133
  nodes[node_info.module_id] = [node_info.case_id]
124
134
  else:
125
135
  nodes[node_info.module_id].append(node_info.case_id)
126
136
 
127
137
  self._reporter.add_case(node_info)
128
- self._reporter.set_module_status(node_info.module_id, TestStatus.READY)
138
+
139
+ self._add_dependency(node_info, nodes)
140
+ modules.add(node_info.module_id)
141
+ for module_id in modules:
142
+ self._reporter.set_module_status(module_id, TestStatus.READY)
129
143
  self._reporter.update_node_order(nodes)
144
+ self._reporter.update_db_by_doc()
130
145
 
131
146
  # Test running (runtest) hooks
132
147
 
@@ -139,6 +154,7 @@ class HardpyPlugin(object):
139
154
 
140
155
  # testrun entrypoint
141
156
  self._reporter.start()
157
+ self._reporter.update_db_by_doc()
142
158
 
143
159
  def pytest_runtest_setup(self, item: Item):
144
160
  """Call before each test setup phase."""
@@ -148,6 +164,8 @@ class HardpyPlugin(object):
148
164
 
149
165
  node_info = NodeInfo(item)
150
166
 
167
+ self._handle_dependency(node_info)
168
+
151
169
  self._reporter.set_module_status(node_info.module_id, TestStatus.RUN)
152
170
  self._reporter.set_module_start_time(node_info.module_id)
153
171
  self._reporter.set_case_status(
@@ -159,6 +177,7 @@ class HardpyPlugin(object):
159
177
  node_info.module_id,
160
178
  node_info.case_id,
161
179
  )
180
+ self._reporter.update_db_by_doc()
162
181
 
163
182
  # Reporting hooks
164
183
 
@@ -188,6 +207,7 @@ class HardpyPlugin(object):
188
207
 
189
208
  if None not in self._results[module_id].values():
190
209
  self._collect_module_result(module_id)
210
+ self._reporter.update_db_by_doc()
191
211
 
192
212
  # Fixture
193
213
 
@@ -250,3 +270,47 @@ class HardpyPlugin(object):
250
270
  index = report.find("\nE")
251
271
  return report[:index]
252
272
  return None
273
+
274
+ def _handle_dependency(self, node_info: NodeInfo):
275
+ dependency = self._dependencies.get(
276
+ TestDependencyInfo(
277
+ node_info.module_id,
278
+ node_info.case_id,
279
+ )
280
+ )
281
+ if dependency and self._is_dependency_failed(dependency):
282
+ self._log.debug(f"Skipping test due to dependency: {dependency}")
283
+ self._results[node_info.module_id][node_info.case_id] = TestStatus.SKIPPED
284
+ skip(f"Test {node_info.module_id}::{node_info.case_id} is skipped")
285
+
286
+ def _is_dependency_failed(self, dependency) -> bool:
287
+ if isinstance(dependency, TestDependencyInfo):
288
+ incorrect_status = {
289
+ TestStatus.FAILED,
290
+ TestStatus.SKIPPED,
291
+ TestStatus.ERROR,
292
+ }
293
+ module_id, case_id = dependency
294
+ if case_id is not None:
295
+ return self._results[module_id][case_id] in incorrect_status
296
+ return any(
297
+ status in incorrect_status
298
+ for status in set(self._results[module_id].values())
299
+ )
300
+ return False
301
+
302
+ def _add_dependency(self, node_info, nodes):
303
+ dependency = node_info.dependency
304
+ if dependency is None or dependency == "":
305
+ return
306
+ module_id, case_id = dependency
307
+ if module_id not in nodes:
308
+ error_message = f"Error: Module dependency '{dependency}' not found."
309
+ exit(error_message, 1)
310
+ elif case_id not in nodes[module_id] and case_id is not None:
311
+ error_message = f"Error: Case dependency '{dependency}' not found."
312
+ exit(error_message, 1)
313
+
314
+ self._dependencies[
315
+ TestDependencyInfo(node_info.module_id, node_info.case_id)
316
+ ] = dependency
@@ -52,7 +52,8 @@ def set_dut_info(info: dict):
52
52
  reporter = RunnerReporter()
53
53
  for dut_key, dut_value in info.items():
54
54
  key = reporter.generate_key(DF.DUT, DF.INFO, dut_key)
55
- reporter.set_db_value(key, dut_value)
55
+ reporter.set_doc_value(key, dut_value)
56
+ reporter.update_db_by_doc()
56
57
 
57
58
 
58
59
  def set_dut_serial_number(serial_number: str):
@@ -68,7 +69,8 @@ def set_dut_serial_number(serial_number: str):
68
69
  key = reporter.generate_key(DF.DUT, DF.SERIAL_NUMBER)
69
70
  if reporter.get_field(key):
70
71
  raise DuplicateSerialNumberError
71
- reporter.set_db_value(key, serial_number)
72
+ reporter.set_doc_value(key, serial_number)
73
+ reporter.update_db_by_doc()
72
74
 
73
75
 
74
76
  def set_stand_info(info: dict):
@@ -80,7 +82,8 @@ def set_stand_info(info: dict):
80
82
  reporter = RunnerReporter()
81
83
  for stand_key, stand_value in info.items():
82
84
  key = reporter.generate_key(DF.TEST_STAND, stand_key)
83
- reporter.set_db_value(key, stand_value)
85
+ reporter.set_doc_value(key, stand_value)
86
+ reporter.update_db_by_doc()
84
87
 
85
88
 
86
89
  def set_message(msg: str, msg_key: Optional[str] = None) -> None:
@@ -111,7 +114,8 @@ def set_message(msg: str, msg_key: Optional[str] = None) -> None:
111
114
 
112
115
  msgs[msg_key] = msg
113
116
 
114
- reporter.set_db_value(key, msgs)
117
+ reporter.set_doc_value(key, msgs)
118
+ reporter.update_db_by_doc()
115
119
 
116
120
 
117
121
  def set_case_artifact(data: dict):
@@ -134,7 +138,8 @@ def set_case_artifact(data: dict):
134
138
  DF.ARTIFACT,
135
139
  stand_key,
136
140
  )
137
- reporter.set_db_value(key, stand_value, is_statestore=False)
141
+ reporter.set_doc_value(key, stand_value, runstore_only=True)
142
+ reporter.update_db_by_doc()
138
143
 
139
144
 
140
145
  def set_module_artifact(data: dict):
@@ -155,7 +160,8 @@ def set_module_artifact(data: dict):
155
160
  DF.ARTIFACT,
156
161
  artifact_key,
157
162
  )
158
- reporter.set_db_value(key, artifact_value, is_statestore=False)
163
+ reporter.set_doc_value(key, artifact_value, runstore_only=True)
164
+ reporter.update_db_by_doc()
159
165
 
160
166
 
161
167
  def set_run_artifact(data: dict):
@@ -173,7 +179,8 @@ def set_run_artifact(data: dict):
173
179
  DF.ARTIFACT,
174
180
  artifact_key,
175
181
  )
176
- reporter.set_db_value(key, artifact_value, is_statestore=False)
182
+ reporter.set_doc_value(key, artifact_value, runstore_only=True)
183
+ reporter.update_db_by_doc()
177
184
 
178
185
 
179
186
  def set_driver_info(drivers: dict) -> None:
@@ -192,7 +199,8 @@ def set_driver_info(drivers: dict) -> None:
192
199
  DF.DRIVERS,
193
200
  driver_name,
194
201
  )
195
- reporter.set_db_value(key, driver_data)
202
+ reporter.set_doc_value(key, driver_data)
203
+ reporter.update_db_by_doc()
196
204
 
197
205
 
198
206
  def _get_current_test() -> CurrentTestInfo:
@@ -0,0 +1,60 @@
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 import StateStore, RunStore
7
+
8
+
9
+ class BaseReporter(object):
10
+ """Base class for test reporter."""
11
+
12
+ def __init__(self):
13
+ self._statestore = StateStore()
14
+ self._runstore = RunStore()
15
+ self._log = getLogger(__name__)
16
+
17
+ def set_doc_value(
18
+ self, key: str, value, runstore_only=False, statestore_only=False
19
+ ):
20
+ """Set value to the document.
21
+
22
+ Update a document without writing to the database.
23
+
24
+ Args:
25
+ key (str): document key
26
+ value: document value
27
+ runstore_only (bool, optional): indicates whether data should
28
+ be written to RunStore. Defaults to True.
29
+ statestore_only (bool, optional): indicates whether data should
30
+ be written to StateStore. Defaults to True.
31
+
32
+ Raises:
33
+ ValueError: if both runstore_only and statestore_only are True
34
+ """
35
+ if runstore_only and statestore_only:
36
+ raise ValueError("Both runstore_only and statestore_only cannot be True")
37
+ if runstore_only:
38
+ self._runstore.update_doc(key, value)
39
+ return
40
+ if statestore_only:
41
+ self._statestore.update_doc(key, value)
42
+ return
43
+ self._runstore.update_doc(key, value)
44
+ self._statestore.update_doc(key, value)
45
+
46
+ def update_db_by_doc(self):
47
+ """Update database by current document."""
48
+ self._statestore.update_db()
49
+ self._runstore.update_db()
50
+
51
+ def generate_key(self, *args) -> str:
52
+ """Generate key for database.
53
+
54
+ Args:
55
+ args: list of database keys
56
+
57
+ Returns:
58
+ str: database key
59
+ """
60
+ return ".".join(args)
@@ -25,23 +25,23 @@ class HookReporter(BaseReporter):
25
25
  Args:
26
26
  doc_name (str): test run name
27
27
  """
28
- self.set_db_value(DF.NAME, doc_name)
29
- self.set_db_value(DF.STATUS, TestStatus.READY)
30
- self.set_db_value(DF.START_TIME, None)
31
- self.set_db_value(DF.TIMEZONE, None)
32
- self.set_db_value(DF.STOP_TIME, None)
33
- self.set_db_value(DF.PROGRESS, 0)
34
- self.set_db_value(DF.DRIVERS, {})
35
- self.set_db_value(DF.ARTIFACT, {}, is_statestore=False)
28
+ self.set_doc_value(DF.NAME, doc_name)
29
+ self.set_doc_value(DF.STATUS, TestStatus.READY)
30
+ self.set_doc_value(DF.START_TIME, None)
31
+ self.set_doc_value(DF.TIMEZONE, None)
32
+ self.set_doc_value(DF.STOP_TIME, None)
33
+ self.set_doc_value(DF.PROGRESS, 0)
34
+ self.set_doc_value(DF.DRIVERS, {})
35
+ self.set_doc_value(DF.ARTIFACT, {}, runstore_only=True)
36
36
 
37
37
  def start(self):
38
38
  """Start test."""
39
39
  self._log.debug("Starting test run.")
40
40
  start_time = int(time())
41
- self.set_db_value(DF.START_TIME, start_time)
42
- self.set_db_value(DF.STATUS, TestStatus.RUN)
43
- self.set_db_value(DF.TIMEZONE, tzname) # noqa: WPS432
44
- self.set_db_value(DF.PROGRESS, 0)
41
+ self.set_doc_value(DF.START_TIME, start_time)
42
+ self.set_doc_value(DF.STATUS, TestStatus.RUN)
43
+ self.set_doc_value(DF.TIMEZONE, tzname) # noqa: WPS432
44
+ self.set_doc_value(DF.PROGRESS, 0)
45
45
 
46
46
  def finish(self, status: RunStatus):
47
47
  """Finish test.
@@ -50,15 +50,11 @@ class HookReporter(BaseReporter):
50
50
  """
51
51
  self._log.debug("Finishing test run.")
52
52
  stop_time = int(time())
53
- self.set_db_value(DF.STOP_TIME, stop_time)
54
- self.set_db_value(DF.STATUS, status)
55
-
56
- if self._statestore.get_document():
57
- self._log.debug("Report StateStore has been successfully validated.")
58
-
59
- if self._runstore.get_document():
60
- self._log.debug("Report RunStore has been successfully validated.")
53
+ self.set_doc_value(DF.STOP_TIME, stop_time)
54
+ self.set_doc_value(DF.STATUS, status)
61
55
 
56
+ def compact_all(self):
57
+ """Compact all databases"""
62
58
  self._statestore.compact()
63
59
  self._runstore.compact()
64
60
 
@@ -68,7 +64,7 @@ class HookReporter(BaseReporter):
68
64
  Args:
69
65
  progress (int): test progress
70
66
  """
71
- self.set_db_value(DF.PROGRESS, progress)
67
+ self.set_doc_value(DF.PROGRESS, progress)
72
68
 
73
69
  def set_assertion_msg(self, module_id: str, case_id: str, msg: str | None):
74
70
  """Set case assertion message.
@@ -81,7 +77,7 @@ class HookReporter(BaseReporter):
81
77
  key = self.generate_key(
82
78
  DF.MODULES, module_id, DF.CASES, case_id, DF.ASSERTION_MSG
83
79
  )
84
- self.set_db_value(key, msg)
80
+ self.set_doc_value(key, msg)
85
81
 
86
82
  def add_case(self, node_info: NodeInfo):
87
83
  """Add test case to document.
@@ -90,18 +86,15 @@ class HookReporter(BaseReporter):
90
86
  node_info (NodeInfo): node info
91
87
  """
92
88
  key = DF.MODULES
89
+
93
90
  item_statestore = self._statestore.get_field(key)
94
91
  item_runstore = self._runstore.get_field(key)
95
92
 
96
- new_item_statestore = self._init_case(item_statestore, node_info)
97
- new_item_runstore = self._init_case(
98
- item_runstore,
99
- node_info,
100
- is_use_artifact=True,
101
- )
93
+ self._init_case(item_statestore, node_info)
94
+ self._init_case(item_runstore, node_info, is_use_artifact=True)
102
95
 
103
- self.set_db_value(key, new_item_statestore, is_runstore=False)
104
- self.set_db_value(key, new_item_runstore, is_statestore=False)
96
+ self.set_doc_value(key, item_statestore, statestore_only=True)
97
+ self.set_doc_value(key, item_runstore, runstore_only=True)
105
98
 
106
99
  def set_case_status(self, module_id: str, case_id: str, status: TestStatus):
107
100
  """Set test case status.
@@ -112,7 +105,7 @@ class HookReporter(BaseReporter):
112
105
  status (TestStatus): test case status
113
106
  """
114
107
  key = self.generate_key(DF.MODULES, module_id, DF.CASES, case_id, DF.STATUS)
115
- self.set_db_value(key, status)
108
+ self.set_doc_value(key, status)
116
109
 
117
110
  def set_case_start_time(self, module_id: str, case_id: str):
118
111
  """Set test case start_time.
@@ -142,7 +135,7 @@ class HookReporter(BaseReporter):
142
135
  status (TestStatus): test module status
143
136
  """
144
137
  key = self.generate_key(DF.MODULES, module_id, DF.STATUS)
145
- self.set_db_value(key, status)
138
+ self.set_doc_value(key, status)
146
139
 
147
140
  def set_module_start_time(self, module_id: str):
148
141
  """Set test module status.
@@ -175,16 +168,16 @@ class HookReporter(BaseReporter):
175
168
  rm_outdated_nodes = self._remove_outdate_node(old_modules, modules_copy, nodes)
176
169
  updated_case_order = self._update_case_order(rm_outdated_nodes, nodes)
177
170
  updated_module_order = self._update_module_order(updated_case_order)
178
- self.set_db_value(key, updated_module_order, is_runstore=False)
171
+ self.set_doc_value(key, updated_module_order, statestore_only=True)
179
172
 
180
173
  def _set_time(self, key: str):
181
174
  current_time = self._statestore.get_field(key)
182
175
  if current_time is None:
183
- self.set_db_value(key, int(time()))
176
+ self.set_doc_value(key, int(time()))
184
177
 
185
178
  def _init_case(
186
179
  self, item: dict, node_info: NodeInfo, is_use_artifact: bool = False
187
- ) -> dict:
180
+ ):
188
181
  module_default = { # noqa: WPS204
189
182
  DF.STATUS: TestStatus.READY,
190
183
  DF.NAME: self._get_module_name(node_info),
@@ -216,8 +209,6 @@ class HookReporter(BaseReporter):
216
209
  case_default[DF.ARTIFACT] = {}
217
210
  item[node_info.module_id][DF.CASES][node_info.case_id] = case_default
218
211
 
219
- return item
220
-
221
212
  def _remove_outdate_node(
222
213
  self, old_modules: dict, new_modules: dict, nodes: dict
223
214
  ) -> dict:
@@ -0,0 +1,125 @@
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 re
5
+ from logging import getLogger
6
+ from pathlib import Path
7
+ from typing import NamedTuple
8
+
9
+ from pytest import Item, Mark
10
+
11
+
12
+ class TestDependencyInfo(NamedTuple):
13
+ """Test info."""
14
+
15
+ def __repr__(self) -> str:
16
+ return f"Dependency: {self.module_id}::{self.case_id}"
17
+
18
+ module_id: str
19
+ case_id: str | None
20
+
21
+
22
+ class NodeInfo(object):
23
+ """Test node info."""
24
+
25
+ def __init__(self, item: Item):
26
+ self._item = item
27
+ self._log = getLogger(__name__)
28
+
29
+ self._case_name = self._get_human_name(
30
+ item.own_markers,
31
+ "case_name",
32
+ )
33
+ self._module_name = self._get_human_name(
34
+ item.parent.own_markers,
35
+ "module_name",
36
+ )
37
+
38
+ self._dependency = self._get_dependency_info(
39
+ item.own_markers + item.parent.own_markers
40
+ )
41
+
42
+ self._module_id = Path(item.parent.nodeid).stem
43
+ self._case_id = item.name
44
+
45
+ @property
46
+ def module_id(self):
47
+ """Get module id.
48
+
49
+ Returns:
50
+ str: module id
51
+ """
52
+ return self._module_id
53
+
54
+ @property
55
+ def case_id(self):
56
+ """Get case id.
57
+
58
+ Returns:
59
+ str: case id
60
+ """
61
+ return self._case_id
62
+
63
+ @property
64
+ def module_name(self):
65
+ """Get module name.
66
+
67
+ Returns:
68
+ str: module name
69
+ """
70
+ return self._module_name
71
+
72
+ @property
73
+ def case_name(self):
74
+ """Get case name.
75
+
76
+ Returns:
77
+ str: case name
78
+ """
79
+ return self._case_name
80
+
81
+ @property
82
+ def dependency(self):
83
+ """Get dependency information.
84
+
85
+ Returns:
86
+ TestDependencyInfo | str: Parsed dependency information.
87
+ """
88
+ return self._dependency
89
+
90
+ def _get_human_name(self, markers: list[Mark], marker_name: str) -> str:
91
+ """Get human name from markers.
92
+
93
+ Args:
94
+ markers (list[Mark]): item markers list
95
+ marker_name (str): marker name
96
+
97
+ Returns:
98
+ str: human name by user
99
+ """
100
+ for marker in markers:
101
+ if marker.name == marker_name:
102
+ if marker.args:
103
+ return marker.args[0]
104
+
105
+ return ""
106
+
107
+ def _get_dependency_info(self, markers: list[Mark]) -> TestDependencyInfo | str:
108
+ """Extract and parse dependency information.
109
+
110
+ Args:
111
+ markers (list[Mark]): item markers list
112
+ marker_name (str): marker name
113
+
114
+ Returns:
115
+ TestDependencyInfo | str | None: Parsed dependency information.
116
+ """
117
+ dependency_value = self._get_human_name(markers, "dependency")
118
+ dependency_data = re.search(r"(\w+)::(\w+)", dependency_value)
119
+ if dependency_data:
120
+ return TestDependencyInfo(*dependency_data.groups())
121
+ elif re.search(r"^\w+$", dependency_value):
122
+ return TestDependencyInfo(dependency_value, None)
123
+ elif dependency_data is None and dependency_value == "":
124
+ return ""
125
+ raise ValueError
@@ -3,7 +3,7 @@
3
3
 
4
4
  [project]
5
5
  name = "hardpy"
6
- version = "0.2.0"
6
+ version = "0.3.0"
7
7
  description = "HardPy library for device testing"
8
8
  authors = [{ name = "Everypin" }]
9
9
  readme = "README.md"
@@ -1,42 +0,0 @@
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 import StateStore, RunStore
7
-
8
-
9
- class BaseReporter(object):
10
- """Base class for test reporter."""
11
-
12
- def __init__(self):
13
- self._statestore = StateStore()
14
- self._runstore = RunStore()
15
- self._log = getLogger(__name__)
16
-
17
- def set_db_value(self, key: str, value, is_statestore=True, is_runstore=True):
18
- """Set value to database.
19
-
20
- Args:
21
- key (str): database key
22
- value (_type_): database value
23
- is_statestore (bool, optional): indicates whether data should
24
- be written to StateStore. Defaults to True.
25
- is_runstore (bool, optional): indicates whether data should
26
- be written to RunStore. Defaults to True.
27
- """
28
- if is_statestore:
29
- self._statestore.set_value(key, value)
30
- if is_runstore:
31
- self._runstore.set_value(key, value)
32
-
33
- def generate_key(self, *args) -> str:
34
- """Generate key for database.
35
-
36
- Args:
37
- args: list of database keys
38
-
39
- Returns:
40
- str: database key
41
- """
42
- return ".".join(args)
@@ -1,59 +0,0 @@
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 pathlib import Path
6
-
7
- from pytest import Item, Mark
8
-
9
-
10
- class NodeInfo(object):
11
- """Test node info."""
12
-
13
- def __init__(self, item: Item):
14
- self._item = item
15
- self._log = getLogger(__name__)
16
-
17
- self._case_name = self._get_human_name(
18
- item.own_markers,
19
- "case_name",
20
- )
21
- self._module_name = self._get_human_name(
22
- item.parent.own_markers,
23
- "module_name",
24
- )
25
- self._module_id = Path(item.parent.nodeid).stem
26
- self._case_id = item.name
27
-
28
- @property
29
- def module_id(self):
30
- return self._module_id
31
-
32
- @property
33
- def case_id(self):
34
- return self._case_id
35
-
36
- @property
37
- def module_name(self):
38
- return self._module_name
39
-
40
- @property
41
- def case_name(self):
42
- return self._case_name
43
-
44
- def _get_human_name(self, markers: list[Mark], marker_name: str) -> str:
45
- """Get human name from markers.
46
-
47
- Args:
48
- markers (list[Mark]): item markers list
49
- marker_name (str): marker name
50
-
51
- Returns:
52
- str: human name by user
53
- """
54
- for marker in markers:
55
- if marker.name == marker_name:
56
- if marker.args:
57
- return marker.args[0]
58
-
59
- return ""
File without changes
File without changes
File without changes