1# Copyright (c) 2018 Foundries.io
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import argparse
6import itertools
7from unittest.mock import patch
8
9import pytest
10
11from runners.pyocd import PyOcdBinaryRunner
12from conftest import RC_BUILD_DIR, RC_GDB, RC_KERNEL_HEX, RC_KERNEL_ELF
13
14
15#
16# Test values to provide as constructor arguments and command line
17# parameters, to verify they're respected.
18#
19
20TEST_PYOCD = 'test-pyocd'
21TEST_ADDR = 0xadd
22TEST_DEV_ID = 'test-dev-id'
23TEST_FREQUENCY = 'test-frequency'
24TEST_DAPARG = 'test-daparg'
25TEST_TARGET = 'test-target'
26TEST_FLASH_OPTS = ['--test-flash', 'args']
27TEST_GDB_PORT = 1
28TEST_TELNET_PORT = 2
29TEST_TOOL_OPTS = ['test-opt-1', 'test-opt-2']
30
31TEST_ALL_KWARGS = {
32    'pyocd': TEST_PYOCD,
33    'flash_addr': TEST_ADDR,
34    'flash_opts': TEST_FLASH_OPTS,
35    'gdb_port': TEST_GDB_PORT,
36    'telnet_port': TEST_TELNET_PORT,
37    'tui': False,
38    'dev_id': TEST_DEV_ID,
39    'frequency': TEST_FREQUENCY,
40    'daparg': TEST_DAPARG,
41    'tool_opt': TEST_TOOL_OPTS
42}
43
44TEST_DEF_KWARGS = {}
45
46TEST_ALL_PARAMS = list(itertools.chain(
47    ['--target', TEST_TARGET,
48     '--daparg', TEST_DAPARG,
49     '--pyocd', TEST_PYOCD],
50    [f'--flash-opt={o}' for o in TEST_FLASH_OPTS],
51    ['--gdb-port', str(TEST_GDB_PORT),
52     '--telnet-port', str(TEST_TELNET_PORT),
53     '--dev-id', TEST_DEV_ID,
54     '--frequency', str(TEST_FREQUENCY)],
55    [f'--tool-opt={o}' for o in TEST_TOOL_OPTS]))
56
57TEST_DEF_PARAMS = ['--target', TEST_TARGET]
58
59#
60# Expected results.
61#
62# These record expected argument lists for system calls made by the
63# pyocd runner using its check_call() and run_server_and_client()
64# methods.
65#
66# They are shared between tests that create runners directly and
67# tests that construct runners from parsed command-line arguments, to
68# ensure that results are consistent.
69#
70
71FLASH_ALL_EXPECTED_CALL = ([TEST_PYOCD,
72                            'flash',
73                            '-e', 'sector',
74                            '-a', hex(TEST_ADDR), '-da', TEST_DAPARG,
75                            '-t', TEST_TARGET, '-u', TEST_DEV_ID,
76                            '-f', TEST_FREQUENCY] +
77                           TEST_TOOL_OPTS +
78                           TEST_FLASH_OPTS +
79                           [RC_KERNEL_HEX])
80FLASH_DEF_EXPECTED_CALL = ['pyocd', 'flash', '-e', 'sector',
81                           '-t', TEST_TARGET, RC_KERNEL_HEX]
82
83
84DEBUG_ALL_EXPECTED_SERVER = [TEST_PYOCD,
85                             'gdbserver',
86                             '-da', TEST_DAPARG,
87                             '-p', str(TEST_GDB_PORT),
88                             '-T', str(TEST_TELNET_PORT),
89                             '-t', TEST_TARGET,
90                             '-u', TEST_DEV_ID,
91                             '-f', TEST_FREQUENCY] + TEST_TOOL_OPTS
92DEBUG_ALL_EXPECTED_CLIENT = [RC_GDB, RC_KERNEL_ELF,
93                             '-ex', 'target remote :{}'.format(TEST_GDB_PORT),
94                             '-ex', 'monitor halt',
95                             '-ex', 'monitor reset',
96                             '-ex', 'load']
97DEBUG_DEF_EXPECTED_SERVER = ['pyocd',
98                             'gdbserver',
99                             '-p', '3333',
100                             '-T', '4444',
101                             '-t', TEST_TARGET]
102DEBUG_DEF_EXPECTED_CLIENT = [RC_GDB, RC_KERNEL_ELF,
103                             '-ex', 'target remote :3333',
104                             '-ex', 'monitor halt',
105                             '-ex', 'monitor reset',
106                             '-ex', 'load']
107
108
109DEBUGSERVER_ALL_EXPECTED_CALL = [TEST_PYOCD,
110                                 'gdbserver',
111                                 '-da', TEST_DAPARG,
112                                 '-p', str(TEST_GDB_PORT),
113                                 '-T', str(TEST_TELNET_PORT),
114                                 '-t', TEST_TARGET,
115                                 '-u', TEST_DEV_ID,
116                                 '-f', TEST_FREQUENCY] + TEST_TOOL_OPTS
117DEBUGSERVER_DEF_EXPECTED_CALL = ['pyocd',
118                                 'gdbserver',
119                                 '-p', '3333',
120                                 '-T', '4444',
121                                 '-t', TEST_TARGET]
122
123
124#
125# Fixtures
126#
127
128@pytest.fixture
129def pyocd(runner_config, tmpdir):
130    '''PyOcdBinaryRunner from constructor kwargs or command line parameters'''
131    # This factory takes either a dict of kwargs to pass to the
132    # constructor, or a list of command-line arguments to parse and
133    # use with the create() method.
134    def _factory(args):
135        # Ensure kernel binaries exist (as empty files, so commands
136        # which use them must be patched out).
137        tmpdir.ensure(RC_KERNEL_HEX)
138        tmpdir.ensure(RC_KERNEL_ELF)
139        tmpdir.chdir()
140
141        if isinstance(args, dict):
142            return PyOcdBinaryRunner(runner_config, TEST_TARGET, **args)
143        elif isinstance(args, list):
144            parser = argparse.ArgumentParser(allow_abbrev=False)
145            PyOcdBinaryRunner.add_parser(parser)
146            arg_namespace = parser.parse_args(args)
147            return PyOcdBinaryRunner.create(runner_config, arg_namespace)
148    return _factory
149
150
151#
152# Helpers
153#
154
155def require_patch(program):
156    assert program in ['pyocd', TEST_PYOCD, RC_GDB]
157
158
159#
160# Test cases for runners created by constructor.
161#
162
163@pytest.mark.parametrize('pyocd_args,expected', [
164    (TEST_ALL_KWARGS, FLASH_ALL_EXPECTED_CALL),
165    (TEST_DEF_KWARGS, FLASH_DEF_EXPECTED_CALL)
166])
167@patch('runners.pyocd.PyOcdBinaryRunner.check_call')
168@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
169def test_flash(require, cc, pyocd_args, expected, pyocd):
170    pyocd(pyocd_args).run('flash')
171    assert require.called
172    cc.assert_called_once_with(expected)
173
174
175@pytest.mark.parametrize('pyocd_args,expectedv', [
176    (TEST_ALL_KWARGS, (DEBUG_ALL_EXPECTED_SERVER, DEBUG_ALL_EXPECTED_CLIENT)),
177    (TEST_DEF_KWARGS, (DEBUG_DEF_EXPECTED_SERVER, DEBUG_DEF_EXPECTED_CLIENT))
178])
179@patch('runners.pyocd.PyOcdBinaryRunner.run_server_and_client')
180@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
181def test_debug(require, rsc, pyocd_args, expectedv, pyocd):
182    pyocd(pyocd_args).run('debug')
183    assert require.called
184    rsc.assert_called_once_with(*expectedv)
185
186
187@pytest.mark.parametrize('pyocd_args,expected', [
188    (TEST_ALL_KWARGS, DEBUGSERVER_ALL_EXPECTED_CALL),
189    (TEST_DEF_KWARGS, DEBUGSERVER_DEF_EXPECTED_CALL)
190])
191@patch('runners.pyocd.PyOcdBinaryRunner.check_call')
192@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
193def test_debugserver(require, cc, pyocd_args, expected, pyocd):
194    pyocd(pyocd_args).run('debugserver')
195    assert require.called
196    cc.assert_called_once_with(expected)
197
198
199#
200# Test cases for runners created via command line arguments.
201#
202# (Unlike the constructor tests, these require additional patching to mock and
203# verify runners.core.BuildConfiguration usage.)
204#
205
206@pytest.mark.parametrize('pyocd_args,flash_addr,expected', [
207    (TEST_ALL_PARAMS, TEST_ADDR, FLASH_ALL_EXPECTED_CALL),
208    (TEST_DEF_PARAMS, 0x0, FLASH_DEF_EXPECTED_CALL)
209])
210@patch('runners.pyocd.BuildConfiguration')
211@patch('runners.pyocd.PyOcdBinaryRunner.check_call')
212@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
213def test_flash_args(require, cc, bc, pyocd_args, flash_addr, expected, pyocd):
214    with patch.object(PyOcdBinaryRunner, 'get_flash_address',
215                      return_value=flash_addr):
216        pyocd(pyocd_args).run('flash')
217        assert require.called
218        bc.assert_called_once_with(RC_BUILD_DIR)
219        cc.assert_called_once_with(expected)
220
221
222@pytest.mark.parametrize('pyocd_args, expectedv', [
223    (TEST_ALL_PARAMS, (DEBUG_ALL_EXPECTED_SERVER, DEBUG_ALL_EXPECTED_CLIENT)),
224    (TEST_DEF_PARAMS, (DEBUG_DEF_EXPECTED_SERVER, DEBUG_DEF_EXPECTED_CLIENT)),
225])
226@patch('runners.pyocd.BuildConfiguration')
227@patch('runners.pyocd.PyOcdBinaryRunner.run_server_and_client')
228@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
229def test_debug_args(require, rsc, bc, pyocd_args, expectedv, pyocd):
230    pyocd(pyocd_args).run('debug')
231    assert require.called
232    bc.assert_called_once_with(RC_BUILD_DIR)
233    rsc.assert_called_once_with(*expectedv)
234
235
236@pytest.mark.parametrize('pyocd_args, expected', [
237    (TEST_ALL_PARAMS, DEBUGSERVER_ALL_EXPECTED_CALL),
238    (TEST_DEF_PARAMS, DEBUGSERVER_DEF_EXPECTED_CALL),
239])
240@patch('runners.pyocd.BuildConfiguration')
241@patch('runners.pyocd.PyOcdBinaryRunner.check_call')
242@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
243def test_debugserver_args(require, cc, bc, pyocd_args, expected, pyocd):
244    pyocd(pyocd_args).run('debugserver')
245    assert require.called
246    bc.assert_called_once_with(RC_BUILD_DIR)
247    cc.assert_called_once_with(expected)
248