diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/device/device_adapter.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/device/device_adapter.py index b5560650c1b..3f73045f899 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/device/device_adapter.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/device/device_adapter.py @@ -14,6 +14,7 @@ import threading import time from datetime import datetime from pathlib import Path +from serial import SerialException from twister_harness.exceptions import ( TwisterHarnessException, @@ -69,14 +70,23 @@ class DeviceAdapter(abc.ABC): if self.device_config.type != 'hardware': self._flash_and_run() + self._device_run.set() + self._start_reader_thread() + self.connect() + return self._device_run.set() self._start_reader_thread() - self.connect() - if self.device_config.type == 'hardware': + if self.device_config.flash_before: + # For hardware devices with shared USB or software USB, connect after flashing. + # Retry for up to 10 seconds for USB-CDC based devices to enumerate. + self._flash_and_run() + self.connect(retry_s = 10) + else: # On hardware, flash after connecting to COM port, otherwise some messages # from target can be lost. + self.connect() self._flash_and_run() def close(self) -> None: @@ -89,7 +99,7 @@ class DeviceAdapter(abc.ABC): self._device_run.clear() self._join_reader_thread() - def connect(self) -> None: + def connect(self, retry_s: int = 0) -> None: """Connect to device - allow for output gathering.""" if self.is_device_connected(): logger.debug('Device already connected') @@ -98,7 +108,20 @@ class DeviceAdapter(abc.ABC): msg = 'Cannot connect to not working device' logger.error(msg) raise TwisterHarnessException(msg) - self._connect_device() + + if retry_s > 0: + retry_cycles = retry_s * 10 + for i in range(retry_cycles): + try: + self._connect_device() + break + except SerialException: + if i == retry_cycles - 1: + raise + time.sleep(0.1) + else: + self._connect_device() + self._device_connected.set() def disconnect(self) -> None: diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py index 34a09aa38f5..60537608357 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py @@ -84,6 +84,13 @@ def pytest_addoption(parser: pytest.Parser): '--device-serial-pty', help='Script for controlling pseudoterminal.' ) + twister_harness_group.addoption( + '--flash-before', + type=bool, + help='Flash device before attaching to serial port' + 'This is useful for devices that share the same port for programming' + 'and serial console, or use soft-USB, where flash must come first.' + ) twister_harness_group.addoption( '--west-flash-extra-args', help='Extend parameters for west flash. ' diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/twister_harness_config.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/twister_harness_config.py index ece190c630f..6ab26c3e8f4 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/twister_harness_config.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/twister_harness_config.py @@ -26,6 +26,7 @@ class DeviceConfig: id: str = '' product: str = '' serial_pty: str = '' + flash_before: bool = False west_flash_extra_args: list[str] = field(default_factory=list, repr=False) name: str = '' pre_script: Path | None = None @@ -62,6 +63,7 @@ class TwisterHarnessConfig: id=config.option.device_id, product=config.option.device_product, serial_pty=config.option.device_serial_pty, + flash_before=bool(config.option.flash_before), west_flash_extra_args=west_flash_extra_args, pre_script=_cast_to_path(config.option.pre_script), post_script=_cast_to_path(config.option.post_script), diff --git a/scripts/pylib/twister/twisterlib/environment.py b/scripts/pylib/twister/twisterlib/environment.py index dcd8fe9189c..85b7cdb371e 100644 --- a/scripts/pylib/twister/twisterlib/environment.py +++ b/scripts/pylib/twister/twisterlib/environment.py @@ -178,7 +178,7 @@ Artificially long but functional example: parser.add_argument("--flash-before", action="store_true", default=False, help="""Flash device before attaching to serial port. This is useful for devices that share the same port for programming - and serial console, where flash must come first. + and serial console, or use soft-USB, where flash must come first. """) test_or_build.add_argument( diff --git a/scripts/pylib/twister/twisterlib/harness.py b/scripts/pylib/twister/twisterlib/harness.py index 6fd6dae73a8..b0925c0fce0 100644 --- a/scripts/pylib/twister/twisterlib/harness.py +++ b/scripts/pylib/twister/twisterlib/harness.py @@ -408,6 +408,9 @@ class Pytest(Harness): if hardware.post_script: command.append(f'--post-script={hardware.post_script}') + if hardware.flash_before: + command.append(f'--flash-before={hardware.flash_before}') + return command def run_command(self, cmd, timeout):