pytest-embedded 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/PKG-INFO +1 -1
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/__init__.py +1 -1
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/dut_factory.py +13 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/plugin.py +64 -3
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/tests/test_base.py +29 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/LICENSE +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/README.md +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pyproject.toml +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/app.py +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/dut.py +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/log.py +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/unity.py +0 -0
- {pytest_embedded-2.2.0 → pytest_embedded-2.3.0}/pytest_embedded/utils.py +0 -0
|
@@ -838,3 +838,16 @@ class DutFactory:
|
|
|
838
838
|
_close_or_terminate(obj)
|
|
839
839
|
del layout
|
|
840
840
|
raise e
|
|
841
|
+
|
|
842
|
+
@classmethod
|
|
843
|
+
def get_all_duts(cls) -> list[Dut]:
|
|
844
|
+
"""Get all DUTs created by DutFactory."""
|
|
845
|
+
|
|
846
|
+
duts = []
|
|
847
|
+
for layout in cls.obj_stack:
|
|
848
|
+
# The DUT is always the last object in the layout
|
|
849
|
+
dut = layout[-1]
|
|
850
|
+
if isinstance(dut, Dut):
|
|
851
|
+
duts.append(dut)
|
|
852
|
+
|
|
853
|
+
return duts
|
|
@@ -153,6 +153,10 @@ def pytest_addoption(parser):
|
|
|
153
153
|
base_group.addoption(
|
|
154
154
|
'--logfile-extension', default='.log', help='set the extension format of the log files. (Default: ".log")'
|
|
155
155
|
)
|
|
156
|
+
base_group.addoption(
|
|
157
|
+
'--metric-path',
|
|
158
|
+
help='Path to openmetrics txt file to log metrics. (Default: None)',
|
|
159
|
+
)
|
|
156
160
|
|
|
157
161
|
serial_group = parser.getgroup('embedded-serial')
|
|
158
162
|
serial_group.addoption('--port', help='serial port. (Env: "ESPPORT" if service "esp" specified, Default: "None")')
|
|
@@ -634,6 +638,55 @@ def port_app_cache() -> dict[str, str]:
|
|
|
634
638
|
return {}
|
|
635
639
|
|
|
636
640
|
|
|
641
|
+
@pytest.fixture(scope='session')
|
|
642
|
+
def metric_path(request: FixtureRequest) -> str | None:
|
|
643
|
+
"""
|
|
644
|
+
Get the metric file path from the command line option.
|
|
645
|
+
|
|
646
|
+
:param request: pytest request object
|
|
647
|
+
:return: The path to the metric file, or None if not provided.
|
|
648
|
+
"""
|
|
649
|
+
return request.config.getoption('metric_path', None)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
@pytest.fixture(scope='session')
|
|
653
|
+
def log_metric(metric_path: str | None) -> t.Callable[..., None]:
|
|
654
|
+
"""
|
|
655
|
+
Provides a function to log metrics in OpenMetrics format.
|
|
656
|
+
|
|
657
|
+
The file is cleared at the beginning of the test session.
|
|
658
|
+
|
|
659
|
+
:param metric_path: Path to the metric file, from the ``--metric-path`` option.
|
|
660
|
+
:return: A function to log metrics, or a no-op function if the path is not provided.
|
|
661
|
+
"""
|
|
662
|
+
if not metric_path:
|
|
663
|
+
|
|
664
|
+
def no_op(key: str, value: t.Any, **kwargs: t.Any) -> None: # noqa: ARG001
|
|
665
|
+
warnings.warn('`--metric-path` is not specified, `log_metric` does nothing.')
|
|
666
|
+
|
|
667
|
+
return no_op
|
|
668
|
+
|
|
669
|
+
if os.path.exists(metric_path):
|
|
670
|
+
os.remove(metric_path)
|
|
671
|
+
elif os.path.dirname(metric_path):
|
|
672
|
+
os.makedirs(os.path.dirname(metric_path), exist_ok=True)
|
|
673
|
+
|
|
674
|
+
def _log_metric_impl(key: str, value: t.Any, **kwargs: t.Any) -> None:
|
|
675
|
+
labels = ''
|
|
676
|
+
if kwargs:
|
|
677
|
+
label_str = ','.join(f'{k}="{v}"' for k, v in kwargs.items())
|
|
678
|
+
labels = f'{{{label_str}}}'
|
|
679
|
+
|
|
680
|
+
line = f'{key}{labels} {value}\n'
|
|
681
|
+
|
|
682
|
+
lock = filelock.FileLock(f'{metric_path}.lock')
|
|
683
|
+
with lock:
|
|
684
|
+
with open(metric_path, 'a') as f:
|
|
685
|
+
f.write(line)
|
|
686
|
+
|
|
687
|
+
return _log_metric_impl
|
|
688
|
+
|
|
689
|
+
|
|
637
690
|
@pytest.fixture(scope='session', autouse=True)
|
|
638
691
|
def _mp_manager():
|
|
639
692
|
manager = MessageQueueManager()
|
|
@@ -1372,10 +1425,18 @@ class PytestEmbedded:
|
|
|
1372
1425
|
|
|
1373
1426
|
@pytest.hookimpl(trylast=True)
|
|
1374
1427
|
def pytest_runtest_call(self, item: Function):
|
|
1375
|
-
|
|
1428
|
+
all_duts: list[Dut] = []
|
|
1429
|
+
|
|
1430
|
+
# Check DUTs created by fixture
|
|
1376
1431
|
if 'dut' in item.funcargs:
|
|
1377
|
-
|
|
1378
|
-
|
|
1432
|
+
fixture_duts = [dut for dut in to_list(item.funcargs['dut']) if isinstance(dut, Dut)]
|
|
1433
|
+
all_duts.extend(fixture_duts)
|
|
1434
|
+
|
|
1435
|
+
# Check DUTs created by DutFactory
|
|
1436
|
+
factory_duts = DutFactory.get_all_duts()
|
|
1437
|
+
all_duts.extend(factory_duts)
|
|
1438
|
+
|
|
1439
|
+
self._raise_dut_failed_cases_if_exists(all_duts) # type: ignore
|
|
1379
1440
|
|
|
1380
1441
|
@pytest.hookimpl(trylast=True) # combine all possible junit reports should be the last step
|
|
1381
1442
|
def pytest_sessionfinish(self, session: Session, exitstatus: int) -> None:
|
|
@@ -792,3 +792,32 @@ class TestTargetMarkers:
|
|
|
792
792
|
|
|
793
793
|
result.assert_outcomes(passed=1)
|
|
794
794
|
assert 'Unknown pytest.mark.esp32 - is this a typo?' not in result.stdout.str()
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def test_log_metric_with_path(pytester):
|
|
798
|
+
metric_file = pytester.path / 'metrics.txt'
|
|
799
|
+
pytester.makepyfile("""
|
|
800
|
+
def test_metric(log_metric):
|
|
801
|
+
log_metric('my_metric', 123.45, label1='value1', target='esp32')
|
|
802
|
+
""")
|
|
803
|
+
|
|
804
|
+
result = pytester.runpytest(f'--metric-path={metric_file}')
|
|
805
|
+
result.assert_outcomes(passed=1)
|
|
806
|
+
|
|
807
|
+
with open(metric_file) as f:
|
|
808
|
+
content = f.read()
|
|
809
|
+
|
|
810
|
+
assert content == 'my_metric{label1="value1",target="esp32"} 123.45\n'
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def test_log_metric_without_path(pytester):
|
|
814
|
+
pytester.makepyfile("""
|
|
815
|
+
import pytest
|
|
816
|
+
|
|
817
|
+
def test_metric_no_path(log_metric):
|
|
818
|
+
with pytest.warns(UserWarning, match='`--metric-path` is not specified, `log_metric` does nothing.'):
|
|
819
|
+
log_metric('my_metric', 123.45)
|
|
820
|
+
""")
|
|
821
|
+
|
|
822
|
+
result = pytester.runpytest()
|
|
823
|
+
result.assert_outcomes(passed=1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|