| # Copyright (c) 2023 Nordic Semiconductor ASA |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import os |
| import time |
| from pathlib import Path |
| from unittest import mock |
| |
| import pytest |
| |
| from twister_harness.device.hardware_adapter import HardwareAdapter |
| from twister_harness.exceptions import TwisterHarnessException |
| from twister_harness.twister_harness_config import DeviceConfig |
| |
| |
| @pytest.fixture(name='device') |
| def fixture_adapter(tmp_path) -> HardwareAdapter: |
| build_dir = tmp_path / 'build_dir' |
| os.mkdir(build_dir) |
| device_config = DeviceConfig( |
| type='hardware', |
| build_dir=build_dir, |
| runner='runner', |
| platform='platform', |
| id='p_id', |
| base_timeout=5.0, |
| ) |
| return HardwareAdapter(device_config) |
| |
| |
| @mock.patch('shutil.which', return_value=None) |
| def test_if_hardware_adapter_raise_exception_when_west_not_found(patched_which, device: HardwareAdapter) -> None: |
| with pytest.raises(TwisterHarnessException, match='west not found'): |
| device.generate_command() |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_1(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == ['west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'runner'] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_2(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'pyocd' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'pyocd', '--', '--board-id', 'p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_3(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'nrfjprog' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'nrfjprog', '--', '--dev-id', 'p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_4(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'openocd' |
| device.device_config.product = 'STM32 STLink' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'openocd', |
| '--', '--cmd-pre-init', 'hla_serial p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_5(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'openocd' |
| device.device_config.product = 'EDBG CMSIS-DAP' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'openocd', |
| '--', '--cmd-pre-init', 'cmsis_dap_serial p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_6(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'jlink' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'jlink', |
| '--tool-opt=-SelectEmuBySN p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_7(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'stm32cubeprogrammer' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'stm32cubeprogrammer', |
| '--tool-opt=sn=p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_8(patched_which, device: HardwareAdapter) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.runner = 'openocd' |
| device.device_config.product = 'STLINK-V3' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', |
| '--runner', 'openocd', '--', '--cmd-pre-init', 'hla_serial p_id' |
| ] |
| |
| |
| @mock.patch('shutil.which', return_value='west') |
| def test_if_get_command_returns_proper_string_with_west_flash_extra_args( |
| patched_which, device: HardwareAdapter |
| ) -> None: |
| device.device_config.build_dir = Path('build') |
| device.device_config.west_flash_extra_args = ['--board-id=foobar', '--erase'] |
| device.device_config.runner = 'pyocd' |
| device.device_config.id = '' |
| device.generate_command() |
| assert isinstance(device.command, list) |
| assert device.command == [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', 'build', '--runner', 'pyocd', |
| '--', '--board-id=foobar', '--erase' |
| ] |
| |
| |
| def test_if_hardware_adapter_raises_exception_empty_command(device: HardwareAdapter) -> None: |
| device.command = [] |
| exception_msg = 'Flash command is empty, please verify if it was generated properly.' |
| with pytest.raises(TwisterHarnessException, match=exception_msg): |
| device._flash_and_run() |
| |
| |
| @mock.patch('twister_harness.device.hardware_adapter.subprocess.Popen') |
| def test_device_log_correct_error_handle(patched_popen, device: HardwareAdapter, tmp_path: Path) -> None: |
| popen_mock = mock.Mock() |
| popen_mock.communicate.return_value = (b'flashing error', b'') |
| patched_popen.return_value = popen_mock |
| device.device_config.build_dir = tmp_path |
| device.command = [ |
| 'west', 'flash', '--skip-rebuild', '--build-dir', str(tmp_path), |
| '--runner', 'nrfjprog', '--', '--dev-id', 'p_id' |
| ] |
| with pytest.raises(expected_exception=TwisterHarnessException, match='Could not flash device p_id'): |
| device._flash_and_run() |
| assert os.path.isfile(device.device_log_path) |
| with open(device.device_log_path, 'r') as file: |
| assert 'flashing error' in file.readlines() |
| |
| |
| @mock.patch('twister_harness.device.hardware_adapter.subprocess.Popen') |
| @mock.patch('twister_harness.device.hardware_adapter.serial.Serial') |
| def test_if_hardware_adapter_uses_serial_pty( |
| patched_serial, patched_popen, device: HardwareAdapter, monkeypatch: pytest.MonkeyPatch |
| ): |
| device.device_config.serial_pty = 'script.py' |
| |
| popen_mock = mock.Mock() |
| popen_mock.communicate.return_value = (b'output', b'error') |
| patched_popen.return_value = popen_mock |
| |
| monkeypatch.setattr('twister_harness.device.hardware_adapter.pty.openpty', lambda: (123, 456)) |
| monkeypatch.setattr('twister_harness.device.hardware_adapter.os.ttyname', lambda x: f'/pty/ttytest/{x}') |
| |
| serial_mock = mock.Mock() |
| serial_mock.port = '/pty/ttytest/456' |
| patched_serial.return_value = serial_mock |
| |
| device._device_run.set() |
| device.connect() |
| assert device._serial_connection.port == '/pty/ttytest/456' # type: ignore[union-attr] |
| assert device._serial_pty_proc |
| patched_popen.assert_called_with( |
| ['script.py'], |
| stdout=123, |
| stdin=123, |
| stderr=123 |
| ) |
| |
| device.disconnect() |
| assert not device._serial_pty_proc |
| |
| |
| def test_if_hardware_adapter_properly_send_data_to_subprocess( |
| device: HardwareAdapter, shell_simulator_path: str |
| ) -> None: |
| """ |
| Run shell_simulator.py under serial_pty, send "zen" command and verify |
| output. Flashing command is mocked by "dummy" echo command. |
| """ |
| device.command = ['echo', 'TEST'] # only to mock flashing command |
| device.device_config.serial_pty = f'python3 {shell_simulator_path}' |
| device.launch() |
| time.sleep(0.1) |
| device.write(b'zen\n') |
| time.sleep(1) |
| lines = device.readlines_until(regex='Namespaces are one honking great idea') |
| assert 'The Zen of Python, by Tim Peters' in lines |
| device.write(b'quit\n') |