hardpy 0.1.0__py3-none-any.whl → 0.3.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 CHANGED
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2024 Everypin
2
2
  # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
 
4
- from hardpy.pytest_hardpy.plugin import HardpyPlugin
5
4
  from hardpy.pytest_hardpy.result import CouchdbLoader
6
5
  from hardpy.pytest_hardpy.utils import DuplicateSerialNumberError
7
6
  from hardpy.pytest_hardpy.result.couchdb_config import CouchdbConfig
@@ -18,7 +17,6 @@ from hardpy.pytest_hardpy.pytest_call import (
18
17
  )
19
18
 
20
19
  __all__ = [
21
- "HardpyPlugin",
22
20
  "CouchdbLoader",
23
21
  "CouchdbConfig",
24
22
  "DuplicateSerialNumberError",
@@ -66,6 +66,6 @@ def couch_connection():
66
66
 
67
67
  app.mount(
68
68
  "/",
69
- StaticFiles(directory=(os.path.dirname(__file__))+'/frontend/dist', html=True),
69
+ StaticFiles(directory=(os.path.dirname(__file__)) + "/frontend/dist", html=True),
70
70
  name="static",
71
71
  )
@@ -15,7 +15,7 @@ def run():
15
15
  config = ConfigData()
16
16
  parser = ArgumentParser(description="Usage: hardpy-panel [OPTION]... [PATH]")
17
17
  # fmt: off
18
- parser.add_argument("-dbu", "--db_user", default=config.db_user, help="database user")
18
+ parser.add_argument("-dbu", "--db_user", default=config.db_user, help="database user") # noqa: E501
19
19
  parser.add_argument("-dbpw", "--db_pswd", default=config.db_pswd, help="database user password") # noqa: E501
20
20
  parser.add_argument("-dbp", "--db_port", type=int, default=config.db_port, help="database port number") # noqa: E501
21
21
  parser.add_argument("-dbh", "--db_host", type=str, default=config.db_host, help="database hostname") # noqa: E501
@@ -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):
@@ -37,9 +39,17 @@ def pytest_addoption(parser: Parser):
37
39
  parser.addoption("--hardpy-dbpw", action="store", default=config_data.db_pswd, help="database user password") # noqa: E501
38
40
  parser.addoption("--hardpy-dbp", action="store", default=config_data.db_port, help="database port number") # noqa: E501
39
41
  parser.addoption("--hardpy-dbh", action="store", default=config_data.db_host, help="database hostname") # noqa: E501
42
+ parser.addoption("--hardpy-pt", action="store_true", default=False, help="enable pytest-hardpy plugin") # noqa: E501
40
43
  # fmt: on
41
44
 
42
45
 
46
+ # Bootstrapping hooks
47
+ def pytest_load_initial_conftests(early_config, parser, args):
48
+ if "--hardpy-pt" in args:
49
+ plugin = HardpyPlugin()
50
+ early_config.pluginmanager.register(plugin)
51
+
52
+
43
53
  class HardpyPlugin(object):
44
54
  """HardPy integration plugin for pytest.
45
55
 
@@ -50,6 +60,7 @@ class HardpyPlugin(object):
50
60
  self._progress = ProgressCalculator()
51
61
  self._results = {}
52
62
  self._post_run_functions: list[Callable] = []
63
+ self._dependencies = {}
53
64
 
54
65
  if system() == "Linux":
55
66
  signal.signal(signal.SIGTERM, self._stop_handler)
@@ -69,6 +80,7 @@ class HardpyPlugin(object):
69
80
 
70
81
  config.addinivalue_line("markers", "case_name")
71
82
  config.addinivalue_line("markers", "module_name")
83
+ config.addinivalue_line("markers", "dependency")
72
84
 
73
85
  # must be init after config data is set
74
86
  self._reporter = HookReporter()
@@ -84,6 +96,8 @@ class HardpyPlugin(object):
84
96
  return
85
97
  status = self._get_run_status(exitstatus)
86
98
  self._reporter.finish(status)
99
+ self._reporter.update_db_by_doc()
100
+ self._reporter.compact_all()
87
101
 
88
102
  # call post run methods
89
103
  if self._post_run_functions:
@@ -99,6 +113,7 @@ class HardpyPlugin(object):
99
113
  self._reporter.init_doc(str(PurePath(config.rootpath).name))
100
114
 
101
115
  nodes = {}
116
+ modules = set()
102
117
 
103
118
  session.items = natsorted(
104
119
  session.items,
@@ -107,18 +122,26 @@ class HardpyPlugin(object):
107
122
  for item in session.items:
108
123
  if item.parent is None:
109
124
  continue
110
- 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)
111
130
 
112
131
  self._init_case_result(node_info.module_id, node_info.case_id)
113
-
114
132
  if node_info.module_id not in nodes:
115
133
  nodes[node_info.module_id] = [node_info.case_id]
116
134
  else:
117
135
  nodes[node_info.module_id].append(node_info.case_id)
118
136
 
119
137
  self._reporter.add_case(node_info)
120
- 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)
121
143
  self._reporter.update_node_order(nodes)
144
+ self._reporter.update_db_by_doc()
122
145
 
123
146
  # Test running (runtest) hooks
124
147
 
@@ -131,6 +154,7 @@ class HardpyPlugin(object):
131
154
 
132
155
  # testrun entrypoint
133
156
  self._reporter.start()
157
+ self._reporter.update_db_by_doc()
134
158
 
135
159
  def pytest_runtest_setup(self, item: Item):
136
160
  """Call before each test setup phase."""
@@ -140,6 +164,8 @@ class HardpyPlugin(object):
140
164
 
141
165
  node_info = NodeInfo(item)
142
166
 
167
+ self._handle_dependency(node_info)
168
+
143
169
  self._reporter.set_module_status(node_info.module_id, TestStatus.RUN)
144
170
  self._reporter.set_module_start_time(node_info.module_id)
145
171
  self._reporter.set_case_status(
@@ -151,6 +177,7 @@ class HardpyPlugin(object):
151
177
  node_info.module_id,
152
178
  node_info.case_id,
153
179
  )
180
+ self._reporter.update_db_by_doc()
154
181
 
155
182
  # Reporting hooks
156
183
 
@@ -180,6 +207,7 @@ class HardpyPlugin(object):
180
207
 
181
208
  if None not in self._results[module_id].values():
182
209
  self._collect_module_result(module_id)
210
+ self._reporter.update_db_by_doc()
183
211
 
184
212
  # Fixture
185
213
 
@@ -242,3 +270,47 @@ class HardpyPlugin(object):
242
270
  index = report.find("\nE")
243
271
  return report[:index]
244
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:
@@ -46,6 +46,7 @@ class PyTestWrapper(object):
46
46
  self.config.db_user,
47
47
  "--hardpy-dbpw",
48
48
  self.config.db_pswd,
49
+ "--hardpy-pt",
49
50
  ],
50
51
  cwd=self.config.tests_dir.absolute(),
51
52
  )
@@ -63,6 +64,7 @@ class PyTestWrapper(object):
63
64
  self.config.db_user,
64
65
  "--hardpy-dbpw",
65
66
  self.config.db_pswd,
67
+ "--hardpy-pt",
66
68
  ],
67
69
  cwd=self.config.tests_dir.absolute(),
68
70
  creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
@@ -107,6 +109,7 @@ class PyTestWrapper(object):
107
109
  self.config.db_user,
108
110
  "--hardpy-dbpw",
109
111
  self.config.db_pswd,
112
+ "--hardpy-pt",
110
113
  ],
111
114
  cwd=self.config.tests_dir.absolute(),
112
115
  )
@@ -14,21 +14,39 @@ class BaseReporter(object):
14
14
  self._runstore = RunStore()
15
15
  self._log = getLogger(__name__)
16
16
 
17
- def set_db_value(self, key: str, value, is_statestore=True, is_runstore=True):
18
- """Set value to database.
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.
19
23
 
20
24
  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.
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
27
34
  """
28
- if is_statestore:
29
- self._statestore.set_value(key, value)
30
- if is_runstore:
31
- self._runstore.set_value(key, value)
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()
32
50
 
33
51
  def generate_key(self, *args) -> str:
34
52
  """Generate key for database.
@@ -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:
@@ -1,12 +1,24 @@
1
1
  # Copyright (c) 2024 Everypin
2
2
  # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
 
4
+ import re
4
5
  from logging import getLogger
5
6
  from pathlib import Path
7
+ from typing import NamedTuple
6
8
 
7
9
  from pytest import Item, Mark
8
10
 
9
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
+
10
22
  class NodeInfo(object):
11
23
  """Test node info."""
12
24
 
@@ -22,25 +34,59 @@ class NodeInfo(object):
22
34
  item.parent.own_markers,
23
35
  "module_name",
24
36
  )
37
+
38
+ self._dependency = self._get_dependency_info(
39
+ item.own_markers + item.parent.own_markers
40
+ )
41
+
25
42
  self._module_id = Path(item.parent.nodeid).stem
26
43
  self._case_id = item.name
27
44
 
28
45
  @property
29
46
  def module_id(self):
47
+ """Get module id.
48
+
49
+ Returns:
50
+ str: module id
51
+ """
30
52
  return self._module_id
31
53
 
32
54
  @property
33
55
  def case_id(self):
56
+ """Get case id.
57
+
58
+ Returns:
59
+ str: case id
60
+ """
34
61
  return self._case_id
35
62
 
36
63
  @property
37
64
  def module_name(self):
65
+ """Get module name.
66
+
67
+ Returns:
68
+ str: module name
69
+ """
38
70
  return self._module_name
39
71
 
40
72
  @property
41
73
  def case_name(self):
74
+ """Get case name.
75
+
76
+ Returns:
77
+ str: case name
78
+ """
42
79
  return self._case_name
43
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
+
44
90
  def _get_human_name(self, markers: list[Mark], marker_name: str) -> str:
45
91
  """Get human name from markers.
46
92
 
@@ -57,3 +103,23 @@ class NodeInfo(object):
57
103
  return marker.args[0]
58
104
 
59
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hardpy
3
- Version: 0.1.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
@@ -36,7 +36,7 @@ Requires-Dist: wemake-python-styleguide; extra == 'dev'
36
36
  Description-Content-Type: text/markdown
37
37
 
38
38
  <h1 align="center">
39
- <img src="docs/img/logo512.png" alt="HardPy" style="width:200px;">
39
+ <img src="https://everypinio.github.io/hardpy/img/logo512.png" alt="HardPy" style="width:200px;">
40
40
  </h1>
41
41
 
42
42
  <h1 align="center">
@@ -47,6 +47,16 @@ Description-Content-Type: text/markdown
47
47
  HardPy is a python library for creating a test bench for devices.
48
48
  </p>
49
49
 
50
+ ---
51
+
52
+ **Documentation**: <a href=https://everypinio.github.io/hardpy/ target="_blank">https://everypinio.github.io/hardpy/</a>
53
+
54
+ **Source Code**: <a href=https://github.com/everypinio/hardpy target="_blank">https://github.com/everypinio/hardpy</a>
55
+
56
+ **PyPi**: <a href=https://pypi.org/project/hardpy/ target="_blank">https://pypi.org/project/hardpy/</a>
57
+
58
+ ---
59
+
50
60
  ## Overview
51
61
 
52
62
  HardPy allows you to:
@@ -69,7 +79,12 @@ Find examples of using the **HardPy** in the `examples` folder.
69
79
 
70
80
  #### CouchDB
71
81
 
82
+ This is a simple instruction for Linux.
83
+ For Windows, follow the instructions from the
84
+ [documentation](https://everypinio.github.io/hardpy/documentation/database/#couchdb-instance).
85
+
72
86
  Launch CouchDB with Docker.
87
+ The Docker version must be 24.0.0 or higher.
73
88
  Create `couchdb.ini` file:
74
89
 
75
90
  ```ini
@@ -86,21 +101,18 @@ headers = accept, authorization, content-type, origin, referer, x-csrf-token
86
101
  Run the Docker container from folder with couchdb.ini file:
87
102
 
88
103
  ```bash
89
- docker run --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
+ 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
90
105
  ```
91
106
 
92
- #### Test steps
107
+ Command for Windows:
93
108
 
94
- Add simple test to `tests` folder
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
+ ```
95
112
 
96
- ```python
97
- # conftest.py
98
- import pytest
99
- import hardpy
113
+ #### Test steps
100
114
 
101
- def pytest_configure(config: pytest.Config):
102
- config.pluginmanager.register(hardpy.HardpyPlugin())
103
- ```
115
+ Add simple test to `tests` folder
104
116
 
105
117
  ```python
106
118
  # test_1.py
@@ -114,16 +126,16 @@ def test_one():
114
126
  Launch `hardpy-panel` from tests folder or launch `hardpy-panel tests` and open page http://localhost:8000/ in browser.
115
127
 
116
128
  <h1 align="center">
117
- <img src="docs/img/hardpy_operator_panel_hello_hardpy.png"
129
+ <img src="https://everypinio.github.io/hardpy/img/hardpy_operator_panel_hello_hardpy.png"
118
130
  alt="hardpy operator panel" style="width:600px;">
119
131
  </h1>
120
132
 
121
133
  #### Test report
122
134
 
123
135
  The last test report is stored in **runstore** database, document - **current**.
124
- You can view the CouchDB instance through Fauxton web interface: http://127.0.0.1:5984/_utils
136
+ You can view the CouchDB instance through Fauxton web interface: http://localhost:5984/_utils
125
137
 
126
138
  <h1 align="center">
127
- <img src="docs/img/runstore_hello_hardpy.png"
139
+ <img src="https://everypinio.github.io/hardpy/img/runstore_hello_hardpy.png"
128
140
  alt="hardpy runstore" style="width:600px;">
129
141
  </h1>
@@ -1,7 +1,7 @@
1
- hardpy/__init__.py,sha256=_IaxXKdnYEnxyDOLKCvBHisLX45MbCBgVOlymMS-0so,937
1
+ hardpy/__init__.py,sha256=RwEQwrdXAtjf2UKOTyXiOUvMKOpXCy7TrJR03V4HY98,864
2
2
  hardpy/hardpy_panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- hardpy/hardpy_panel/api.py,sha256=D5w0Y2VPfdOsatlGMKY0ywMKRNmNfZvcke4uGhyrRqI,1565
4
- hardpy/hardpy_panel/runner.py,sha256=pmyIgdXIf-RvNFUsw5uBUdXzBOLeGOFzBTZMxQg45KM,1852
3
+ hardpy/hardpy_panel/api.py,sha256=ool8MvCH9SFGHooOloboListg1Mas4FciMQsyRjhWGE,1567
4
+ hardpy/hardpy_panel/runner.py,sha256=J8c2Pzemwr1Xzs0SOIqLqUNNf_4Jb5BJN_fo4pU3Za4,1866
5
5
  hardpy/hardpy_panel/frontend/dist/asset-manifest.json,sha256=Z6KDx9WMNghh4d_7w52kTSdEec-lup56JR6JEP3WY94,2824
6
6
  hardpy/hardpy_panel/frontend/dist/favicon.ico,sha256=sgIk5PKUKEKBDpkSrc8dJgjpObp0iF82Mec0GpfKId4,15406
7
7
  hardpy/hardpy_panel/frontend/dist/index.html,sha256=u1IJG5LkBLmaPgL-dYJ4rIfmxzf0xNhmmOupezuobgg,656
@@ -36,20 +36,20 @@ hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.afbadb627d43b7
36
36
  hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff,sha256=mQZTxE1PyyAL16VWuASOvXlZFwuI4aCPvbrhfgpdIdU,55356
37
37
  hardpy/hardpy_panel/frontend/dist/static/media/logo_smol.5b16f92447a4a9e80331.png,sha256=E4K7drvhJCg9HcTpRihOXZhVJVBZ7-W97Se-3tDb46o,14485
38
38
  hardpy/pytest_hardpy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- hardpy/pytest_hardpy/plugin.py,sha256=wvZaEpJHufYl5vPWXoeMPpnwud6vvL8el1atX-EowD0,8092
40
- hardpy/pytest_hardpy/pytest_call.py,sha256=wyMDt4AwQC4qg6pYnevQv7FIhIvHNYreQdU1kEenqdY,5910
41
- hardpy/pytest_hardpy/pytest_wrapper.py,sha256=mocoISmNE5sfWtY8rsAe-8N9PxWV7ASUr6pRj9N6yUo,3453
39
+ hardpy/pytest_hardpy/plugin.py,sha256=lL3ZNVVSlS7VzpVDiF_SWbnbFBbK9vXtD6JZwqBoObA,10973
40
+ hardpy/pytest_hardpy/pytest_call.py,sha256=CrbAw_W6A1HU06xIcylivgNKVUzGoo0B0ikNqflHbDo,6171
41
+ hardpy/pytest_hardpy/pytest_wrapper.py,sha256=bC8ROwAEjo3TpXpNtHXUS-C_fpIyeCPtgfGBSZ82aI8,3554
42
42
  hardpy/pytest_hardpy/db/__init__.py,sha256=MxDufncz0zgRAxrndvPXXW4NrU7rRP7MzIrR7S5Cwwo,558
43
43
  hardpy/pytest_hardpy/db/base_connector.py,sha256=7KUgPY-GmAo8MFN4OFpG5y3WH1xjohRnpeQ1gxQF1tg,751
44
44
  hardpy/pytest_hardpy/db/base_server.py,sha256=uBnq5zGkzEIq_EGzLw0C8kfDEDvQyN52Y6L41KKL9FQ,397
45
- hardpy/pytest_hardpy/db/base_store.py,sha256=iKh8qN-sPu79Ogp5857TR0t5RbGssC8V7iFAZAF5aFU,2504
45
+ hardpy/pytest_hardpy/db/base_store.py,sha256=DiYaBOwufEOdtDpo9dUb3ZaZ7-c1FInAWjLpUXSEFHA,2668
46
46
  hardpy/pytest_hardpy/db/const.py,sha256=ffYW54TP0aNF5LhW3g_2G5kVuvqAMWfuJqNDzWZg2nI,618
47
47
  hardpy/pytest_hardpy/db/runstore.py,sha256=50amoTIO7OTqd5Ks1_7uTzqjCldLpTapkxbIQOgj1sQ,1023
48
48
  hardpy/pytest_hardpy/db/schema.py,sha256=iIclTudP0tauTWLYiEW9MMlvBfuWOteA7eRzDU5gKwI,6859
49
49
  hardpy/pytest_hardpy/db/statestore.py,sha256=1BUfA4oqG4vx7z5v_uUYi_Un6YA769JeuShxDicrl9Q,636
50
50
  hardpy/pytest_hardpy/reporter/__init__.py,sha256=RONapygH3c_FyXokAlyCVJXGV2cV_jCYDxLymvvA1uE,322
51
- hardpy/pytest_hardpy/reporter/base.py,sha256=kHYSLVxiq5Fyf4V-xGtgfOS3nImN1j5c13zcvqK1sq0,1343
52
- hardpy/pytest_hardpy/reporter/hook_reporter.py,sha256=_7mM3B0U_vsuGGAKuX7jtuS045XVRouCeF0Q8uSUAwY,10181
51
+ hardpy/pytest_hardpy/reporter/base.py,sha256=M-lwli64ty9FW8HlGEpUyoFsZv48tyNgzPjCWVUrATY,1941
52
+ hardpy/pytest_hardpy/reporter/hook_reporter.py,sha256=TE6IzQVH10ce8JXWtep80kgyVOicU02C75FDpV6NfSQ,9895
53
53
  hardpy/pytest_hardpy/reporter/runner_reporter.py,sha256=NXkBIoERqmLI-GYtHavmOWC5t6NIpcAE-NECrUKIAJs,827
54
54
  hardpy/pytest_hardpy/result/__init__.py,sha256=NMeCGx3yh8ds9VpaUpuNFDxbwgYFq3e-o7W6rYIv8uI,346
55
55
  hardpy/pytest_hardpy/result/couchdb_config.py,sha256=QZryfA2QoHIjzbVT3OAD76DCNppCghtRWdZMZ5v7KhY,611
@@ -61,11 +61,11 @@ hardpy/pytest_hardpy/utils/__init__.py,sha256=IeOr27pgzvMolQtEXJxODJKNdQAFww8Ejj
61
61
  hardpy/pytest_hardpy/utils/config_data.py,sha256=F8khHsvkEsJjDnoHeLjI0rgsAfETN7nSlEP2snf2kio,990
62
62
  hardpy/pytest_hardpy/utils/const.py,sha256=rjW1Rzhe2vCr8GeQqeN_pafepGDYhjhY4u1VfTOVI6U,625
63
63
  hardpy/pytest_hardpy/utils/exception.py,sha256=5GnVkOchSPDEXaOXaruO0YzKXoY7b3Y5mVU5-51ZKRg,457
64
- hardpy/pytest_hardpy/utils/node_info.py,sha256=9aK8X-lNrY3unjHAR-DBS6GFKyo7rEoDH-CTy_WGdFw,1437
64
+ hardpy/pytest_hardpy/utils/node_info.py,sha256=VnEbhKBNAL5xpuFtJTCg90TmkjkFCQA59F5W2RcOlx4,3157
65
65
  hardpy/pytest_hardpy/utils/progress_calculator.py,sha256=r0qb3p6_yDIyLeCshF3Ceo5pCzd3BoTahL4rCD2oMNw,1041
66
66
  hardpy/pytest_hardpy/utils/singleton.py,sha256=C8cgRDydnG2b5dcN1LCLw4aM-AUMAvJc1W39mTkNWlQ,614
67
- hardpy-0.1.0.dist-info/METADATA,sha256=fAfyR3m3ZccxR-F_HCjTQS6VKXjPMtX1kp3ucB9dPDo,3393
68
- hardpy-0.1.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
69
- hardpy-0.1.0.dist-info/entry_points.txt,sha256=q73g5GfznSUpjkayi0SV4uaAtrf7D-7rmDoWoEZmZe0,120
70
- hardpy-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
- hardpy-0.1.0.dist-info/RECORD,,
67
+ hardpy-0.3.0.dist-info/METADATA,sha256=lWSZtZSy7b_PISotSgTw3lyM1X2uJPj0dVmOfI2A9N8,4096
68
+ hardpy-0.3.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
69
+ hardpy-0.3.0.dist-info/entry_points.txt,sha256=q73g5GfznSUpjkayi0SV4uaAtrf7D-7rmDoWoEZmZe0,120
70
+ hardpy-0.3.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
+ hardpy-0.3.0.dist-info/RECORD,,
File without changes