1# Copyright (c) 2023 Intel Corporation.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import os
6import subprocess
7import sys
8import logging
9import shlex
10import re
11import pytest
12from twister_harness import DeviceAdapter
13
14ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
15sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "pylib", "twister"))
16from twisterlib.cmakecache import CMakeCache
17
18logger = logging.getLogger(__name__)
19
20@pytest.fixture()
21def gdb_process(dut: DeviceAdapter, gdb_script, gdb_timeout, gdb_target_remote) -> subprocess.CompletedProcess:
22    build_dir = dut.device_config.build_dir
23    cmake_cache = CMakeCache.from_file(os.path.join(build_dir, 'CMakeCache.txt'))
24    gdb_exec = cmake_cache.get('CMAKE_GDB', None)
25    assert gdb_exec
26    source_dir = cmake_cache.get('APPLICATION_SOURCE_DIR', None)
27    assert source_dir
28    build_image = cmake_cache.get('BYPRODUCT_KERNEL_ELF_NAME', None)
29    assert build_image
30    gdb_log_file = os.path.join(build_dir, 'gdb.log')
31    cmd = [gdb_exec, '-batch',
32           '-ex', f'set pagination off',
33           '-ex', f'set trace-commands on',
34           '-ex', f'set logging file {gdb_log_file}',
35           '-ex', f'set logging enabled on',
36           '-ex', f'target remote {gdb_target_remote}',
37           '-x', f'{source_dir}/{gdb_script}', build_image]
38    logger.info(f'Run GDB: {shlex.join(cmd)}')
39    result = subprocess.run(cmd, capture_output=True, text=True, timeout=gdb_timeout)
40    logger.info(f'GDB ends rc={result.returncode}')
41    return result
42#
43
44@pytest.fixture(scope="module")
45def expected_app():
46    return [
47    re.compile(r"Booting from ROM"),
48    re.compile(r"Booting Zephyr OS build"),
49    re.compile(r"main\(\):enter"),
50    ]
51
52@pytest.fixture(scope="module")
53def expected_gdb():
54    return [
55    re.compile(r'Breakpoint 1 at 0x'),
56    re.compile(r'Breakpoint 2 at 0x'),
57    re.compile(r'Breakpoint 1, test '),
58    re.compile(r'Breakpoint 2, main '),
59    re.compile(r'GDB:PASSED'),
60    re.compile(r'Breakpoint 3, k_thread_abort '),
61    re.compile(r'2 .* breakpoint .* in main '),
62    ]
63
64@pytest.fixture(scope="module")
65def unexpected_gdb():
66    return [
67    re.compile(r'breakpoint .* in test '),
68    re.compile(r'breakpoint .* in k_thread_abort '),
69    ]
70
71@pytest.fixture(scope="module")
72def expected_gdb_detach():
73    return [
74    re.compile(r'Inferior.*will be killed'),  # Zephyr gdbstub
75    re.compile(r'Inferior.*detached')  # QEMU gdbstub
76    ]
77
78
79def test_gdbstub(dut: DeviceAdapter, gdb_process, expected_app, expected_gdb, expected_gdb_detach, unexpected_gdb):
80    """
81    Test gdbstub feature using a GDB script. We connect to the DUT, run the
82    GDB script then evaluate return code and expected patterns at the GDB
83    and Test Applicaiton outputs.
84    """
85    logger.debug(f"GDB output:\n{gdb_process.stdout}\n")
86    assert gdb_process.returncode == 0
87    assert all([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb]), 'No expected GDB output'
88    assert not any([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in unexpected_gdb]), 'Unexpected GDB output'
89    assert any([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb_detach]), 'No expected GDB quit'
90    app_output = '\n'.join(dut.readlines(print_output = False))
91    logger.debug(f"App output:\n{app_output}\n")
92    assert all([ex_re.search(app_output, re.MULTILINE) for ex_re in expected_app]), 'No expected Application output'
93#
94