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
(native posix, qemu, hardware, etc.). All types of devices share the same API.
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
@ -81,8 +84,10 @@ shell
-----
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.
Note that it uses `dut` fixture, so `dut` can be skipped when `shell` is used.
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.
Scope of this fixture is determined by the ``pytest_dut_scope``
keyword placed under ``harness_config`` section.
.. code-block:: python

View file

@ -514,6 +514,11 @@ harness_config: <harness configuration options>
pytest_args: <list of arguments> (default empty)
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)
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()
@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]:
"""Return launched device - with run application."""
test_name = request.node.name
device_object.initialize_log_files(test_name)
device_object.initialize_log_files(request.node.name)
try:
device_object.launch()
yield device_object
@ -48,7 +53,7 @@ def dut(request: pytest.FixtureRequest, device_object: DeviceAdapter) -> Generat
device_object.close()
@pytest.fixture(scope='function')
@pytest.fixture(scope=determine_scope)
def shell(dut: DeviceAdapter) -> Shell:
"""Return ready to use shell interface"""
shell = Shell(dut, timeout=20.0)

View file

@ -100,6 +100,11 @@ def pytest_addoption(parser: pytest.Parser):
metavar='PATH',
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):

View file

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

View file

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

View file

@ -48,6 +48,25 @@ def test_pytest_command(testinstance: TestInstance, device_type):
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_root', 'expected'),
[