hardpy 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hardpy/__init__.py +6 -0
- hardpy/cli/cli.py +7 -0
- hardpy/cli/template.py +0 -4
- hardpy/hardpy_panel/frontend/dist/assets/allPaths-B26356fZ.js +1 -0
- hardpy/hardpy_panel/frontend/dist/assets/allPathsLoader-0BeGWuiy.js +2 -0
- hardpy/hardpy_panel/frontend/dist/assets/index-BMEat_ws.js +1 -0
- hardpy/hardpy_panel/frontend/dist/assets/index-Bl_IX0Up.js +790 -0
- hardpy/hardpy_panel/frontend/dist/assets/index-BwCQzehg.css +1 -0
- hardpy/hardpy_panel/frontend/dist/assets/index-xb4M2ucX.js +1 -0
- hardpy/hardpy_panel/frontend/dist/assets/splitPathsBySizeLoader-BEs5IL5-.js +1 -0
- hardpy/hardpy_panel/frontend/dist/index.html +45 -1
- hardpy/pytest_hardpy/db/base_store.py +2 -0
- hardpy/pytest_hardpy/db/const.py +1 -0
- hardpy/pytest_hardpy/db/schema/v1.py +7 -3
- hardpy/pytest_hardpy/plugin.py +51 -43
- hardpy/pytest_hardpy/pytest_call.py +25 -0
- hardpy/pytest_hardpy/reporter/hook_reporter.py +18 -0
- hardpy/pytest_hardpy/result/report_reader/stand_cloud_reader.py +26 -2
- hardpy/pytest_hardpy/utils/__init__.py +4 -0
- hardpy/pytest_hardpy/utils/exception.py +14 -0
- hardpy/pytest_hardpy/utils/node_info.py +58 -13
- {hardpy-0.11.2.dist-info → hardpy-0.12.1.dist-info}/METADATA +3 -1
- hardpy-0.12.1.dist-info/RECORD +76 -0
- hardpy/hardpy_panel/frontend/dist/asset-manifest.json +0 -36
- hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css +0 -2
- hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js +0 -2
- hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map +0 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.LICENSE.txt +0 -90
- hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.map +0 -1
- hardpy-0.11.2.dist-info/RECORD +0 -87
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.e02ecf515378db143652.ttf → assets/blueprint-icons-16-Bfs1BwbR.ttf} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.84db1772f4bfb529f64f.woff → assets/blueprint-icons-16-Btb8d-Hu.woff} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.5c52b39c697f2323ce8b.svg → assets/blueprint-icons-16-CzsyEoPG.svg} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.b67ee1736e20e37a3225.woff2 → assets/blueprint-icons-16-DrH54W_x.woff2} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-16.520846c6beb41df528c8.eot → assets/blueprint-icons-16-RCDSkC4W.eot} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.429cacb8accf72488451.ttf → assets/blueprint-icons-20-BGGGsqDJ.ttf} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.8cecf62de42997e4d82f.woff2 → assets/blueprint-icons-20-D9WO2FSG.woff2} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.afbadb627d43b7857223.eot → assets/blueprint-icons-20-Doom1bSH.eot} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.6ae3791ee2d86fc228a6.svg → assets/blueprint-icons-20-DyVnGNfQ.svg} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff → assets/blueprint-icons-20-ZW-9JnPf.woff} +0 -0
- /hardpy/hardpy_panel/frontend/dist/{static/media/logo_smol.5b16f92447a4a9e80331.png → assets/logo_smol-CK3jE85c.png} +0 -0
- {hardpy-0.11.2.dist-info → hardpy-0.12.1.dist-info}/WHEEL +0 -0
- {hardpy-0.11.2.dist-info → hardpy-0.12.1.dist-info}/entry_points.txt +0 -0
- {hardpy-0.11.2.dist-info → hardpy-0.12.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1 +1,45 @@
|
|
|
1
|
-
<!
|
|
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
|
}
|
hardpy/pytest_hardpy/db/const.py
CHANGED
|
@@ -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": {
|
hardpy/pytest_hardpy/plugin.py
CHANGED
|
@@ -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),
|
|
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,
|
|
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:
|
|
@@ -425,6 +424,8 @@ class HardpyPlugin:
|
|
|
425
424
|
# update module statuses
|
|
426
425
|
self._results[module_id]["module_status"] = TestStatus.STOPPED
|
|
427
426
|
self._reporter.set_module_status(module_id, TestStatus.STOPPED)
|
|
427
|
+
if self._reporter.get_module_start_time(module_id):
|
|
428
|
+
self._reporter.set_module_stop_time(module_id)
|
|
428
429
|
|
|
429
430
|
# update case statuses
|
|
430
431
|
for module_key, module_value in module_data.items():
|
|
@@ -440,6 +441,9 @@ class HardpyPlugin:
|
|
|
440
441
|
case_id,
|
|
441
442
|
TestStatus.STOPPED,
|
|
442
443
|
)
|
|
444
|
+
if self._reporter.get_case_start_time(module_id, case_id):
|
|
445
|
+
self._reporter.set_case_stop_time(module_id, case_id)
|
|
446
|
+
|
|
443
447
|
self._reporter.update_db_by_doc()
|
|
444
448
|
|
|
445
449
|
def _decode_assertion_msg(
|
|
@@ -482,36 +486,40 @@ class HardpyPlugin:
|
|
|
482
486
|
|
|
483
487
|
def _is_skip_test(self, node_info: NodeInfo) -> bool:
|
|
484
488
|
"""Is need to skip a test because it depends on another test."""
|
|
485
|
-
|
|
489
|
+
dependency_tests = self._dependencies.get(
|
|
486
490
|
TestDependencyInfo(node_info.module_id, node_info.case_id),
|
|
487
491
|
)
|
|
488
|
-
|
|
489
|
-
if
|
|
492
|
+
is_skip = False
|
|
493
|
+
if dependency_tests:
|
|
490
494
|
wrong_status = {TestStatus.FAILED, TestStatus.SKIPPED, TestStatus.ERROR}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
495
|
+
for dependency_test in dependency_tests:
|
|
496
|
+
module_id, case_id = dependency_test
|
|
497
|
+
module_data = self._results[module_id]
|
|
498
|
+
# case result is the reason for the skipping
|
|
499
|
+
if case_id is not None and module_data[case_id] in wrong_status: # noqa: SIM114
|
|
500
|
+
is_skip = True
|
|
501
|
+
break
|
|
502
|
+
# module result is the reason for the skipping
|
|
503
|
+
elif case_id is None and module_data["module_status"] in wrong_status: # noqa: RET508
|
|
504
|
+
is_skip = True
|
|
505
|
+
break
|
|
506
|
+
if is_skip and node_info.critical is True:
|
|
507
|
+
self._is_critical_not_passed = True
|
|
508
|
+
return is_skip
|
|
502
509
|
|
|
503
510
|
def _add_dependency(self, node_info: NodeInfo, nodes: dict) -> None:
|
|
504
|
-
|
|
505
|
-
if
|
|
511
|
+
dependencies = node_info.dependency
|
|
512
|
+
if dependencies is None:
|
|
506
513
|
return
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
for dependency in dependencies:
|
|
515
|
+
module_id, case_id = dependency
|
|
516
|
+
# incorrect module id in dependency
|
|
517
|
+
if module_id not in nodes:
|
|
518
|
+
continue
|
|
519
|
+
# incorrect case id in dependency
|
|
520
|
+
if case_id not in nodes[module_id] and case_id is not None:
|
|
521
|
+
continue
|
|
522
|
+
test_key = TestDependencyInfo(node_info.module_id, node_info.case_id)
|
|
523
|
+
if test_key not in self._dependencies:
|
|
524
|
+
self._dependencies[test_key] = set()
|
|
525
|
+
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
|
|
|
@@ -202,6 +202,24 @@ class HookReporter(BaseReporter):
|
|
|
202
202
|
"""
|
|
203
203
|
self.set_doc_value(DF.ALERT, alert, statestore_only=True)
|
|
204
204
|
|
|
205
|
+
def get_module_start_time(self, module_id: str) -> int:
|
|
206
|
+
"""Get module start time.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
int: module time
|
|
210
|
+
"""
|
|
211
|
+
key = self.generate_key(DF.MODULES, module_id, DF.START_TIME)
|
|
212
|
+
return self._statestore.get_field(key)
|
|
213
|
+
|
|
214
|
+
def get_case_start_time(self, module_id: str, case_id: str) -> int:
|
|
215
|
+
"""Get case start time.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
int: module time
|
|
219
|
+
"""
|
|
220
|
+
key = self.generate_key(DF.MODULES, module_id, DF.CASES, case_id, DF.START_TIME)
|
|
221
|
+
return self._statestore.get_field(key)
|
|
222
|
+
|
|
205
223
|
def update_node_order(self, nodes: dict) -> None:
|
|
206
224
|
"""Update node order.
|
|
207
225
|
|
|
@@ -36,17 +36,41 @@ class StandCloudReader:
|
|
|
36
36
|
self._verify_ssl = not __debug__
|
|
37
37
|
self._sc_connector = sc_connector
|
|
38
38
|
|
|
39
|
-
def
|
|
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
|
-
|
|
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 |
|
|
88
|
+
def dependency(self) -> list[TestDependencyInfo] | None:
|
|
87
89
|
"""Get dependency information.
|
|
88
90
|
|
|
89
91
|
Returns:
|
|
90
|
-
TestDependencyInfo |
|
|
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
|
|
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 |
|
|
155
|
+
list[TestDependencyInfo] | None: Dependency information
|
|
127
156
|
"""
|
|
128
|
-
dependency_value = self.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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.
|
|
3
|
+
Version: 0.12.1
|
|
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">
|