Skip to content

Commit

Permalink
pytest: gdbstub: Add GDB helper to Twister pytest plugin
Browse files Browse the repository at this point in the history
Add GDB helper and fixture to Twister pytest plugin to reuse
it for test cases where GDB execution is needed.

Adjust GDB stub test to use the new helper class.

Signed-off-by: Dmitrii Golovanov <dmitrii.golovanov@intel.com>
  • Loading branch information
golowanow committed Oct 11, 2023
1 parent a2fecdc commit b741b2a
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0

import logging
from subprocess import CompletedProcess
from typing import Generator, Type

import pytest
Expand All @@ -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__)

Expand Down Expand Up @@ -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)
#
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 12 in scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

W0611

scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py:12 Unused import re (unused-import)

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:

Check failure on line 51 in scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

E0602

scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/gdb.py:51 Undefined variable 'CompletedProcess' (undefined-variable)
"""
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
40 changes: 6 additions & 34 deletions tests/subsys/debug/gdbstub/pytest/test_gdbstub.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,16 @@
# SPDX-License-Identifier: Apache-2.0

import os

Check warning on line 5 in tests/subsys/debug/gdbstub/pytest/test_gdbstub.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

W0611

tests/subsys/debug/gdbstub/pytest/test_gdbstub.py:5 Unused import os (unused-import)
import subprocess
from subprocess import CompletedProcess
import sys

Check warning on line 7 in tests/subsys/debug/gdbstub/pytest/test_gdbstub.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

W0611

tests/subsys/debug/gdbstub/pytest/test_gdbstub.py:7 Unused import sys (unused-import)
import logging
import shlex

Check warning on line 9 in tests/subsys/debug/gdbstub/pytest/test_gdbstub.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

W0611

tests/subsys/debug/gdbstub/pytest/test_gdbstub.py:9 Unused import shlex (unused-import)
import re
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 [
Expand All @@ -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'
Expand Down

0 comments on commit b741b2a

Please sign in to comment.