west: add tests for NXP S32 Debug Probe Add minimal set of tests to verify behavior of supported commands. Signed-off-by: Manuel Argüelles <manuel.arguelles@nxp.com>
diff --git a/scripts/west_commands/tests/test_nxp_s32dbg.py b/scripts/west_commands/tests/test_nxp_s32dbg.py new file mode 100644 index 0000000..a0d982c --- /dev/null +++ b/scripts/west_commands/tests/test_nxp_s32dbg.py
@@ -0,0 +1,247 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +from pathlib import Path +from unittest.mock import patch + +import pytest +from conftest import RC_KERNEL_ELF +from runners.nxp_s32dbg import NXPS32DebugProbeConfig, NXPS32DebugProbeRunner + +TEST_DEVICE = 's32dbg' +TEST_SPEED = 16000 +TEST_SERVER_PORT = 45000 +TEST_REMOTE_TIMEOUT = 30 +TEST_CORE_NAME = 'R52_0_0' +TEST_SOC_NAME = 'S32Z270' +TEST_SOC_FAMILY_NAME = 's32e2z2' +TEST_START_ALL_CORES = True +TEST_S32DS_PATH_OVERRIDE = None +TEST_TOOL_OPT = ['--test-opt-1', '--test-opt-2'] +TEST_RESET_TYPE = 'default' +TEST_RESET_DELAY = 0 + +TEST_S32DS_CMD = 's32ds' +TEST_SERVER_CMD = Path('S32DS') / 'tools' / 'S32Debugger' / 'Debugger' / 'Server' / 'gta' / 'gta' +TEST_ARM_GDB_CMD = Path('S32DS') / 'tools' / 'gdb-arm' / 'arm32-eabi' / 'bin' / 'arm-none-eabi-gdb-py' + +TEST_S32DS_PYTHON_LIB = Path('S32DS') / 'build_tools' / 'msys32' / 'mingw32' / 'lib' / 'python2.7' +TEST_S32DS_RUNTIME_ENV = { + 'PYTHONPATH': f'{TEST_S32DS_PYTHON_LIB}{os.pathsep}{TEST_S32DS_PYTHON_LIB / "site-packages"}' +} + +TEST_ALL_KWARGS = { + 'NXPS32DebugProbeConfig': { + 'conn_str': TEST_DEVICE, + 'server_port': TEST_SERVER_PORT, + 'speed': TEST_SPEED, + 'remote_timeout': TEST_REMOTE_TIMEOUT, + }, + 'NXPS32DebugProbeRunner': { + 'core_name': TEST_CORE_NAME, + 'soc_name': TEST_SOC_NAME, + 'soc_family_name': TEST_SOC_FAMILY_NAME, + 'start_all_cores': TEST_START_ALL_CORES, + 's32ds_path': TEST_S32DS_PATH_OVERRIDE, + 'tool_opt': TEST_TOOL_OPT + } +} + +TEST_ALL_PARAMS = [ + # generic + '--dev-id', TEST_DEVICE, + *[f'--tool-opt={o}' for o in TEST_TOOL_OPT], + # from runner + '--s32ds-path', TEST_S32DS_PATH_OVERRIDE, + '--core-name', TEST_CORE_NAME, + '--soc-name', TEST_SOC_NAME, + '--soc-family-name', TEST_SOC_FAMILY_NAME, + '--server-port', TEST_SERVER_PORT, + '--speed', TEST_SPEED, + '--remote-timeout', TEST_REMOTE_TIMEOUT, + '--start-all-cores', +] + +TEST_ALL_S32DBG_PY_VARS = [ + f'py _PROBE_IP = {repr(TEST_DEVICE)}', + f'py _JTAG_SPEED = {repr(TEST_SPEED)}', + f'py _GDB_SERVER_PORT = {repr(TEST_SERVER_PORT)}', + f"py _RESET_TYPE = {repr(TEST_RESET_TYPE)}", + f'py _RESET_DELAY = {repr(TEST_RESET_DELAY)}', + f'py _REMOTE_TIMEOUT = {repr(TEST_REMOTE_TIMEOUT)}', + f'py _CORE_NAME = {repr(f"{TEST_SOC_NAME}_{TEST_CORE_NAME}")}', + f'py _SOC_NAME = {repr(TEST_SOC_NAME)}', + 'py _IS_LOGGING_ENABLED = False', + 'py _FLASH_NAME = None', + 'py _SECURE_TYPE = None', + 'py _SECURE_KEY = None', +] + +DEBUGSERVER_ALL_EXPECTED_CALL = [ + str(TEST_SERVER_CMD), + '-p', str(TEST_SERVER_PORT), + *TEST_TOOL_OPT, +] + +DEBUG_ALL_EXPECTED_CALL = { + 'client': [ + str(TEST_ARM_GDB_CMD), + '-x', 'TEST_GDB_SCRIPT', + *TEST_TOOL_OPT, + ], + 'server': [ + str(TEST_SERVER_CMD), + '-p', str(TEST_SERVER_PORT), + ], + 'gdb_script': [ + *TEST_ALL_S32DBG_PY_VARS, + f'source generic_bareboard{"_all_cores" if TEST_START_ALL_CORES else ""}.py', + 'py board_init()', + 'py core_init()', + f'file {RC_KERNEL_ELF}', + 'load', + ] +} + +ATTACH_ALL_EXPECTED_CALL = { + **DEBUG_ALL_EXPECTED_CALL, + 'gdb_script': [ + *TEST_ALL_S32DBG_PY_VARS, + f'source attach.py', + 'py core_init()', + f'file {RC_KERNEL_ELF}', + ] +} + + +@pytest.fixture +def s32dbg(runner_config, tmp_path): + '''NXPS32DebugProbeRunner from constructor kwargs or command line parameters''' + def _factory(args): + # create empty files to ensure kernel binaries exist + (tmp_path / RC_KERNEL_ELF).touch() + os.chdir(tmp_path) + + runner_config_patched = fix_up_runner_config(runner_config, tmp_path) + + if isinstance(args, dict): + probe_cfg = NXPS32DebugProbeConfig(**args['NXPS32DebugProbeConfig']) + return NXPS32DebugProbeRunner(runner_config_patched, probe_cfg, + **args['NXPS32DebugProbeRunner']) + elif isinstance(args, list): + parser = argparse.ArgumentParser(allow_abbrev=False) + NXPS32DebugProbeRunner.add_parser(parser) + arg_namespace = parser.parse_args(str(x) for x in args) + return NXPS32DebugProbeRunner.create(runner_config_patched, arg_namespace) + return _factory + + +def fix_up_runner_config(runner_config, tmp_path): + to_replace = {} + + zephyr = tmp_path / 'zephyr' + zephyr.mkdir() + dotconfig = zephyr / '.config' + dotconfig.write_text('CONFIG_ARCH="arm"') + to_replace['build_dir'] = tmp_path + + return runner_config._replace(**to_replace) + + +def require_patch(program, path=None): + assert Path(program).stem == TEST_S32DS_CMD + return program + + +def s32dbg_get_script(name): + return Path(f'{name}.py') + + +@pytest.mark.parametrize('s32dbg_args,expected,osname', [ + (TEST_ALL_KWARGS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_PARAMS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_KWARGS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Linux'), + (TEST_ALL_PARAMS, DEBUGSERVER_ALL_EXPECTED_CALL, 'Linux'), +]) +@patch('platform.system') +@patch('runners.core.ZephyrBinaryRunner.check_call') +@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch) +def test_debugserver(require, check_call, system, + s32dbg_args, expected, osname, s32dbg): + system.return_value = osname + + runner = s32dbg(s32dbg_args) + runner.run('debugserver') + + assert require.called + check_call.assert_called_once_with(expected) + + +@pytest.mark.parametrize('s32dbg_args,expected,osname', [ + (TEST_ALL_KWARGS, DEBUG_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_PARAMS, DEBUG_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_KWARGS, DEBUG_ALL_EXPECTED_CALL, 'Linux'), + (TEST_ALL_PARAMS, DEBUG_ALL_EXPECTED_CALL, 'Linux'), +]) +@patch.dict(os.environ, TEST_S32DS_RUNTIME_ENV, clear=True) +@patch('platform.system') +@patch('tempfile.TemporaryDirectory') +@patch('runners.nxp_s32dbg.NXPS32DebugProbeRunner.get_script', side_effect=s32dbg_get_script) +@patch('runners.core.ZephyrBinaryRunner.popen_ignore_int') +@patch('runners.core.ZephyrBinaryRunner.check_call') +@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch) +def test_debug(require, check_call, popen_ignore_int, get_script, temporary_dir, system, + s32dbg_args, expected, osname, s32dbg, tmp_path): + + # mock tempfile.TemporaryDirectory to return `tmp_path` and create gdb init script there + temporary_dir.return_value.__enter__.return_value = tmp_path + gdb_script = tmp_path / 'runner.nxp_s32dbg' + expected_client = [e.replace('TEST_GDB_SCRIPT', gdb_script.as_posix()) + for e in expected['client']] + + system.return_value = osname + expected_env = TEST_S32DS_RUNTIME_ENV if osname == 'Windows' else None + + runner = s32dbg(s32dbg_args) + runner.run('debug') + + assert require.called + assert gdb_script.read_text().splitlines() == expected['gdb_script'] + popen_ignore_int.assert_called_once_with(expected['server'], env=expected_env) + check_call.assert_called_once_with(expected_client, env=expected_env) + + +@pytest.mark.parametrize('s32dbg_args,expected,osname', [ + (TEST_ALL_KWARGS, ATTACH_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_PARAMS, ATTACH_ALL_EXPECTED_CALL, 'Windows'), + (TEST_ALL_KWARGS, ATTACH_ALL_EXPECTED_CALL, 'Linux'), + (TEST_ALL_PARAMS, ATTACH_ALL_EXPECTED_CALL, 'Linux'), +]) +@patch.dict(os.environ, TEST_S32DS_RUNTIME_ENV, clear=True) +@patch('platform.system') +@patch('tempfile.TemporaryDirectory') +@patch('runners.nxp_s32dbg.NXPS32DebugProbeRunner.get_script', side_effect=s32dbg_get_script) +@patch('runners.core.ZephyrBinaryRunner.popen_ignore_int') +@patch('runners.core.ZephyrBinaryRunner.check_call') +@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch) +def test_attach(require, check_call, popen_ignore_int, get_script, temporary_dir, system, + s32dbg_args, expected, osname, s32dbg, tmp_path): + + # mock tempfile.TemporaryDirectory to return `tmp_path` and create gdb init script there + temporary_dir.return_value.__enter__.return_value = tmp_path + gdb_script = tmp_path / 'runner.nxp_s32dbg' + expected_client = [e.replace('TEST_GDB_SCRIPT', gdb_script.as_posix()) + for e in expected['client']] + + system.return_value = osname + expected_env = TEST_S32DS_RUNTIME_ENV if osname == 'Windows' else None + + runner = s32dbg(s32dbg_args) + runner.run('attach') + + assert require.called + assert gdb_script.read_text().splitlines() == expected['gdb_script'] + popen_ignore_int.assert_called_once_with(expected['server'], env=expected_env) + check_call.assert_called_once_with(expected_client, env=expected_env)