1# Copyright 2023 NXP
2# SPDX-License-Identifier: Apache-2.0
3
4import argparse
5import os
6from pathlib import Path
7from unittest.mock import patch
8
9import pytest
10from conftest import RC_KERNEL_ELF
11from runners.nxp_s32dbg import NXPS32DebugProbeConfig, NXPS32DebugProbeRunner
12
13TEST_DEVICE = 's32dbg'
14TEST_SPEED = 16000
15TEST_SERVER_PORT = 45000
16TEST_REMOTE_TIMEOUT = 30
17TEST_CORE_NAME = 'R52_0_0'
18TEST_SOC_NAME = 'S32Z270'
19TEST_SOC_FAMILY_NAME = 's32e2z2'
20TEST_START_ALL_CORES = True
21TEST_S32DS_PATH_OVERRIDE = None
22TEST_TOOL_OPT = ['--test-opt-1', '--test-opt-2']
23TEST_RESET_TYPE = 'default'
24TEST_RESET_DELAY = 0
25
26TEST_S32DS_CMD = 's32ds'
27TEST_SERVER_CMD = Path('S32DS') / 'tools' / 'S32Debugger' / 'Debugger' / 'Server' / 'gta' / 'gta'
28TEST_ARM_GDB_CMD = Path('S32DS') / 'tools' / 'gdb-arm' / 'arm32-eabi' / 'bin' / 'arm-none-eabi-gdb-py'
29
30TEST_S32DS_PYTHON_LIB = Path('S32DS') / 'build_tools' / 'msys32' / 'mingw32' / 'lib' / 'python2.7'
31TEST_S32DS_RUNTIME_ENV = {
32    'PYTHONPATH': f'{TEST_S32DS_PYTHON_LIB}{os.pathsep}{TEST_S32DS_PYTHON_LIB / "site-packages"}'
33}
34
35TEST_ALL_KWARGS = {
36    'NXPS32DebugProbeConfig': {
37        'conn_str': TEST_DEVICE,
38        'server_port': TEST_SERVER_PORT,
39        'speed': TEST_SPEED,
40        'remote_timeout': TEST_REMOTE_TIMEOUT,
41    },
42    'NXPS32DebugProbeRunner': {
43        'core_name': TEST_CORE_NAME,
44        'soc_name': TEST_SOC_NAME,
45        'soc_family_name': TEST_SOC_FAMILY_NAME,
46        'start_all_cores': TEST_START_ALL_CORES,
47        's32ds_path': TEST_S32DS_PATH_OVERRIDE,
48        'tool_opt': TEST_TOOL_OPT
49    }
50}
51
52TEST_ALL_PARAMS = [
53    # generic
54    '--dev-id', TEST_DEVICE,
55    *[f'--tool-opt={o}' for o in TEST_TOOL_OPT],
56    # from runner
57    '--s32ds-path', TEST_S32DS_PATH_OVERRIDE,
58    '--core-name', TEST_CORE_NAME,
59    '--soc-name', TEST_SOC_NAME,
60    '--soc-family-name', TEST_SOC_FAMILY_NAME,
61    '--server-port', TEST_SERVER_PORT,
62    '--speed', TEST_SPEED,
63    '--remote-timeout', TEST_REMOTE_TIMEOUT,
64    '--start-all-cores',
65]
66
67TEST_ALL_S32DBG_PY_VARS = [
68    f'py _PROBE_IP = {repr(TEST_DEVICE)}',
69    f'py _JTAG_SPEED = {repr(TEST_SPEED)}',
70    f'py _GDB_SERVER_PORT = {repr(TEST_SERVER_PORT)}',
71    f"py _RESET_TYPE = {repr(TEST_RESET_TYPE)}",
72    f'py _RESET_DELAY = {repr(TEST_RESET_DELAY)}',
73    f'py _REMOTE_TIMEOUT = {repr(TEST_REMOTE_TIMEOUT)}',
74    f'py _CORE_NAME = {repr(f"{TEST_SOC_NAME}_{TEST_CORE_NAME}")}',
75    f'py _SOC_NAME = {repr(TEST_SOC_NAME)}',
76    'py _IS_LOGGING_ENABLED = False',
77    'py _FLASH_NAME = None',
78    'py _SECURE_TYPE = None',
79    'py _SECURE_KEY = None',
80]
81
82DEBUGSERVER_ALL_EXPECTED_CALL = [
83    str(TEST_SERVER_CMD),
84    '-p', str(TEST_SERVER_PORT),
85    *TEST_TOOL_OPT,
86]
87
88DEBUG_ALL_EXPECTED_CALL = {
89    'client': [
90        str(TEST_ARM_GDB_CMD),
91        '-x', 'TEST_GDB_SCRIPT',
92        *TEST_TOOL_OPT,
93    ],
94    'server': [
95        str(TEST_SERVER_CMD),
96        '-p', str(TEST_SERVER_PORT),
97    ],
98    'gdb_script': [
99        *TEST_ALL_S32DBG_PY_VARS,
100        f'source generic_bareboard{"_all_cores" if TEST_START_ALL_CORES else ""}.py',
101        'py board_init()',
102        'py core_init()',
103        f'file {RC_KERNEL_ELF}',
104        'load',
105    ]
106}
107
108ATTACH_ALL_EXPECTED_CALL = {
109    **DEBUG_ALL_EXPECTED_CALL,
110    'gdb_script': [
111        *TEST_ALL_S32DBG_PY_VARS,
112        f'source attach.py',
113        'py core_init()',
114        f'file {RC_KERNEL_ELF}',
115    ]
116}
117
118
119@pytest.fixture
120def s32dbg(runner_config, tmp_path):
121    '''NXPS32DebugProbeRunner from constructor kwargs or command line parameters'''
122    def _factory(args):
123        # create empty files to ensure kernel binaries exist
124        (tmp_path / RC_KERNEL_ELF).touch()
125        os.chdir(tmp_path)
126
127        runner_config_patched = fix_up_runner_config(runner_config, tmp_path)
128
129        if isinstance(args, dict):
130            probe_cfg = NXPS32DebugProbeConfig(**args['NXPS32DebugProbeConfig'])
131            return NXPS32DebugProbeRunner(runner_config_patched, probe_cfg,
132                                          **args['NXPS32DebugProbeRunner'])
133        elif isinstance(args, list):
134            parser = argparse.ArgumentParser(allow_abbrev=False)
135            NXPS32DebugProbeRunner.add_parser(parser)
136            arg_namespace = parser.parse_args(str(x) for x in args)
137            return NXPS32DebugProbeRunner.create(runner_config_patched, arg_namespace)
138    return _factory
139
140
141def fix_up_runner_config(runner_config, tmp_path):
142    to_replace = {}
143
144    zephyr = tmp_path / 'zephyr'
145    zephyr.mkdir()
146    dotconfig = zephyr / '.config'
147    dotconfig.write_text('CONFIG_ARCH="arm"')
148    to_replace['build_dir'] = tmp_path
149
150    return runner_config._replace(**to_replace)
151
152
153def require_patch(program, path=None):
154    assert Path(program).stem == TEST_S32DS_CMD
155    return program
156
157
158def s32dbg_get_script(name):
159    return Path(f'{name}.py')
160
161
162@pytest.mark.parametrize('s32dbg_args,expected,osname', [
163    (TEST_ALL_KWARGS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Windows'),
164    (TEST_ALL_PARAMS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Windows'),
165    (TEST_ALL_KWARGS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Linux'),
166    (TEST_ALL_PARAMS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Linux'),
167])
168@patch('platform.system')
169@patch('runners.core.ZephyrBinaryRunner.check_call')
170@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
171def test_debugserver(require, check_call, system,
172                     s32dbg_args, expected, osname, s32dbg):
173    system.return_value = osname
174
175    runner = s32dbg(s32dbg_args)
176    runner.run('debugserver')
177
178    assert require.called
179    check_call.assert_called_once_with(expected)
180
181
182@pytest.mark.parametrize('s32dbg_args,expected,osname', [
183    (TEST_ALL_KWARGS, DEBUG_ALL_EXPECTED_CALL, 'Windows'),
184    (TEST_ALL_PARAMS, DEBUG_ALL_EXPECTED_CALL, 'Windows'),
185    (TEST_ALL_KWARGS, DEBUG_ALL_EXPECTED_CALL, 'Linux'),
186    (TEST_ALL_PARAMS, DEBUG_ALL_EXPECTED_CALL, 'Linux'),
187])
188@patch.dict(os.environ, TEST_S32DS_RUNTIME_ENV, clear=True)
189@patch('platform.system')
190@patch('tempfile.TemporaryDirectory')
191@patch('runners.nxp_s32dbg.NXPS32DebugProbeRunner.get_script', side_effect=s32dbg_get_script)
192@patch('runners.core.ZephyrBinaryRunner.popen_ignore_int')
193@patch('runners.core.ZephyrBinaryRunner.check_call')
194@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
195def test_debug(require, check_call, popen_ignore_int, get_script, temporary_dir, system,
196               s32dbg_args, expected, osname, s32dbg, tmp_path):
197
198    # mock tempfile.TemporaryDirectory to return `tmp_path` and create gdb init script there
199    temporary_dir.return_value.__enter__.return_value = tmp_path
200    gdb_script = tmp_path / 'runner.nxp_s32dbg'
201    expected_client = [e.replace('TEST_GDB_SCRIPT', gdb_script.as_posix())
202                       for e in expected['client']]
203
204    system.return_value = osname
205    expected_env = TEST_S32DS_RUNTIME_ENV if osname == 'Windows' else None
206
207    runner = s32dbg(s32dbg_args)
208    runner.run('debug')
209
210    assert require.called
211    assert gdb_script.read_text().splitlines() == expected['gdb_script']
212    popen_ignore_int.assert_called_once_with(expected['server'], env=expected_env)
213    check_call.assert_called_once_with(expected_client, env=expected_env)
214
215
216@pytest.mark.parametrize('s32dbg_args,expected,osname', [
217    (TEST_ALL_KWARGS, ATTACH_ALL_EXPECTED_CALL, 'Windows'),
218    (TEST_ALL_PARAMS, ATTACH_ALL_EXPECTED_CALL, 'Windows'),
219    (TEST_ALL_KWARGS, ATTACH_ALL_EXPECTED_CALL, 'Linux'),
220    (TEST_ALL_PARAMS, ATTACH_ALL_EXPECTED_CALL, 'Linux'),
221])
222@patch.dict(os.environ, TEST_S32DS_RUNTIME_ENV, clear=True)
223@patch('platform.system')
224@patch('tempfile.TemporaryDirectory')
225@patch('runners.nxp_s32dbg.NXPS32DebugProbeRunner.get_script', side_effect=s32dbg_get_script)
226@patch('runners.core.ZephyrBinaryRunner.popen_ignore_int')
227@patch('runners.core.ZephyrBinaryRunner.check_call')
228@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
229def test_attach(require, check_call, popen_ignore_int, get_script, temporary_dir, system,
230                s32dbg_args, expected, osname, s32dbg, tmp_path):
231
232    # mock tempfile.TemporaryDirectory to return `tmp_path` and create gdb init script there
233    temporary_dir.return_value.__enter__.return_value = tmp_path
234    gdb_script = tmp_path / 'runner.nxp_s32dbg'
235    expected_client = [e.replace('TEST_GDB_SCRIPT', gdb_script.as_posix())
236                       for e in expected['client']]
237
238    system.return_value = osname
239    expected_env = TEST_S32DS_RUNTIME_ENV if osname == 'Windows' else None
240
241    runner = s32dbg(s32dbg_args)
242    runner.run('attach')
243
244    assert require.called
245    assert gdb_script.read_text().splitlines() == expected['gdb_script']
246    popen_ignore_int.assert_called_once_with(expected['server'], env=expected_env)
247    check_call.assert_called_once_with(expected_client, env=expected_env)
248