1# Copyright (c) 2023 Nordic Semiconductor ASA 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import os 6import time 7from pathlib import Path 8from unittest import mock 9 10import pytest 11 12from twister_harness.device.hardware_adapter import HardwareAdapter 13from twister_harness.exceptions import TwisterHarnessException 14from twister_harness.twister_harness_config import DeviceConfig 15 16 17@pytest.fixture(name='device') 18def fixture_adapter(tmp_path) -> HardwareAdapter: 19 build_dir = tmp_path / 'build_dir' 20 os.mkdir(build_dir) 21 device_config = DeviceConfig( 22 type='hardware', 23 build_dir=build_dir, 24 runner='runner', 25 platform='platform', 26 id='p_id', 27 base_timeout=5.0, 28 ) 29 return HardwareAdapter(device_config) 30 31 32@mock.patch('shutil.which', return_value=None) 33def test_if_hardware_adapter_raise_exception_when_west_not_found(patched_which, device: HardwareAdapter) -> None: 34 with pytest.raises(TwisterHarnessException, match='west not found'): 35 device.generate_command() 36 37 38@mock.patch('shutil.which', return_value='west') 39def test_if_get_command_returns_proper_string_1(patched_which, device: HardwareAdapter) -> None: 40 device.device_config.build_dir = Path('build') 41 device.generate_command() 42 assert isinstance(device.command, list) 43 assert device.command == ['west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'runner'] 44 45 46@mock.patch('shutil.which', return_value='west') 47def test_if_get_command_returns_proper_string_2(patched_which, device: HardwareAdapter) -> None: 48 device.device_config.build_dir = Path('build') 49 device.device_config.runner = 'pyocd' 50 device.generate_command() 51 assert isinstance(device.command, list) 52 assert device.command == [ 53 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'pyocd', '--', '--board-id', 'p_id' 54 ] 55 56 57@mock.patch('shutil.which', return_value='west') 58def test_if_get_command_returns_proper_string_3(patched_which, device: HardwareAdapter) -> None: 59 device.device_config.build_dir = Path('build') 60 device.device_config.runner = 'nrfjprog' 61 device.generate_command() 62 assert isinstance(device.command, list) 63 assert device.command == [ 64 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'nrfjprog', '--', '--dev-id', 'p_id' 65 ] 66 67 68@mock.patch('shutil.which', return_value='west') 69def test_if_get_command_returns_proper_string_4(patched_which, device: HardwareAdapter) -> None: 70 device.device_config.build_dir = Path('build') 71 device.device_config.runner = 'openocd' 72 device.device_config.product = 'STM32 STLink' 73 device.generate_command() 74 assert isinstance(device.command, list) 75 assert device.command == [ 76 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'openocd', 77 '--', '--cmd-pre-init', 'hla_serial p_id' 78 ] 79 80 81@mock.patch('shutil.which', return_value='west') 82def test_if_get_command_returns_proper_string_5(patched_which, device: HardwareAdapter) -> None: 83 device.device_config.build_dir = Path('build') 84 device.device_config.runner = 'openocd' 85 device.device_config.product = 'EDBG CMSIS-DAP' 86 device.generate_command() 87 assert isinstance(device.command, list) 88 assert device.command == [ 89 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'openocd', 90 '--', '--cmd-pre-init', 'cmsis_dap_serial p_id' 91 ] 92 93 94@mock.patch('shutil.which', return_value='west') 95def test_if_get_command_returns_proper_string_6(patched_which, device: HardwareAdapter) -> None: 96 device.device_config.build_dir = Path('build') 97 device.device_config.runner = 'jlink' 98 device.generate_command() 99 assert isinstance(device.command, list) 100 assert device.command == [ 101 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'jlink', 102 '--dev-id', 'p_id' 103 ] 104 105 106@mock.patch('shutil.which', return_value='west') 107def test_if_get_command_returns_proper_string_7(patched_which, device: HardwareAdapter) -> None: 108 device.device_config.build_dir = Path('build') 109 device.device_config.runner = 'stm32cubeprogrammer' 110 device.generate_command() 111 assert isinstance(device.command, list) 112 assert device.command == [ 113 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'stm32cubeprogrammer', 114 '--tool-opt=sn=p_id' 115 ] 116 117 118@mock.patch('shutil.which', return_value='west') 119def test_if_get_command_returns_proper_string_8(patched_which, device: HardwareAdapter) -> None: 120 device.device_config.build_dir = Path('build') 121 device.device_config.runner = 'openocd' 122 device.device_config.product = 'STLINK-V3' 123 device.generate_command() 124 assert isinstance(device.command, list) 125 assert device.command == [ 126 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', 127 '--runner', 'openocd', '--', '--cmd-pre-init', 'hla_serial p_id' 128 ] 129 130 131@mock.patch('shutil.which', return_value='west') 132def test_if_get_command_returns_proper_string_with_runner_params_1(patched_which, device: HardwareAdapter) -> None: 133 device.device_config.build_dir = Path('build') 134 device.device_config.runner_params = ['--runner-param1', 'runner-param2'] 135 device.generate_command() 136 assert isinstance(device.command, list) 137 assert device.command == [ 138 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', 139 '--runner', 'runner', '--', '--runner-param1', 'runner-param2' 140 ] 141 142 143@mock.patch('shutil.which', return_value='west') 144def test_if_get_command_returns_proper_string_with_runner_params_2(patched_which, device: HardwareAdapter) -> None: 145 device.device_config.build_dir = Path('build') 146 device.device_config.runner = 'openocd' 147 device.device_config.runner_params = [ 148 '--cmd-pre-init', 'adapter serial FT1LRSRD', 149 '--cmd-pre-init', 'source [find interface/ftdi/jtag-lock-pick_tiny_2.cfg]', 150 '--cmd-pre-init', 'transport select swd', 151 '--cmd-pre-init', 'source [find target/nrf52.cfg]', 152 '--cmd-pre-init', 'adapter speed 10000', 153 ] 154 device.device_config.product = 'JTAG-lock-pick Tiny 2' 155 device.generate_command() 156 assert isinstance(device.command, list) 157 assert device.command == [ 158 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', 159 '--runner', 'openocd', '--', 160 '--cmd-pre-init', 'adapter serial FT1LRSRD', 161 '--cmd-pre-init', 'source [find interface/ftdi/jtag-lock-pick_tiny_2.cfg]', 162 '--cmd-pre-init', 'transport select swd', 163 '--cmd-pre-init', 'source [find target/nrf52.cfg]', 164 '--cmd-pre-init', 'adapter speed 10000', 165 ] 166 167 168@mock.patch('shutil.which', return_value='west') 169def test_if_get_command_returns_proper_string_with_west_flash_extra_args( 170 patched_which, device: HardwareAdapter 171) -> None: 172 device.device_config.build_dir = Path('build') 173 device.device_config.west_flash_extra_args = ['--board-id=foobar', '--erase'] 174 device.device_config.runner = 'pyocd' 175 device.device_config.id = '' 176 device.generate_command() 177 assert isinstance(device.command, list) 178 assert device.command == [ 179 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'pyocd', 180 '--', '--board-id=foobar', '--erase' 181 ] 182 183 184def test_if_hardware_adapter_raises_exception_empty_command(device: HardwareAdapter) -> None: 185 device.command = [] 186 exception_msg = 'Flash command is empty, please verify if it was generated properly.' 187 with pytest.raises(TwisterHarnessException, match=exception_msg): 188 device._flash_and_run() 189 190 191@mock.patch('twister_harness.device.hardware_adapter.subprocess.Popen') 192def test_device_log_correct_error_handle(patched_popen, device: HardwareAdapter, tmp_path: Path) -> None: 193 popen_mock = mock.Mock() 194 popen_mock.communicate.return_value = (b'flashing error', b'') 195 patched_popen.return_value = popen_mock 196 device.device_config.build_dir = tmp_path 197 device.command = [ 198 'west', 'flash', '--skip-rebuild', '--build-dir', str(tmp_path), 199 '--runner', 'nrfjprog', '--', '--dev-id', 'p_id' 200 ] 201 with pytest.raises(expected_exception=TwisterHarnessException, match='Could not flash device p_id'): 202 device._flash_and_run() 203 assert os.path.isfile(device.device_log_path) 204 with open(device.device_log_path, 'r') as file: 205 assert 'flashing error' in file.readlines() 206 207 208@mock.patch('twister_harness.device.hardware_adapter.subprocess.Popen') 209@mock.patch('twister_harness.device.hardware_adapter.serial.Serial') 210def test_if_hardware_adapter_uses_serial_pty( 211 patched_serial, patched_popen, device: HardwareAdapter, monkeypatch: pytest.MonkeyPatch 212): 213 device.device_config.serial_pty = 'script.py' 214 215 popen_mock = mock.Mock() 216 popen_mock.communicate.return_value = (b'output', b'error') 217 patched_popen.return_value = popen_mock 218 219 monkeypatch.setattr('twister_harness.device.hardware_adapter.pty.openpty', lambda: (123, 456)) 220 monkeypatch.setattr('twister_harness.device.hardware_adapter.os.ttyname', lambda x: f'/pty/ttytest/{x}') 221 222 serial_mock = mock.Mock() 223 serial_mock.port = '/pty/ttytest/456' 224 patched_serial.return_value = serial_mock 225 226 device._device_run.set() 227 device.connect() 228 assert device._serial_connection.port == '/pty/ttytest/456' # type: ignore[union-attr] 229 assert device._serial_pty_proc 230 patched_popen.assert_called_with( 231 ['script.py'], 232 stdout=123, 233 stdin=123, 234 stderr=123 235 ) 236 237 device.disconnect() 238 assert not device._serial_pty_proc 239 240 241def test_if_hardware_adapter_properly_send_data_to_subprocess( 242 device: HardwareAdapter, shell_simulator_path: str 243) -> None: 244 """ 245 Run shell_simulator.py under serial_pty, send "zen" command and verify 246 output. Flashing command is mocked by "dummy" echo command. 247 """ 248 device.command = ['echo', 'TEST'] # only to mock flashing command 249 device.device_config.serial_pty = f'python3 {shell_simulator_path}' 250 device.launch() 251 time.sleep(0.1) 252 device.write(b'zen\n') 253 time.sleep(1) 254 lines = device.readlines_until(regex='Namespaces are one honking great idea') 255 assert 'The Zen of Python, by Tim Peters' in lines 256 device.write(b'quit\n') 257