twister: pytest: Parametrize scope of the dut fixture

Added pytest_dut_scope keyword under harness_config section.
New keyword is used to determine the scope of dut and shell
fixtures in pytest-twister-harness plugin.

Signed-off-by: Grzegorz Chwierut <grzegorz.chwierut@nordicsemi.no>
This commit is contained in:
Grzegorz Chwierut 2023-10-25 12:33:23 +02:00 committed by Maureen Helm
commit fffe0b9fad
7 changed files with 57 additions and 7 deletions

View file

@ -69,6 +69,9 @@ DUT (initialize logging, flash device, connect serial etc).
This fixture yields a device prepared according to the requested type This fixture yields a device prepared according to the requested type
(native posix, qemu, hardware, etc.). All types of devices share the same API. (native posix, qemu, hardware, etc.). All types of devices share the same API.
This allows for writing tests which are device-type-agnostic. This allows for writing tests which are device-type-agnostic.
Scope of this fixture is determined by the ``pytest_dut_scope``
keyword placed under ``harness_config`` section.
.. code-block:: python .. code-block:: python
@ -81,8 +84,10 @@ shell
----- -----
Provide an object with methods used to interact with shell application. Provide an object with methods used to interact with shell application.
It calls `wait_for_promt` method, to not start scenario until DUT is ready. It calls ``wait_for_promt`` method, to not start scenario until DUT is ready.
Note that it uses `dut` fixture, so `dut` can be skipped when `shell` is used. Note that it uses ``dut`` fixture, so ``dut`` can be skipped when ``shell`` is used.
Scope of this fixture is determined by the ``pytest_dut_scope``
keyword placed under ``harness_config`` section.
.. code-block:: python .. code-block:: python

View file

@ -514,6 +514,11 @@ harness_config: <harness configuration options>
pytest_args: <list of arguments> (default empty) pytest_args: <list of arguments> (default empty)
Specify a list of additional arguments to pass to ``pytest``. Specify a list of additional arguments to pass to ``pytest``.
pytest_dut_scope: <function|class|module|package|session> (default function)
The scope for which ``dut`` and ``shell`` pytest fixtures are shared.
If the scope is set to ``function``, DUT is launched for every test case
in python script. For ``session`` scope, DUT is launched only once.
robot_test_path: <robot file path> (default empty) robot_test_path: <robot file path> (default empty)
Specify a path to a file containing a Robot Framework test suite to be run. Specify a path to a file containing a Robot Framework test suite to be run.

View file

@ -36,11 +36,16 @@ def device_object(twister_harness_config: TwisterHarnessConfig) -> Generator[Dev
device_object.close() device_object.close()
@pytest.fixture(scope='function') def determine_scope(fixture_name, config):
if dut_scope := config.getoption("--dut-scope", None):
return dut_scope
return 'function'
@pytest.fixture(scope=determine_scope)
def dut(request: pytest.FixtureRequest, device_object: DeviceAdapter) -> Generator[DeviceAdapter, None, None]: def dut(request: pytest.FixtureRequest, device_object: DeviceAdapter) -> Generator[DeviceAdapter, None, None]:
"""Return launched device - with run application.""" """Return launched device - with run application."""
test_name = request.node.name device_object.initialize_log_files(request.node.name)
device_object.initialize_log_files(test_name)
try: try:
device_object.launch() device_object.launch()
yield device_object yield device_object
@ -48,7 +53,7 @@ def dut(request: pytest.FixtureRequest, device_object: DeviceAdapter) -> Generat
device_object.close() device_object.close()
@pytest.fixture(scope='function') @pytest.fixture(scope=determine_scope)
def shell(dut: DeviceAdapter) -> Shell: def shell(dut: DeviceAdapter) -> Shell:
"""Return ready to use shell interface""" """Return ready to use shell interface"""
shell = Shell(dut, timeout=20.0) shell = Shell(dut, timeout=20.0)

View file

@ -100,6 +100,11 @@ def pytest_addoption(parser: pytest.Parser):
metavar='PATH', metavar='PATH',
help='Script executed after closing serial connection.' help='Script executed after closing serial connection.'
) )
twister_harness_group.addoption(
'--dut-scope',
choices=('function', 'class', 'module', 'package', 'session'),
help='The scope for which `dut` and `shell` fixtures are shared.'
)
def pytest_configure(config: pytest.Config): def pytest_configure(config: pytest.Config):

View file

@ -268,6 +268,7 @@ class Pytest(Harness):
config = self.instance.testsuite.harness_config config = self.instance.testsuite.harness_config
pytest_root = config.get('pytest_root', ['pytest']) if config else ['pytest'] pytest_root = config.get('pytest_root', ['pytest']) if config else ['pytest']
pytest_args = config.get('pytest_args', []) if config else [] pytest_args = config.get('pytest_args', []) if config else []
pytest_dut_scope = config.get('pytest_dut_scope', None) if config else None
command = [ command = [
'pytest', 'pytest',
'--twister-harness', '--twister-harness',
@ -281,6 +282,8 @@ class Pytest(Harness):
command.extend([os.path.normpath(os.path.join( command.extend([os.path.normpath(os.path.join(
self.source_dir, os.path.expanduser(os.path.expandvars(src)))) for src in pytest_root]) self.source_dir, os.path.expanduser(os.path.expandvars(src)))) for src in pytest_root])
command.extend(pytest_args) command.extend(pytest_args)
if pytest_dut_scope:
command.append(f'--dut-scope={pytest_dut_scope}')
handler: Handler = self.instance.handler handler: Handler = self.instance.handler
@ -427,7 +430,7 @@ class Pytest(Harness):
self.instance.execution_time = float(elem_ts.get('time')) self.instance.execution_time = float(elem_ts.get('time'))
for elem_tc in elem_ts.findall('testcase'): for elem_tc in elem_ts.findall('testcase'):
tc = self.instance.get_case_or_create(f"{self.id}.{elem_tc.get('name')}") tc = self.instance.add_testcase(f"{self.id}.{elem_tc.get('name')}")
tc.duration = float(elem_tc.get('time')) tc.duration = float(elem_tc.get('time'))
elem = elem_tc.find('*') elem = elem_tc.find('*')
if elem is None: if elem is None:

View file

@ -104,6 +104,10 @@ mapping:
required: false required: false
sequence: sequence:
- type: str - type: str
"pytest_dut_scope":
type: str
enum: ["function", "class", "module", "package", "session"]
required: false
"regex": "regex":
type: seq type: seq
required: false required: false
@ -304,6 +308,10 @@ mapping:
required: false required: false
sequence: sequence:
- type: str - type: str
"pytest_dut_scope":
type: str
enum: ["function", "class", "module", "package", "session"]
required: false
"regex": "regex":
type: seq type: seq
required: false required: false

View file

@ -48,6 +48,25 @@ def test_pytest_command(testinstance: TestInstance, device_type):
assert c in command assert c in command
def test_pytest_command_dut_scope(testinstance: TestInstance):
pytest_harness = Pytest()
dut_scope = 'session'
testinstance.testsuite.harness_config['pytest_dut_scope'] = dut_scope
pytest_harness.configure(testinstance)
command = pytest_harness.generate_command()
assert f'--dut-scope={dut_scope}' in command
def test_pytest_command_extra_args(testinstance: TestInstance):
pytest_harness = Pytest()
pytest_args = ['-k test1', '-m mark1']
testinstance.testsuite.harness_config['pytest_args'] = pytest_args
pytest_harness.configure(testinstance)
command = pytest_harness.generate_command()
for c in pytest_args:
assert c in command
@pytest.mark.parametrize( @pytest.mark.parametrize(
('pytest_root', 'expected'), ('pytest_root', 'expected'),
[ [