Skip to content

Commit 956b526

Browse files
Merge branch 'master' into feature/add-tapo-support
2 parents c31d2dc + 1da3afa commit 956b526

File tree

4 files changed

+264
-179
lines changed

4 files changed

+264
-179
lines changed

doc/configuration.rst

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3760,6 +3760,51 @@ All the resources and drivers in this chapter have a YAML example snippet which
37603760
can simply be added (at the correct indentation level, one level deeper) to the
37613761
environment configuration.
37623762

3763+
See the :ref:`labgrid-device-config` man page for documentation on the
3764+
top-level ``options``, ``images``, ``tools``, and ``imports`` keys in the
3765+
environment configuration.
3766+
3767+
.. _environment-configuration-feature-flags:
3768+
3769+
Feature Flags
3770+
~~~~~~~~~~~~~
3771+
Similar targets or multi target environments may differ from each other in
3772+
certain small aspects, e.g. one device has a camera or screen connected, but
3773+
another one has not.
3774+
In labgrid's environment configs, such variations are described as feature flags.
3775+
3776+
Here's an example environment configuration for a target-scoped feature
3777+
``camera``:
3778+
3779+
.. code-block:: yaml
3780+
:name: feature-flag-env.yaml
3781+
3782+
targets:
3783+
main:
3784+
features:
3785+
- camera
3786+
resources: {}
3787+
drivers: {}
3788+
3789+
Features can not only be set per target, but also globally:
3790+
3791+
.. code-block:: yaml
3792+
:name: feature-flag-global-env.yaml
3793+
3794+
features:
3795+
- camera
3796+
targets:
3797+
main:
3798+
features:
3799+
- console
3800+
resources: {}
3801+
drivers: {}
3802+
3803+
See :ref:`usage_pytestplugin_mark_lg_feature` for how to make use of feature
3804+
flags in tests.
3805+
3806+
Multiple Drivers of the Same Type
3807+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37633808
If you want to use multiple drivers of the same type, the resources and drivers
37643809
need to be lists, e.g:
37653810

@@ -3812,6 +3857,8 @@ To bind the correct driver to the correct resource, explicit ``name`` and
38123857
The property name for the binding (e.g. ``port`` in the example above) is
38133858
documented for each individual driver in this chapter.
38143859

3860+
Templating the Environment Configuration
3861+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38153862
The YAML configuration file also supports templating for some substitutions,
38163863
these are:
38173864

@@ -3835,10 +3882,6 @@ would resolve the ``qemu_bin`` path relative to the ``BASE`` dir of the YAML
38353882
file and try to use the `RemotePlace`_ with the name set in the ``LG_PLACE``
38363883
environment variable.
38373884

3838-
See the :ref:`labgrid-device-config` man page for documentation on the
3839-
top-level ``options``, ``images``, ``tools``, and ``examples`` keys in the
3840-
environment configuration.
3841-
38423885
.. _exporter-configuration:
38433886

38443887
Exporter Configuration
@@ -3920,8 +3963,8 @@ to achieve the same effect:
39203963
match:
39213964
'@ID_PATH': 'pci-0000:05:00.0-usb-3-1.4'
39223965
3923-
Templating
3924-
~~~~~~~~~~
3966+
Templating the Exporter Configuration
3967+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39253968
To reduce the amount of repeated declarations when many similar resources
39263969
need to be exported, the `Jinja2 template engine <http://jinja.pocoo.org/>`_
39273970
is used as a preprocessor for the configuration file:

doc/usage.rst

Lines changed: 51 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,12 @@ own proxy, and only fallback to LG_PROXY.
481481

482482
See also :ref:`overview-proxy-mechanism`.
483483

484-
Simple Example
485-
~~~~~~~~~~~~~~
486484

485+
Writing and Running Tests
486+
~~~~~~~~~~~~~~~~~~~~~~~~~
487+
488+
Getting Started: A Minimal Test
489+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
487490
As a minimal example, we have a target connected via a USB serial converter
488491
('/dev/ttyUSB0') and booted to the Linux shell.
489492
The following environment config file (``shell-example.yaml``) describes how to
@@ -559,8 +562,8 @@ environment config:
559562
560563
pytest has automatically found the test case and executed it on the target.
561564

562-
Custom Fixture Example
563-
~~~~~~~~~~~~~~~~~~~~~~
565+
Reusing Setup Code with Custom Fixtures
566+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
564567
When writing many test cases which use the same driver, we can get rid of some
565568
common code by wrapping the :any:`CommandProtocol` in a fixture.
566569
As pytest always executes the ``conftest.py`` file in the test suite directory,
@@ -597,8 +600,8 @@ With this fixture, we can simplify the ``test_example.py`` file to:
597600

598601
... 1 passed...
599602

600-
Strategy Fixture Example
601-
~~~~~~~~~~~~~~~~~~~~~~~~
603+
Managing Target States with Strategy Fixtures
604+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
602605
When using a :any:`Strategy` to transition the target between states, it is
603606
useful to define a function scope fixture per state in ``conftest.py``:
604607

@@ -696,116 +699,74 @@ For this example, you should get a report similar to this:
696699
697700
========================== 3 passed in 29.77 seconds ===========================
698701
699-
Feature Flags
700-
~~~~~~~~~~~~~
701-
labgrid includes support for feature flags on a global and target scope.
702-
Adding a ``@pytest.mark.lg_feature`` decorator to a test ensures it is only
703-
executed if the desired feature is available:
702+
.. _usage_pytestplugin_mark_lg_feature:
703+
704+
@pytest.mark.lg_feature()
705+
~~~~~~~~~~~~~~~~~~~~~~~~~
706+
labgrid supports :ref:`environment-configuration-feature-flags` in the
707+
:ref:`environment-configuration`.
708+
Adding a ``@pytest.mark.lg_feature()`` decorator to a test ensures it is only
709+
executed if the desired feature is set, either under the target or global
710+
``features:`` keys.
704711

705712
.. code-block:: python
706-
:name: test_feature_flags.py
707713
708714
import pytest
709715
710716
@pytest.mark.lg_feature("camera")
711717
def test_camera(target):
712718
pass
713719
714-
Here's an example environment configuration:
715-
716-
.. code-block:: yaml
717-
:name: feature-flag-env.yaml
718-
719-
targets:
720-
main:
721-
features:
722-
- camera
723-
resources: {}
724-
drivers: {}
725-
726-
.. testcode:: pytest-example
727-
:hide:
728-
729-
import pytest
730-
731-
plugins = ['labgrid.pytestplugin']
732-
pytest.main(['--lg-env', 'feature-flag-env.yaml', 'test_feature_flags.py'], plugins)
720+
In case the feature is unavailable, pytest will record the missing feature
721+
as the skip reason.
733722

734-
.. testoutput:: pytest-example
735-
:hide:
736-
737-
... 1 passed...
738-
739-
This would run the above test, however the following configuration would skip the
740-
test because of the missing feature:
741-
742-
.. code-block:: yaml
743-
:name: feature-flag-skip-env.yaml
744-
745-
targets:
746-
main:
747-
features:
748-
- console
749-
resources: {}
750-
drivers: {}
751-
752-
.. testcode:: pytest-example
753-
:hide:
754-
755-
import pytest
756-
757-
plugins = ['labgrid.pytestplugin']
758-
pytest.main(['--lg-env', 'feature-flag-skip-env.yaml', 'test_feature_flags.py'], plugins)
759-
760-
.. testoutput:: pytest-example
761-
:hide:
762-
763-
... 1 skipped...
764-
765-
pytest will record the missing feature as the skip reason.
766-
767-
For tests with multiple required features, pass them as a list to pytest:
723+
Tests requiring multiple features are also possible:
768724

769725
.. code-block:: python
770-
:name: test_feature_flags_global.py
771726
772727
import pytest
773728
774729
@pytest.mark.lg_feature(["camera", "console"])
775730
def test_camera(target):
776731
pass
777732
778-
Features do not have to be set per target, they can also be set via the global
779-
features key:
780-
781-
.. code-block:: yaml
782-
:name: feature-flag-global-env.yaml
783-
784-
features:
785-
- camera
786-
targets:
787-
main:
788-
features:
789-
- console
790-
resources: {}
791-
drivers: {}
733+
@pytest.mark.lg_xfail_feature()
734+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
735+
labgrid supports :ref:`environment-configuration-feature-flags` in the
736+
:ref:`environment-configuration`.
737+
pytest supports the ``xfail`` marker, see
738+
`pytest.mark.xfail() <https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail>`_.
792739

793-
.. testcode:: pytest-example
794-
:hide:
740+
When having more specific features, tests can be marked as ``xfail`` for a
741+
particular feature.
795742

796-
import pytest
743+
Imagine two targets have ``camera`` feature flags.
744+
One of them has the additional ``special-camera-2000`` feature flag.
745+
The other has the additional ``special-camera-3000`` feature flag.
746+
Due to a known bug on ``special-camera-3000``, the test is expected to
747+
fail.
748+
The test can be marked as ``xfail`` for that feature:
797749

798-
plugins = ['labgrid.pytestplugin']
799-
pytest.main(['--lg-env', 'feature-flag-global-env.yaml', 'test_feature_flags_global.py'],
800-
plugins)
750+
.. code-block:: python
801751
802-
.. testoutput:: pytest-example
803-
:hide:
752+
import pytest
804753
805-
... 1 passed...
754+
@pytest.mark.lg_feature("camera"])
755+
@pytest.mark.lg_xfail_feature(
756+
"special-camera-3000",
757+
reason="known bug xy on special-camera-3000",
758+
raises=AssertionError,
759+
strict=True,
760+
)
761+
def test_camera(target):
762+
pass
806763
807-
This YAML configuration would combine both the global and the target features.
764+
Features under the target and global ``features:`` keys are considered.
808765

766+
``@pytest.mark.lg_xfail_feature(feature, **kwargs)``:
767+
- ``feature`` (str) - Feature that should mark the test as ``xfail``, passed
768+
as boolean ``condition=`` to ``pytest.mark.xfail()``.
769+
- ``**kwargs`` - All kw-only args are passed to ``pytest.mark.xfail()``.
809770

810771
Test Reports
811772
~~~~~~~~~~~~

labgrid/pytestplugin/hooks.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import copy
23
import logging
34
import pytest
45

@@ -71,7 +72,10 @@ def pytest_configure(config):
7172
configure_pytest_logging(config, logging_plugin)
7273

7374
config.addinivalue_line("markers",
74-
"lg_feature: marker for labgrid feature flags")
75+
"lg_feature: skip tests on envs/targets without given labgrid feature flags")
76+
config.addinivalue_line("markers",
77+
"lg_xfail_feature: mark tests xfail on envs/targets with given labgrid feature flag")
78+
7579
lg_log = config.option.lg_log
7680
if lg_log:
7781
ConsoleLoggingReporter(lg_log)
@@ -101,27 +105,44 @@ def pytest_collection_modifyitems(config, items):
101105
have_feature = env.get_features() | env.get_target_features()
102106

103107
for item in items:
108+
# pytest.mark.lg_feature
109+
lg_feature_signature = "pytest.mark.lg_feature(features: str | list[str])"
104110
want_feature = set()
105111

106112
for marker in item.iter_markers("lg_feature"):
107-
arg = marker.args[0]
108-
if isinstance(arg, str):
109-
want_feature.add(arg)
110-
elif isinstance(arg, list):
111-
want_feature.update(arg)
113+
if len(marker.args) != 1 or marker.kwargs:
114+
raise pytest.UsageError(f"Unexpected number of args/kwargs for {lg_feature_signature}")
115+
elif isinstance(marker.args[0], str):
116+
want_feature.add(marker.args[0])
117+
elif isinstance(marker.args[0], list):
118+
want_feature.update(marker.args[0])
112119
else:
113-
raise Exception("Unsupported feature argument type")
120+
raise pytest.UsageError(f"Unsupported 'features' argument type ({type(marker.args[0])}) for {lg_feature_signature}")
121+
114122
missing_feature = want_feature - have_feature
115123
if missing_feature:
116-
if len(missing_feature) == 1:
117-
skip = pytest.mark.skip(
118-
reason=f'Skipping because feature "{missing_feature}" is not supported'
119-
)
120-
else:
121-
skip = pytest.mark.skip(
122-
reason=f'Skipping because features "{missing_feature}" are not supported'
124+
reason = f'unsupported feature(s): {", ".join(missing_feature)}'
125+
item.add_marker(pytest.mark.skip(reason=reason))
126+
127+
# pytest.mark.lg_xfail_feature
128+
lg_xfail_feature_signature = "pytest.mark.lg_xfail_feature(feature: str, *, **xfail_kwargs), xfail_kwargs as pytest.mark.xfail expects them"
129+
for marker in item.iter_markers("lg_xfail_feature"):
130+
if len(marker.args) != 1:
131+
raise pytest.UsageError(f"Unexpected number of arguments for {lg_xfail_feature_signature}")
132+
elif not isinstance(marker.args[0], str):
133+
raise pytest.UsageError(f"Unsupported 'feature' argument type {type(marker.args[0])} for {lg_xfail_feature_signature}")
134+
if "condition" in marker.kwargs:
135+
raise pytest.UsageError(f"Unsupported 'condition' argument for {lg_xfail_feature_signature}")
136+
137+
kwargs = copy.copy(marker.kwargs)
138+
reason = kwargs.pop("reason", marker.args[0])
139+
item.add_marker(
140+
pytest.mark.xfail(
141+
condition=marker.args[0] in have_feature,
142+
reason=reason,
143+
**kwargs,
123144
)
124-
item.add_marker(skip)
145+
)
125146

126147
@pytest.hookimpl(tryfirst=True)
127148
def pytest_runtest_setup(item):

0 commit comments

Comments
 (0)