hardpy 0.11.2__py3-none-any.whl → 0.12.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 (55) hide show
  1. hardpy/__init__.py +6 -0
  2. hardpy/cli/cli.py +7 -0
  3. hardpy/cli/template.py +0 -4
  4. hardpy/hardpy_panel/frontend/dist/assets/allPaths-B26356fZ.js +1 -0
  5. hardpy/hardpy_panel/frontend/dist/assets/allPathsLoader-0BeGWuiy.js +2 -0
  6. hardpy/hardpy_panel/frontend/dist/assets/index-BMEat_ws.js +1 -0
  7. hardpy/hardpy_panel/frontend/dist/assets/index-Bl_IX0Up.js +790 -0
  8. hardpy/hardpy_panel/frontend/dist/assets/index-BwCQzehg.css +1 -0
  9. hardpy/hardpy_panel/frontend/dist/assets/index-xb4M2ucX.js +1 -0
  10. hardpy/hardpy_panel/frontend/dist/assets/splitPathsBySizeLoader-BEs5IL5-.js +1 -0
  11. hardpy/hardpy_panel/frontend/dist/index.html +45 -1
  12. hardpy/pytest_hardpy/db/base_store.py +2 -0
  13. hardpy/pytest_hardpy/db/const.py +1 -0
  14. hardpy/pytest_hardpy/db/schema/v1.py +7 -3
  15. hardpy/pytest_hardpy/plugin.py +46 -43
  16. hardpy/pytest_hardpy/pytest_call.py +25 -0
  17. hardpy/pytest_hardpy/result/report_reader/stand_cloud_reader.py +26 -2
  18. hardpy/pytest_hardpy/utils/__init__.py +4 -0
  19. hardpy/pytest_hardpy/utils/exception.py +14 -0
  20. hardpy/pytest_hardpy/utils/node_info.py +58 -13
  21. {hardpy-0.11.2.dist-info → hardpy-0.12.0.dist-info}/METADATA +3 -1
  22. hardpy-0.12.0.dist-info/RECORD +76 -0
  23. hardpy/hardpy_panel/frontend/dist/asset-manifest.json +0 -36
  24. hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css +0 -2
  25. hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css.map +0 -1
  26. hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js +0 -2
  27. hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js.map +0 -1
  28. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js +0 -2
  29. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js.map +0 -1
  30. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js +0 -2
  31. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js.map +0 -1
  32. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js +0 -2
  33. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map +0 -1
  34. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js +0 -2
  35. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js.map +0 -1
  36. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js +0 -2
  37. hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map +0 -1
  38. hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js +0 -3
  39. hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.LICENSE.txt +0 -90
  40. hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.map +0 -1
  41. hardpy-0.11.2.dist-info/RECORD +0 -87
  42. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.e02ecf515378db143652.ttf → assets/blueprint-icons-16-Bfs1BwbR.ttf} +0 -0
  43. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.84db1772f4bfb529f64f.woff → assets/blueprint-icons-16-Btb8d-Hu.woff} +0 -0
  44. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.5c52b39c697f2323ce8b.svg → assets/blueprint-icons-16-CzsyEoPG.svg} +0 -0
  45. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.b67ee1736e20e37a3225.woff2 → assets/blueprint-icons-16-DrH54W_x.woff2} +0 -0
  46. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.520846c6beb41df528c8.eot → assets/blueprint-icons-16-RCDSkC4W.eot} +0 -0
  47. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.429cacb8accf72488451.ttf → assets/blueprint-icons-20-BGGGsqDJ.ttf} +0 -0
  48. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.8cecf62de42997e4d82f.woff2 → assets/blueprint-icons-20-D9WO2FSG.woff2} +0 -0
  49. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.afbadb627d43b7857223.eot → assets/blueprint-icons-20-Doom1bSH.eot} +0 -0
  50. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.6ae3791ee2d86fc228a6.svg → assets/blueprint-icons-20-DyVnGNfQ.svg} +0 -0
  51. /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff → assets/blueprint-icons-20-ZW-9JnPf.woff} +0 -0
  52. /hardpy/hardpy_panel/frontend/dist/{static/media/logo_smol.5b16f92447a4a9e80331.png → assets/logo_smol-CK3jE85c.png} +0 -0
  53. {hardpy-0.11.2.dist-info → hardpy-0.12.0.dist-info}/WHEEL +0 -0
  54. {hardpy-0.11.2.dist-info → hardpy-0.12.0.dist-info}/entry_points.txt +0 -0
  55. {hardpy-0.11.2.dist-info → hardpy-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1,45 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>HardPy Operator Panel</title><script defer="defer" src="/static/js/main.fb8b84a3.js"></script><link href="/static/css/main.e8a862f1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta
9
+ name="description"
10
+ content="Web site created using create-react-app"
11
+ />
12
+ <link rel="apple-touch-icon" href="/logo192.png" />
13
+ <!--
14
+ manifest.json provides metadata used when your web app is installed on a
15
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
+ -->
17
+ <link rel="manifest" href="/manifest.json" />
18
+ <!--
19
+ Notice the use of %PUBLIC_URL% in the tags above.
20
+ It will be replaced with the URL of the `public` folder during the build.
21
+ Only files inside the `public` folder can be referenced from the HTML.
22
+
23
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24
+ work correctly both with client-side routing and a non-root public URL.
25
+ Learn how to configure a non-root public URL by running `npm run build`.
26
+ -->
27
+ <title>HardPy Operator Panel</title>
28
+ <script type="module" crossorigin src="/assets/index-Bl_IX0Up.js"></script>
29
+ <link rel="stylesheet" crossorigin href="/assets/index-BwCQzehg.css">
30
+ </head>
31
+ <body>
32
+ <noscript>You need to enable JavaScript to run this app.</noscript>
33
+ <div id="root"></div>
34
+ <!--
35
+ This HTML file is a template.
36
+ If you open it directly in the browser, you will see an empty page.
37
+
38
+ You can add webfonts, meta tags, or analytics to this file.
39
+ The build step will place the bundled scripts into the <body> tag.
40
+
41
+ To begin the development, run `npm start` or `yarn start`.
42
+ To create a production bundle, use `npm run build` or `yarn build`.
43
+ -->
44
+ </body>
45
+ </html>
@@ -100,6 +100,7 @@ class BaseStore(BaseConnector):
100
100
  DF.NAME: None,
101
101
  DF.TIMEZONE: None,
102
102
  DF.LOCATION: None,
103
+ DF.NUMBER: None,
103
104
  DF.DRIVERS: {},
104
105
  DF.INFO: {},
105
106
  },
@@ -120,6 +121,7 @@ class BaseStore(BaseConnector):
120
121
  DF.NAME: None,
121
122
  DF.TIMEZONE: None,
122
123
  DF.LOCATION: None,
124
+ DF.NUMBER: None,
123
125
  DF.DRIVERS: {},
124
126
  DF.INFO: {},
125
127
  }
@@ -38,3 +38,4 @@ class DatabaseField(str, Enum):
38
38
  ALERT = "alert"
39
39
  OPERATOR_DATA = "operator_data"
40
40
  DIALOG = "dialog"
41
+ NUMBER = "number"
@@ -188,7 +188,8 @@ class TestStand(BaseModel):
188
188
  "port": 8000
189
189
  }
190
190
  },
191
- "location": "Belgrade_1"
191
+ "location": "Belgrade_1",
192
+ "number": 2
192
193
  }
193
194
  }
194
195
  ```
@@ -202,6 +203,7 @@ class TestStand(BaseModel):
202
203
  drivers: dict = {}
203
204
  info: dict = {}
204
205
  location: str | None = None
206
+ number: int | None = None
205
207
 
206
208
 
207
209
  class OperatorData(BaseModel):
@@ -261,7 +263,8 @@ class ResultStateStore(IBaseResult):
261
263
  "port": 8000
262
264
  }
263
265
  },
264
- "location": "Belgrade_1"
266
+ "location": "Belgrade_1",
267
+ "number": 2
265
268
  },
266
269
  "operator_msg": {
267
270
  "msg": "Operator message",
@@ -363,7 +366,8 @@ class ResultRunStore(IBaseResult):
363
366
  "port": 8000
364
367
  }
365
368
  },
366
- "location": "Belgrade_1"
369
+ "location": "Belgrade_1",
370
+ "number": 2
367
371
  },
368
372
  "artifact": {},
369
373
  "modules": {
@@ -47,6 +47,7 @@ if __debug__:
47
47
 
48
48
  disable_warnings(InsecureRequestWarning)
49
49
 
50
+
50
51
  def pytest_addoption(parser: Parser) -> None:
51
52
  """Register argparse-style options."""
52
53
  con_data = ConnectionData()
@@ -123,6 +124,7 @@ class HardpyPlugin:
123
124
  self._post_run_functions: list[Callable] = []
124
125
  self._dependencies = {}
125
126
  self._tests_name: str = ""
127
+ self._is_critical_not_passed = False
126
128
 
127
129
  if system() == "Linux":
128
130
  signal.signal(signal.SIGTERM, self._stop_handler)
@@ -160,12 +162,13 @@ class HardpyPlugin:
160
162
  config.addinivalue_line("markers", "module_name")
161
163
  config.addinivalue_line("markers", "dependency")
162
164
  config.addinivalue_line("markers", "attempt")
165
+ config.addinivalue_line("markers", "critical")
163
166
 
164
167
  # must be init after config data is set
165
168
  try:
166
169
  self._reporter = HookReporter(bool(is_clear_database))
167
170
  except RuntimeError as exc:
168
- exit(str(exc), 1)
171
+ exit(str(exc), ExitCode.INTERNAL_ERROR)
169
172
 
170
173
  def pytest_sessionfinish(self, session: Session, exitstatus: int) -> None:
171
174
  """Call at the end of test session."""
@@ -206,7 +209,7 @@ class HardpyPlugin:
206
209
  node_info = NodeInfo(item)
207
210
  except ValueError as exc:
208
211
  error_msg = f"Error creating NodeInfo for item: {item}. {exc}"
209
- exit(error_msg, 1)
212
+ exit(error_msg, ExitCode.NO_TESTS_COLLECTED)
210
213
 
211
214
  self._init_case_result(node_info.module_id, node_info.case_id)
212
215
  if node_info.module_id not in nodes:
@@ -240,7 +243,7 @@ class HardpyPlugin:
240
243
  except StandCloudError as exc:
241
244
  msg = str(exc)
242
245
  self._reporter.set_alert(msg)
243
- exit(msg)
246
+ exit(msg, ExitCode.INTERNAL_ERROR)
244
247
  try:
245
248
  sc_connector.healthcheck()
246
249
  except Exception: # noqa: BLE001
@@ -251,7 +254,7 @@ class HardpyPlugin:
251
254
  )
252
255
  self._reporter.set_alert(msg)
253
256
  self._reporter.update_db_by_doc()
254
- exit(msg)
257
+ exit(msg, ExitCode.INTERNAL_ERROR)
255
258
 
256
259
  # testrun entrypoint
257
260
  self._reporter.start()
@@ -267,7 +270,7 @@ class HardpyPlugin:
267
270
  node_info = NodeInfo(item)
268
271
 
269
272
  status = TestStatus.RUN
270
- is_skip_test = self._is_skip_test(node_info)
273
+ is_skip_test = self._is_critical_not_passed or self._is_skip_test(node_info)
271
274
  self._reporter.set_module_start_time(node_info.module_id)
272
275
  if not is_skip_test:
273
276
  self._reporter.set_case_start_time(node_info.module_id, node_info.case_id)
@@ -287,11 +290,7 @@ class HardpyPlugin:
287
290
  def pytest_runtest_call(self, item: Item) -> None:
288
291
  """Call the test item."""
289
292
  node_info = NodeInfo(item)
290
- self._reporter.set_case_attempt(
291
- node_info.module_id,
292
- node_info.case_id,
293
- 1,
294
- )
293
+ self._reporter.set_case_attempt(node_info.module_id, node_info.case_id, 1)
295
294
  self._reporter.update_db_by_doc()
296
295
 
297
296
  def pytest_runtest_makereport(self, item: Item, call: CallInfo) -> None:
@@ -304,6 +303,9 @@ class HardpyPlugin:
304
303
  module_id = node_info.module_id
305
304
  case_id = node_info.case_id
306
305
 
306
+ if node_info.critical:
307
+ self._is_critical_not_passed = True
308
+
307
309
  # first attempt was in pytest_runtest_call
308
310
  for current_attempt in range(2, attempt + 1):
309
311
  # add pause between attempts to verify STOP condition
@@ -317,6 +319,7 @@ class HardpyPlugin:
317
319
  try:
318
320
  item.runtest()
319
321
  call.excinfo = None
322
+ self._is_critical_not_passed = False
320
323
  self._reporter.set_case_status(module_id, case_id, TestStatus.PASSED)
321
324
  break
322
325
  except AssertionError:
@@ -340,11 +343,7 @@ class HardpyPlugin:
340
343
  module_id = Path(report.fspath).stem
341
344
  case_id = report.nodeid.rpartition("::")[2]
342
345
 
343
- self._reporter.set_case_status(
344
- module_id,
345
- case_id,
346
- TestStatus(report.outcome),
347
- )
346
+ self._reporter.set_case_status(module_id, case_id, TestStatus(report.outcome))
348
347
  # update case stop_time in non-skipped tests or user-skipped tests
349
348
  if report.skipped is False or is_skipped_by_plugin is False:
350
349
  self._reporter.set_case_stop_time(module_id, case_id)
@@ -376,7 +375,7 @@ class HardpyPlugin:
376
375
  # Not hooks
377
376
 
378
377
  def _stop_handler(self, signum: int, frame: Any) -> None: # noqa: ANN401, ARG002
379
- exit("Tests stopped by user")
378
+ exit("Tests stopped by user", ExitCode.INTERRUPTED)
380
379
 
381
380
  def _init_case_result(self, module_id: str, case_id: str) -> None:
382
381
  if self._results.get(module_id) is None:
@@ -482,36 +481,40 @@ class HardpyPlugin:
482
481
 
483
482
  def _is_skip_test(self, node_info: NodeInfo) -> bool:
484
483
  """Is need to skip a test because it depends on another test."""
485
- is_dependency_test_exist = self._dependencies.get(
484
+ dependency_tests = self._dependencies.get(
486
485
  TestDependencyInfo(node_info.module_id, node_info.case_id),
487
486
  )
488
- is_dependency_test_failed = False
489
- if is_dependency_test_exist:
487
+ is_skip = False
488
+ if dependency_tests:
490
489
  wrong_status = {TestStatus.FAILED, TestStatus.SKIPPED, TestStatus.ERROR}
491
- module_id, case_id = is_dependency_test_exist
492
- if case_id is not None:
493
- is_dependency_test_failed = (
494
- self._results[module_id][case_id] in wrong_status
495
- )
496
- else:
497
- result_set = set(self._results[module_id].values())
498
- is_dependency_test_failed = any(
499
- status in wrong_status for status in result_set
500
- )
501
- return bool(is_dependency_test_exist and is_dependency_test_failed)
490
+ for dependency_test in dependency_tests:
491
+ module_id, case_id = dependency_test
492
+ module_data = self._results[module_id]
493
+ # case result is the reason for the skipping
494
+ if case_id is not None and module_data[case_id] in wrong_status: # noqa: SIM114
495
+ is_skip = True
496
+ break
497
+ # module result is the reason for the skipping
498
+ elif case_id is None and module_data["module_status"] in wrong_status: # noqa: RET508
499
+ is_skip = True
500
+ break
501
+ if is_skip and node_info.critical is True:
502
+ self._is_critical_not_passed = True
503
+ return is_skip
502
504
 
503
505
  def _add_dependency(self, node_info: NodeInfo, nodes: dict) -> None:
504
- dependency = node_info.dependency
505
- if dependency is None or dependency == "":
506
+ dependencies = node_info.dependency
507
+ if dependencies is None:
506
508
  return
507
- module_id, case_id = dependency
508
- if module_id not in nodes:
509
- error_message = f"Error: Module dependency '{dependency}' not found."
510
- exit(error_message, 1)
511
- elif case_id not in nodes[module_id] and case_id is not None:
512
- error_message = f"Error: Case dependency '{dependency}' not found."
513
- exit(error_message, 1)
514
-
515
- self._dependencies[
516
- TestDependencyInfo(node_info.module_id, node_info.case_id)
517
- ] = dependency
509
+ for dependency in dependencies:
510
+ module_id, case_id = dependency
511
+ # incorrect module id in dependency
512
+ if module_id not in nodes:
513
+ continue
514
+ # incorrect case id in dependency
515
+ if case_id not in nodes[module_id] and case_id is not None:
516
+ continue
517
+ test_key = TestDependencyInfo(node_info.module_id, node_info.case_id)
518
+ if test_key not in self._dependencies:
519
+ self._dependencies[test_key] = set()
520
+ self._dependencies[test_key].add(dependency)
@@ -23,8 +23,10 @@ from hardpy.pytest_hardpy.utils import (
23
23
  DuplicateSerialNumberError,
24
24
  DuplicateTestStandLocationError,
25
25
  DuplicateTestStandNameError,
26
+ DuplicateTestStandNumberError,
26
27
  HTMLComponent,
27
28
  ImageComponent,
29
+ TestStandNumberError,
28
30
  )
29
31
 
30
32
 
@@ -138,6 +140,9 @@ def set_stand_location(location: str) -> None:
138
140
 
139
141
  Args:
140
142
  location (str): test stand location
143
+
144
+ Raises:
145
+ DuplicateTestStandLocationError: if test stand location is already set
141
146
  """
142
147
  reporter = RunnerReporter()
143
148
  key = reporter.generate_key(DF.TEST_STAND, DF.LOCATION)
@@ -147,6 +152,26 @@ def set_stand_location(location: str) -> None:
147
152
  reporter.update_db_by_doc()
148
153
 
149
154
 
155
+ def set_stand_number(number: int) -> None:
156
+ """Add test stand number to document.
157
+
158
+ Args:
159
+ number (int): test stand number (non negative integer)
160
+
161
+ Raises:
162
+ DuplicateTestStandNumberError: if stand number is already set
163
+ TestStandNumberError: if stand number is incorrect (negative or non integer)
164
+ """
165
+ reporter = RunnerReporter()
166
+ key = reporter.generate_key(DF.TEST_STAND, DF.NUMBER)
167
+ if not isinstance(number, int) or number < 0:
168
+ raise TestStandNumberError
169
+ if reporter.get_field(key):
170
+ raise DuplicateTestStandNumberError
171
+ reporter.set_doc_value(key, number)
172
+ reporter.update_db_by_doc()
173
+
174
+
150
175
  def set_message(msg: str, msg_key: str | None = None) -> None:
151
176
  """Add or update message in current test.
152
177
 
@@ -36,17 +36,41 @@ class StandCloudReader:
36
36
  self._verify_ssl = not __debug__
37
37
  self._sc_connector = sc_connector
38
38
 
39
- def test_run(self, run_id: str) -> Response:
39
+ def request(self, endpoint: str, params: dict[str, Any] | None = None) -> Response:
40
+ """Get data from endpoint.
41
+
42
+ Args:
43
+ endpoint (str): endpoint address.
44
+ params (dict[str, Any] | None, optional): endpoint parameters.
45
+ Defaults to None.
46
+
47
+ Returns:
48
+ Response: endpoint data.
49
+ """
50
+ return self._request(endpoint=endpoint, params=params)
51
+
52
+ def test_run(
53
+ self,
54
+ run_id: str | None = None,
55
+ params: dict[str, Any] | None = None,
56
+ ) -> Response:
40
57
  """Get run data from '/test_run' endpoint.
41
58
 
42
59
  Args:
43
60
  run_id (str): UUIDv4 test run identifier.
44
61
  Example: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
62
+ params (dict[str, Any] | None, optional): test_run parameters.
63
+ Defaults to None.
45
64
 
46
65
  Returns:
47
66
  Response: test run data.
48
67
  """
49
- return self._request(endpoint=f"test_run/{run_id}")
68
+ if run_id is not None and params is not None:
69
+ msg = "run_id and params are mutually exclusive"
70
+ raise ValueError(msg)
71
+ if run_id is not None:
72
+ return self._request(endpoint=f"test_run/{run_id}")
73
+ return self._request(endpoint="test_run", params=params)
50
74
 
51
75
  def tested_dut(self, params: dict[str, Any]) -> Response:
52
76
  """Get tested DUT's data from '/tested_dut' endpoint.
@@ -20,7 +20,9 @@ from hardpy.pytest_hardpy.utils.exception import (
20
20
  DuplicateSerialNumberError,
21
21
  DuplicateTestStandLocationError,
22
22
  DuplicateTestStandNameError,
23
+ DuplicateTestStandNumberError,
23
24
  ImageError,
25
+ TestStandNumberError,
24
26
  WidgetInfoError,
25
27
  )
26
28
  from hardpy.pytest_hardpy.utils.machineid import machine_id
@@ -37,6 +39,7 @@ __all__ = [
37
39
  "DuplicateSerialNumberError",
38
40
  "DuplicateTestStandLocationError",
39
41
  "DuplicateTestStandNameError",
42
+ "DuplicateTestStandNumberError",
40
43
  "HTMLComponent",
41
44
  "ImageComponent",
42
45
  "ImageError",
@@ -47,6 +50,7 @@ __all__ = [
47
50
  "RadiobuttonWidget",
48
51
  "SingletonMeta",
49
52
  "StepWidget",
53
+ "TestStandNumberError",
50
54
  "TestStatus",
51
55
  "TextInputWidget",
52
56
  "WidgetInfoError",
@@ -37,6 +37,20 @@ class DuplicateTestStandLocationError(HardpyError):
37
37
  super().__init__(self.__doc__) # type: ignore
38
38
 
39
39
 
40
+ class DuplicateTestStandNumberError(HardpyError):
41
+ """The test stand number has already been determined."""
42
+
43
+ def __init__(self) -> None:
44
+ super().__init__(self.__doc__) # type: ignore
45
+
46
+
47
+ class TestStandNumberError(HardpyError):
48
+ """The test stand number is in the incorrect format."""
49
+
50
+ def __init__(self) -> None:
51
+ super().__init__(self.__doc__) # type: ignore
52
+
53
+
40
54
  class WidgetInfoError(HardpyError):
41
55
  """The widget info is not correct."""
42
56
 
@@ -43,6 +43,8 @@ class NodeInfo:
43
43
 
44
44
  self._attempt = self._get_attempt(item.own_markers)
45
45
 
46
+ self._critical = self._get_critical(item.own_markers + item.parent.own_markers)
47
+
46
48
  self._module_id = Path(item.parent.nodeid).stem # type: ignore
47
49
  self._case_id = item.name
48
50
 
@@ -83,11 +85,11 @@ class NodeInfo:
83
85
  return self._case_name
84
86
 
85
87
  @property
86
- def dependency(self) -> TestDependencyInfo | str:
88
+ def dependency(self) -> list[TestDependencyInfo] | None:
87
89
  """Get dependency information.
88
90
 
89
91
  Returns:
90
- TestDependencyInfo | str: Parsed dependency information.
92
+ list[TestDependencyInfo] | None: Dependency information
91
93
  """
92
94
  return self._dependency
93
95
 
@@ -100,6 +102,15 @@ class NodeInfo:
100
102
  """
101
103
  return self._attempt
102
104
 
105
+ @property
106
+ def critical(self) -> bool:
107
+ """Get critical status.
108
+
109
+ Returns:
110
+ bool: critical status
111
+ """
112
+ return self._critical
113
+
103
114
  def _get_human_name(self, markers: list[Mark], marker_name: str) -> str:
104
115
  """Get human name from markers.
105
116
 
@@ -115,7 +126,25 @@ class NodeInfo:
115
126
  return marker.args[0]
116
127
  return ""
117
128
 
118
- def _get_dependency_info(self, markers: list[Mark]) -> TestDependencyInfo | str:
129
+ def _get_human_names(self, markers: list[Mark], marker_name: str) -> list[str]:
130
+ """Get human names from markers.
131
+
132
+ Args:
133
+ markers (list[Mark]): item markers list
134
+ marker_name (str): marker name
135
+
136
+ Returns:
137
+ list[str]: human names by user
138
+ """
139
+ names = []
140
+ for marker in markers:
141
+ if marker.name == marker_name and marker.args:
142
+ names.append(marker.args[0]) # noqa: PERF401
143
+ return names
144
+
145
+ def _get_dependency_info(
146
+ self, markers: list[Mark],
147
+ ) -> list[TestDependencyInfo] | None:
119
148
  """Extract and parse dependency information.
120
149
 
121
150
  Args:
@@ -123,17 +152,22 @@ class NodeInfo:
123
152
  marker_name (str): marker name
124
153
 
125
154
  Returns:
126
- TestDependencyInfo | str | None: Parsed dependency information.
155
+ list[TestDependencyInfo] | None: Dependency information
127
156
  """
128
- dependency_value = self._get_human_name(markers, "dependency")
129
- dependency_data = re.search(r"(\w+)::(\w+)", dependency_value)
130
- if dependency_data:
131
- return TestDependencyInfo(*dependency_data.groups())
132
- elif re.search(r"^\w+$", dependency_value): # noqa: RET505
133
- return TestDependencyInfo(dependency_value, None)
134
- elif dependency_data is None and dependency_value == "":
135
- return ""
136
- raise ValueError
157
+ dependency_value = self._get_human_names(markers, "dependency")
158
+ dependencies = []
159
+ for dependency in dependency_value:
160
+ dependency_data = re.search(r"(\w+)::(\w+)", dependency)
161
+ if dependency_data:
162
+ dependencies.append(TestDependencyInfo(*dependency_data.groups()))
163
+ elif re.search(r"^\w+$", dependency):
164
+ dependencies.append(TestDependencyInfo(dependency, None))
165
+ elif not dependency:
166
+ continue
167
+ else:
168
+ error_msg = f"Invalid dependency format: {dependency}"
169
+ raise ValueError(error_msg)
170
+ return dependencies if dependencies else None
137
171
 
138
172
  def _get_attempt(self, markers: list[Mark]) -> int:
139
173
  """Get the number of attempts.
@@ -152,3 +186,14 @@ class NodeInfo:
152
186
  msg = "The 'attempt' marker value must be a positive integer."
153
187
  raise ValueError(msg)
154
188
  return attempt
189
+
190
+ def _get_critical(self, markers: list[Mark]) -> bool:
191
+ """Check if test or module is marked as critical.
192
+
193
+ Args:
194
+ markers (list[Mark]): item markers list
195
+
196
+ Returns:
197
+ bool: True if test or module is critical, False otherwise
198
+ """
199
+ return any(marker.name == "critical" for marker in markers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hardpy
3
- Version: 0.11.2
3
+ Version: 0.12.0
4
4
  Summary: HardPy library for device testing
5
5
  Project-URL: Homepage, https://github.com/everypinio/hardpy/
6
6
  Project-URL: Documentation, https://everypinio.github.io/hardpy/
@@ -48,6 +48,8 @@ Provides-Extra: dev
48
48
  Requires-Dist: mypy>=1.11.0; extra == 'dev'
49
49
  Requires-Dist: ruff==0.8.0; extra == 'dev'
50
50
  Requires-Dist: wemake-python-styleguide>=0.19.2; extra == 'dev'
51
+ Provides-Extra: tests
52
+ Requires-Dist: psutil~=7.0.0; extra == 'tests'
51
53
  Description-Content-Type: text/markdown
52
54
 
53
55
  <h1 align="center">
@@ -0,0 +1,76 @@
1
+ hardpy/__init__.py,sha256=49-EDyH1KzK7kmuU9zFHAe8bUPfuayzcUIk3ksXiL7s,2237
2
+ hardpy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ hardpy/cli/cli.py,sha256=XSlfRd_tRoBEsZUrJXoMbs6VF40whcjixmzsbUrVfNo,9045
4
+ hardpy/cli/template.py,sha256=44phTqeKgFch5xdAJmDQ-za1mM1_z60izRVbmCQHU-8,6225
5
+ hardpy/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ hardpy/common/config.py,sha256=nA2r2FrdKl0e-OqVvoa-3KvQ26Af-b86j7RMhGsWCPE,4966
7
+ hardpy/common/stand_cloud/__init__.py,sha256=fezdiYAehtT2H-GAef-xZU12CbmCRe64XHA9UB3kJDU,456
8
+ hardpy/common/stand_cloud/connector.py,sha256=euoD6rG8hclol6SJ2629bcAIjj6UZdl9sEp6mlm_Kzw,7024
9
+ hardpy/common/stand_cloud/exception.py,sha256=eKkqu5ylDRIGN_yZhvz2xVGm49XmlZ8nryALgdRqpbY,287
10
+ hardpy/common/stand_cloud/oauth_callback.py,sha256=GkADOnQ46HwxKEdgG_4bS1xX0ybfdQqMK3us-dMuHVw,3322
11
+ hardpy/common/stand_cloud/registration.py,sha256=HxvTex-PxanfVMYt7jiOuxs2lSMt0-f5PbWBvbobvCU,6754
12
+ hardpy/common/stand_cloud/token_storage.py,sha256=aH3-BRefCR-CHd0La6wOEwWxBZY8wOkdXh8WE86vLMo,856
13
+ hardpy/hardpy_panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ hardpy/hardpy_panel/api.py,sha256=BRY_RuYKPo0e1WdVCPS7iA46GzLIc5A4hLPvtagKqRc,3533
15
+ hardpy/hardpy_panel/frontend/dist/favicon.ico,sha256=sgIk5PKUKEKBDpkSrc8dJgjpObp0iF82Mec0GpfKId4,15406
16
+ hardpy/hardpy_panel/frontend/dist/index.html,sha256=d2hl85yjg39vnovgIVFrahQXYvCXS0LFDL2j_M_ZRqI,1851
17
+ hardpy/hardpy_panel/frontend/dist/logo192.png,sha256=E4K7drvhJCg9HcTpRihOXZhVJVBZ7-W97Se-3tDb46o,14485
18
+ hardpy/hardpy_panel/frontend/dist/logo512.png,sha256=-fIMbqX7PYUpheK4kX1C1erRTe_hHZwFQYDLrAbhFRU,34188
19
+ hardpy/hardpy_panel/frontend/dist/manifest.json,sha256=PfmJlN2JMJtHS6OnhU4b4X5wPQC_yRBdjesjoirObSA,502
20
+ hardpy/hardpy_panel/frontend/dist/assets/allPaths-B26356fZ.js,sha256=CbXV61mb3jGcg6XKR_5-qFByVFdSwrD7etX1ivlI_jg,309
21
+ hardpy/hardpy_panel/frontend/dist/assets/allPathsLoader-0BeGWuiy.js,sha256=py8TcDy4MrJJYLL6q66zXdHFM7KEltoUvN5r7atd2rc,550
22
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-16-Bfs1BwbR.ttf,sha256=wA1ItrnCJli8ESQ1KwLR3egJZG55dKBbLPonqFIa0K0,117420
23
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-16-Btb8d-Hu.woff,sha256=edyqQN0nw4dNBs1pgr7pQB7nJhhR6T_YfklFcG_fHj0,53344
24
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-16-CzsyEoPG.svg,sha256=lDCQy06aS-9bmhwuFOUs-EdcR8MP2wqwAwky5oamtkQ,509417
25
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-16-DrH54W_x.woff2,sha256=B8GmKQn3syZejgghoYsm2txy6Jy6RpUD-7Bbv-u_JC8,41612
26
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-16-RCDSkC4W.eot,sha256=PTCTrQYNHX2hIPUaYWtOKrI30-iQGXt_EGxq6JCXie0,117628
27
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-20-BGGGsqDJ.ttf,sha256=WOcMDqEVBixNLV64eYwTLVRLx61HKOjLHkGXXeoqJPg,120472
28
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-20-D9WO2FSG.woff2,sha256=1k6PvzcKh53cY1fYJB_CfHFcw_fUfuTGOb_BpW9EGr8,42808
29
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-20-Doom1bSH.eot,sha256=RPan5eGrt9_bHe9FXYXVYsbOA4J-BgNweKZJNaVXf2I,120680
30
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-20-DyVnGNfQ.svg,sha256=BsTLtmUaqeiMSZwv2ysmTV4qWVv-qYbwsE0_sVUjJk0,546938
31
+ hardpy/hardpy_panel/frontend/dist/assets/blueprint-icons-20-ZW-9JnPf.woff,sha256=mQZTxE1PyyAL16VWuASOvXlZFwuI4aCPvbrhfgpdIdU,55356
32
+ hardpy/hardpy_panel/frontend/dist/assets/index-BMEat_ws.js,sha256=ZiKN4IGxolgim42tqk68UOPUA0g8ABojet1dj4Wru1s,254121
33
+ hardpy/hardpy_panel/frontend/dist/assets/index-Bl_IX0Up.js,sha256=eiRyh2G4k3lhsY2Xr5b6MvfYaMW0qKEOxw0VkZkZ8o4,984130
34
+ hardpy/hardpy_panel/frontend/dist/assets/index-BwCQzehg.css,sha256=uvaqKowYoduAxTwlw3ojcSjKqrMtu5XErb_T668JGdU,299275
35
+ hardpy/hardpy_panel/frontend/dist/assets/index-xb4M2ucX.js,sha256=eeaZrH0pRpkWViEVZ2Mt6f7VgpOxluRB7S8SovkFeTE,245762
36
+ hardpy/hardpy_panel/frontend/dist/assets/logo_smol-CK3jE85c.png,sha256=E4K7drvhJCg9HcTpRihOXZhVJVBZ7-W97Se-3tDb46o,14485
37
+ hardpy/hardpy_panel/frontend/dist/assets/splitPathsBySizeLoader-BEs5IL5-.js,sha256=19mgQwGFpV8yOhxvL02RtnpEye_5NIAeHC06_1NdY9E,472
38
+ hardpy/pytest_hardpy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ hardpy/pytest_hardpy/plugin.py,sha256=smyZM03ijUsAvCcqnbNJU2YyjEt8xqkKaw5uL3OXTg8,18732
40
+ hardpy/pytest_hardpy/pytest_call.py,sha256=NaJHZkaY85bG3gFVGHbIZPOAP761-iezRzUAjursLXA,13132
41
+ hardpy/pytest_hardpy/pytest_wrapper.py,sha256=_eULTqbJhFj0uu3XPplS4WSrI_LJC_ZX2kA5t5YJ49U,5162
42
+ hardpy/pytest_hardpy/db/__init__.py,sha256=G6y13JPh8HaH2O9E3_LTH_bTUVSgiezQFjDGaNIljec,557
43
+ hardpy/pytest_hardpy/db/base_connector.py,sha256=5a476F5LwvFUfQ4Yc0Q6biacULDrCk8UHPlpc6n0NRQ,1111
44
+ hardpy/pytest_hardpy/db/base_server.py,sha256=XqTff225iIymPYUGGEja9r9WOilVw7ljcAVp1M8VuAI,404
45
+ hardpy/pytest_hardpy/db/base_store.py,sha256=m-glO0EsHu0NYArVaKRXUIOE-wVwvenbFNACznYo1Tg,3806
46
+ hardpy/pytest_hardpy/db/const.py,sha256=AltZXf-h88sV29v4vYLuWk5ax-Tuv_rC7CeBxN0AMtA,992
47
+ hardpy/pytest_hardpy/db/runstore.py,sha256=tCXWo2AW0er3lbDcCqYbYxOBbINMZNtfnnjlIJpXmIA,949
48
+ hardpy/pytest_hardpy/db/statestore.py,sha256=0sv4AqzwW_J34O-cb7aN3zmgULIVtZRi_qg4XvC2_L0,586
49
+ hardpy/pytest_hardpy/db/schema/__init__.py,sha256=1S73W3PLQt8gX5Y33nbX1JdwLvnrtlKH4cElID3pwuc,263
50
+ hardpy/pytest_hardpy/db/schema/v1.py,sha256=lWE5jWh5ek43-i4aKO51lflNPoyyzaveV06PP5nKQ3Y,9897
51
+ hardpy/pytest_hardpy/reporter/__init__.py,sha256=rztpM2HlLUpMOvad0JHbZU4Mk8PDDQyCFXLhpLktGQI,322
52
+ hardpy/pytest_hardpy/reporter/base.py,sha256=IGVzKpOTN2uauhrUn3HWTyHhhMQFXAWcOvfo1EzDOkw,2707
53
+ hardpy/pytest_hardpy/reporter/hook_reporter.py,sha256=8cOlOgmLqDty4ib3p5gkZdi6ZAYqAFIjQhQ3Vm6AHGU,11952
54
+ hardpy/pytest_hardpy/reporter/runner_reporter.py,sha256=YsK8wrLIulsixePG6WNfC4MagpKfhP5j0CUaXkcfeL0,790
55
+ hardpy/pytest_hardpy/result/__init__.py,sha256=2afpuEuOcxYfIEOwWzsGZe960iQaPVCmsbYujijQg1s,592
56
+ hardpy/pytest_hardpy/result/couchdb_config.py,sha256=ujxyJYM2pdZzi3GZ2Zysbz2_ZeTRN5sQc8AGuzRJm_0,3243
57
+ hardpy/pytest_hardpy/result/report_loader/__init__.py,sha256=wq5Y-_JW2ExCRnQ9VVesKmTToEQrcTY5RxNJIWaT9ag,374
58
+ hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py,sha256=KcZ0JkCgWhrj2J9M04JBDy0fpqtpVEYtu9GCLDG27pU,2255
59
+ hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py,sha256=lRPDGIOFquo-KcCYxR6C4vnfe1m_dBWENFN8IkNZDPc,2217
60
+ hardpy/pytest_hardpy/result/report_reader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
+ hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py,sha256=GrROwfTVyJaVLPBxkvOM35HCksFEnWm0aVI8FibPikg,5911
62
+ hardpy/pytest_hardpy/result/report_reader/stand_cloud_reader.py,sha256=uT7YSBu1QyURH9IkgRCdpbinn8LKXUhgVEhwPmGZV7I,3636
63
+ hardpy/pytest_hardpy/utils/__init__.py,sha256=nvcT-9RRsRTxk1tP9qIua8rm_SskuuUfNGEkROtIn90,1637
64
+ hardpy/pytest_hardpy/utils/connection_data.py,sha256=Oq1LdIpmYkwakNCNwAPD-FTH4W7lj_v8vYkQCqJTof8,449
65
+ hardpy/pytest_hardpy/utils/const.py,sha256=RuzRmnpvmUylRbj8CxtaVbo7J9kp6rELvjPdfUzMQLU,407
66
+ hardpy/pytest_hardpy/utils/dialog_box.py,sha256=LNukQ7ukUzLUFmwwH6L6M8wWmF-Mo4HF-UpVkyf8nY8,11224
67
+ hardpy/pytest_hardpy/utils/exception.py,sha256=N5g4tIorQaT0I5mFqqj9qoigf4YUwy7Jvy58VHL6IxE,1773
68
+ hardpy/pytest_hardpy/utils/machineid.py,sha256=6JAzUt7KtjTYn8kL9hSMaCQ20U8liH-zDT9v-5Ch7Q8,296
69
+ hardpy/pytest_hardpy/utils/node_info.py,sha256=BibPo2ltxy-hIUQbaYA2om7x1LNK1JvQtHNC0EKl_9k,5474
70
+ hardpy/pytest_hardpy/utils/progress_calculator.py,sha256=TPl2gG0ZSvMe8otPythhF9hkD6fa6-mJAhy9yI83-yE,1071
71
+ hardpy/pytest_hardpy/utils/singleton.py,sha256=tjUGs48o_vBeVpRsEBZEOTCoCUikpIFmQ1c3rsfymso,948
72
+ hardpy-0.12.0.dist-info/METADATA,sha256=jUwQ1T4g6Cpvqae4ZCdFagy5g4kKD1zr5QTJ9eaeNbs,3978
73
+ hardpy-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
+ hardpy-0.12.0.dist-info/entry_points.txt,sha256=nL2sMkKMScNaOE0IPkYnu9Yr-BUswZvGSrwY-SxHY3E,102
75
+ hardpy-0.12.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
76
+ hardpy-0.12.0.dist-info/RECORD,,