diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py index e2e82674ada817f..792ac5879898ea7 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import logging +from subprocess import CompletedProcess from typing import Generator, Type import pytest @@ -12,6 +13,7 @@ from twister_harness.twister_harness_config import DeviceConfig, TwisterHarnessConfig from twister_harness.helpers.shell import Shell from twister_harness.helpers.mcumgr import MCUmgr +from twister_harness.helpers.gdb import GDBrunner logger = logging.getLogger(__name__) @@ -66,3 +68,11 @@ def is_mcumgr_available() -> None: @pytest.fixture() def mcumgr(is_mcumgr_available: None, dut: DeviceAdapter) -> Generator[MCUmgr, None, None]: yield MCUmgr.create_for_serial(dut.device_config.serial) + + +@pytest.fixture(scope='function') +def gdb_batch(dut: DeviceAdapter, gdb_target_remote: str, gdb_script: str, gdb_timeout) -> CompletedProcess: + """Returns results from GDB script execution""" + gdb = GDBrunner(dut) + return gdb.run_script(gdb_target_remote, gdb_script, gdb_timeout) +# diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py new file mode 100644 index 000000000000000..001f476d2fbd0f6 --- /dev/null +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py @@ -0,0 +1,65 @@ +# Copyright (c) 2023 Intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import os +import subprocess +import sys +import logging +import shlex +import re + +from twister_harness.device.device_adapter import DeviceAdapter + +ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") +sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "pylib", "twister")) +from twisterlib.cmakecache import CMakeCache + +logger = logging.getLogger(__name__) + + +class GDBrunner: + """ + Helper class to run GDB process as a part of the test. + """ + + def __init__(self, device: DeviceAdapter) -> None: + build_dir = device.device_config.build_dir + cmake_cache = CMakeCache.from_file(os.path.join(build_dir, 'CMakeCache.txt')) + + gdb_exec = cmake_cache.get('CMAKE_GDB', None) + assert gdb_exec + + self.source_dir = cmake_cache.get('APPLICATION_SOURCE_DIR', None) + assert self.source_dir + + self.build_image = cmake_cache.get('BYPRODUCT_KERNEL_ELF_NAME', None) + assert self.build_image + + gdb_log_file = os.path.join(build_dir, 'gdb.log') + + self.command = [gdb_exec, '-batch', + '-ex', f'set pagination off', + '-ex', f'set trace-commands on', + '-ex', f'set logging file {gdb_log_file}', + '-ex', f'set logging enabled on' + ] + # + + def run_script(self, target_remote: str, gdb_script:str, timeout:float | None = None) -> CompletedProcess: + """ + Run GDB process which executes a script connecting to remote target backend. + """ + cmd = self.command + [ + '-ex', f'target remote {target_remote}', + '-x', f'{self.source_dir}/{gdb_script}', + self.build_image + ] + logger.info(f'Run GDB: {shlex.join(cmd)}') + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + logger.info(f'GDB ends rc={result.returncode}') + return result + +# GDBrunner diff --git a/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py b/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py index 420af11a33ffaed..829724f5ff101f4 100755 --- a/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py +++ b/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import os -import subprocess +from subprocess import CompletedProcess import sys import logging import shlex @@ -11,36 +11,8 @@ import pytest from twister_harness import DeviceAdapter -ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") -sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "pylib", "twister")) -from twisterlib.cmakecache import CMakeCache - logger = logging.getLogger(__name__) -@pytest.fixture() -def gdb_process(dut: DeviceAdapter, gdb_script, gdb_timeout, gdb_target_remote): - build_dir = dut.device_config.build_dir - cmake_cache = CMakeCache.from_file(os.path.join(build_dir, 'CMakeCache.txt')) - gdb_exec = cmake_cache.get('CMAKE_GDB', None) - assert gdb_exec - source_dir = cmake_cache.get('APPLICATION_SOURCE_DIR', None) - assert source_dir - build_image = cmake_cache.get('BYPRODUCT_KERNEL_ELF_NAME', None) - assert build_image - gdb_log_file = os.path.join(build_dir, 'gdb.log') - cmd = [gdb_exec, '-batch', - '-ex', f'set pagination off', - '-ex', f'set trace-commands on', - '-ex', f'set logging file {gdb_log_file}', - '-ex', f'set logging enabled on', - '-ex', f'target remote {gdb_target_remote}', - '-x', f'{source_dir}/{gdb_script}', build_image] - logger.info(f'Run GDB: {shlex.join(cmd)}') - result = subprocess.run(cmd, capture_output=True, text=True, timeout=gdb_timeout) - logger.info(f'GDB ends rc={result.returncode}') - return result -# - @pytest.fixture(scope="module") def expected_app(): return [ @@ -67,16 +39,16 @@ def expected_gdb_detach(): ] -def test_gdbstub(dut: DeviceAdapter, gdb_process, expected_app, expected_gdb, expected_gdb_detach): +def test_gdbstub(dut: DeviceAdapter, gdb_batch: CompletedProcess, expected_app, expected_gdb, expected_gdb_detach): """ Test gdbstub feature using a GDB script. We connect to the DUT, run the GDB script then evaluate return code and expected patterns at the GDB and Test Applicaiton outputs. """ - logger.debug(f"GDB output:\n{gdb_process.stdout}\n") - assert gdb_process.returncode == 0 - assert all([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb]), 'No expected GDB output' - assert any([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb_detach]), 'No expected GDB quit' + logger.debug(f"GDB output:\n{gdb_batch.stdout}\n") + assert gdb_batch.returncode == 0 + assert all([ex_re.search(gdb_batch.stdout, re.MULTILINE) for ex_re in expected_gdb]), 'No expected GDB output' + assert any([ex_re.search(gdb_batch.stdout, re.MULTILINE) for ex_re in expected_gdb_detach]), 'No expected GDB quit' app_output = '\n'.join(dut.readlines(print_output = False)) logger.debug(f"App output:\n{app_output}\n") assert all([ex_re.search(app_output, re.MULTILINE) for ex_re in expected_app]), 'No expected Application output'