1# Copyright (c) 2018 Foundries.io
2# Copyright (c) 2019 Nordic Semiconductor ASA.
3#
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7import os
8from unittest.mock import patch, call
9
10import pytest
11
12from runners.dfu import DfuUtilBinaryRunner, DfuSeConfig
13from conftest import RC_KERNEL_BIN
14
15DFU_UTIL = 'dfu-util'
16TEST_EXE = 'test-dfu-util'
17TEST_PID = '0000:9999'
18TEST_PID_RES = '-d,{}'.format(TEST_PID)
19TEST_ALT_INT = '1'
20TEST_ALT_STR = 'alt-name'
21TEST_BIN_NAME = 'test-img.bin'
22TEST_DFUSE_ADDR = 2
23TEST_DFUSE_OPTS = 'test-dfuse-opt'
24TEST_DCFG_OPT = DfuSeConfig(address=TEST_DFUSE_ADDR, options='test-dfuse-opt')
25TEST_DCFG_OPT_RES = '{}:{}'.format(hex(TEST_DFUSE_ADDR), TEST_DFUSE_OPTS)
26TEST_DCFG_NOPT = DfuSeConfig(address=TEST_DFUSE_ADDR, options='')
27TEST_DCFG_NOPT_RES = '{}:'.format(hex(TEST_DFUSE_ADDR))
28# A map from a test case to the expected dfu-util call.
29# Test cases are (alt, exe, img, dfuse) tuples.
30EXPECTED_COMMAND = {
31    (DFU_UTIL, TEST_ALT_INT, None, RC_KERNEL_BIN):
32    [DFU_UTIL, TEST_PID_RES, '-a', TEST_ALT_INT, '-D', RC_KERNEL_BIN],
33
34    (DFU_UTIL, TEST_ALT_STR, None, RC_KERNEL_BIN):
35    [DFU_UTIL, TEST_PID_RES, '-a', TEST_ALT_STR, '-D', RC_KERNEL_BIN],
36
37    (TEST_EXE, TEST_ALT_INT, None, RC_KERNEL_BIN):
38    [TEST_EXE, TEST_PID_RES, '-a', TEST_ALT_INT, '-D', RC_KERNEL_BIN],
39
40    (DFU_UTIL, TEST_ALT_INT, None, TEST_BIN_NAME):
41    [DFU_UTIL, TEST_PID_RES, '-a', TEST_ALT_INT, '-D', TEST_BIN_NAME],
42
43    (DFU_UTIL, TEST_ALT_INT, TEST_DCFG_OPT, RC_KERNEL_BIN):
44    [DFU_UTIL, TEST_PID_RES, '-s', TEST_DCFG_OPT_RES, '-a', TEST_ALT_INT,
45     '-D', RC_KERNEL_BIN],
46
47    (DFU_UTIL, TEST_ALT_INT, TEST_DCFG_NOPT, RC_KERNEL_BIN):
48    [DFU_UTIL, TEST_PID_RES, '-s', TEST_DCFG_NOPT_RES, '-a', TEST_ALT_INT,
49     '-D', RC_KERNEL_BIN],
50}
51
52def find_device_patch():
53    return True
54
55def require_patch(program):
56    assert program in [DFU_UTIL, TEST_EXE]
57
58os_path_isfile = os.path.isfile
59
60def os_path_isfile_patch(filename):
61    if filename == RC_KERNEL_BIN:
62        return True
63    return os_path_isfile(filename)
64
65def id_fn(tc):
66    return 'exe={},alt={},dfuse_config={},img={}'.format(*tc)
67
68@pytest.mark.parametrize('tc', [
69    # (exe, alt, dfuse_config, img)
70    (DFU_UTIL, TEST_ALT_INT, None, RC_KERNEL_BIN),
71    (DFU_UTIL, TEST_ALT_STR, None, RC_KERNEL_BIN),
72    (TEST_EXE, TEST_ALT_INT, None, RC_KERNEL_BIN),
73    (DFU_UTIL, TEST_ALT_INT, None, TEST_BIN_NAME),
74    (DFU_UTIL, TEST_ALT_INT, TEST_DCFG_OPT, RC_KERNEL_BIN),
75    (DFU_UTIL, TEST_ALT_INT, TEST_DCFG_NOPT, RC_KERNEL_BIN),
76], ids=id_fn)
77@patch('runners.dfu.DfuUtilBinaryRunner.find_device',
78       side_effect=find_device_patch)
79@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
80@patch('runners.core.ZephyrBinaryRunner.check_call')
81def test_dfu_util_init(cc, req, find_device, tc, runner_config):
82    '''Test commands using a runner created by constructor.'''
83    exe, alt, dfuse_config, img = tc
84    runner = DfuUtilBinaryRunner(runner_config, TEST_PID, alt, img, exe=exe,
85                                 dfuse_config=dfuse_config)
86    with patch('os.path.isfile', side_effect=os_path_isfile_patch):
87        runner.run('flash')
88    assert find_device.called
89    assert req.called_with(exe)
90    assert cc.call_args_list == [call(EXPECTED_COMMAND[tc])]
91
92def get_flash_address_patch(args, bcfg):
93    return TEST_DFUSE_ADDR
94
95@pytest.mark.parametrize('tc', [
96    # arg spec: (exe, alt, dfuse, modifiers, img)
97    (None, TEST_ALT_INT, False, None, None),
98    (None, TEST_ALT_STR, False, None, None),
99    (TEST_EXE, TEST_ALT_INT, False, None, None),
100    (None, TEST_ALT_INT, False, None, TEST_BIN_NAME),
101    (None, TEST_ALT_INT, True, TEST_DFUSE_OPTS, None),
102    (None, TEST_ALT_INT, True, None, None),
103
104], ids=id_fn)
105@patch('runners.dfu.DfuUtilBinaryRunner.find_device',
106       side_effect=find_device_patch)
107@patch('runners.core.ZephyrBinaryRunner.get_flash_address',
108       side_effect=get_flash_address_patch)
109@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
110@patch('runners.core.ZephyrBinaryRunner.check_call')
111def test_dfu_util_create(cc, req, gfa, find_device, tc, runner_config, tmpdir):
112    '''Test commands using a runner created from command line parameters.'''
113    exe, alt, dfuse, modifiers, img = tc
114    args = ['--pid', TEST_PID, '--alt', alt]
115    if img:
116        args.extend(['--img', img])
117    if dfuse:
118        args.append('--dfuse')
119        if modifiers:
120            args.extend(['--dfuse-modifiers', modifiers])
121        else:
122            args.extend(['--dfuse-modifiers', ''])
123    if exe:
124        args.extend(['--dfu-util', exe])
125
126    (tmpdir / 'zephyr').mkdir()
127    with open(os.fspath(tmpdir / 'zephyr' / '.config'), 'w') as f:
128        f.write('\n')
129    runner_config = runner_config._replace(build_dir=os.fspath(tmpdir))
130
131    parser = argparse.ArgumentParser()
132    DfuUtilBinaryRunner.add_parser(parser)
133    arg_namespace = parser.parse_args(args)
134    runner = DfuUtilBinaryRunner.create(runner_config, arg_namespace)
135    with patch('os.path.isfile', side_effect=os_path_isfile_patch):
136        runner.run('flash')
137
138    if dfuse:
139        cfg = DfuSeConfig(address=TEST_DFUSE_ADDR, options=modifiers or '')
140    else:
141        cfg = None
142    map_tc = (exe or DFU_UTIL, alt, cfg, img or RC_KERNEL_BIN)
143    assert find_device.called
144    assert req.called_with(exe)
145    assert cc.call_args_list == [call(EXPECTED_COMMAND[map_tc])]
146