From deb47c749db06870dd9f59e79c2158fffad2e24f Mon Sep 17 00:00:00 2001 From: Marko Sagadin Date: Mon, 22 Sep 2025 09:13:58 +0200 Subject: [PATCH 1/3] scripts: west_commands: runners: fix west rtt shell interaction When the west rtt command is executed, two things happen: - A GDB server is run. - A telnet client (via sockets) is opened to connect to that server. Previously, that telnet client was operating in the line mode in both directions. That mode is perfectly fine for just presenting the captured content (RTT data) to the stdout; however, it is not suitable for interacting with the device via the shell subsystem. None of the control commands, such as tab and arrow keys, were correctly passed down to the device. To solve that the telnet client had to be swithced into the character mode. For that two separate implementations were needed, one for the Unix and one for Windows. Signed-off-by: Marko Sagadin --- scripts/west_commands/runners/core.py | 145 +++++++++++++++++++++++--- 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py index 611848f671cd9..e4b0fe8e4e669 100644 --- a/scripts/west_commands/runners/core.py +++ b/scripts/west_commands/runners/core.py @@ -13,6 +13,7 @@ import abc import argparse +import contextlib import errno import logging import os @@ -965,20 +966,138 @@ def run_telnet_client(self, host: str, port: int, active_sock=None) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) - # Otherwise, use a pure python implementation. This will work well for logging, - # but input is line based only. + if platform.system() == 'Windows': + self._windows_telnet_client(sock) + else: + self._unix_telnet_client(sock) + + + def _unix_telnet_client(self, sock: socket.socket) -> None: + """ + Start a telnet client on Unix. + + Selectors are used to register both the given socket and the stdin and wait on + incoming data, to later process it. + """ + import termios + import tty + sel = selectors.DefaultSelector() sel.register(sys.stdin, selectors.EVENT_READ) sel.register(sock, selectors.EVENT_READ) - while True: - events = sel.select() - for key, _ in events: - if key.fileobj == sys.stdin: - text = sys.stdin.readline() - if text: - sock.send(text.encode()) - - elif key.fileobj == sock: + + + # We can't type check functions from tty and termios on Windows operating + # systems: mypy thinks the modules have no such attributes. + fd = sys.stdin.fileno() + orig_attr = termios.tcgetattr(fd) # type: ignore + try: + # Enable cbreak mode on the keyboard input, that way all shell editing + # commands (arrow keys, backspace, etc.) work as expected. + tty.setcbreak(fd) # type: ignore + while True: + events = sel.select() + for key, _ in events: + if key.fileobj == sys.stdin: + data = os.read(fd, 2048) + if data: + sock.send(data) + + elif key.fileobj == sock: + resp = sock.recv(2048) + if resp: + print(resp.decode(), end='', flush=True) + except OSError as e: + print("\n\nCommunication was closed, reason:", e) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, orig_attr) # type: ignore + + + def _windows_telnet_client(self, sock): + """ + Start a telnet client on Windows. + + Since we don't have tty package on the Windows, a different approach to + establish two-way communction: + - Two threads are started. + - One uses the kbhit() and getch() from msvcrt package to read every single + character from input. + - The other one reads data from the socket and prints it to the screen. + + Additional logic is need to convert arrow keys and navigation keys into + corresponding ANSI escape sequences + Additional logic with the stop event is needed to handle CTRL+C and any errors. + """ + import msvcrt + import threading + + def send_keyboard_input(sock: socket.socket, + stop: threading.Event, + err: threading.Event): + KEY_MAP = { + # Arrow keys + b'\xe0H': b'\x1b[A', # Up + b'\xe0P': b'\x1b[B', # Down + b'\xe0K': b'\x1b[D', # Left + b'\xe0M': b'\x1b[C', # Right + + # Navigation keys + b'\xe0G': b'\x1b[H', # Home + b'\xe0O': b'\x1b[F', # End + b'\xe0R': b'\x1b[2~', # Insert + b'\xe0S': b'\x1b[3~', # Delete + } + + try: + # We can't type check functions from msvcrt on Unix operating systems: + # mypy thinks the module has no such attributes. + while not stop.is_set(): + if msvcrt.kbhit(): # type: ignore + ch = msvcrt.getch() # type: ignore + if ch == b'\xe0': + ch2 = msvcrt.getch() # type: ignore + if ansi := KEY_MAP.get(ch + ch2): + sock.sendall(ansi) + elif ch == b'\r': + # Normalize CR to CRLF + sock.sendall(b"\r\n") + else: + sock.sendall(ch) + except OSError as e: + if not err.is_set() and not stop.is_set(): + err.set() + print("\n\nCommunication was closed, reason:", e) + stop.set() + + def receive_rtt_data(sock: socket.socket, + stop: threading.Event, + err: threading.Event): + try: + while not stop.is_set(): resp = sock.recv(2048) - if resp: - print(resp.decode(), end='') + if not resp: + print("\n\nConnection closed by the remote host, exiting!") + stop.set() + break + print(resp.decode(errors="replace"), end='', flush=True) + except OSError as e: + if not err.is_set() and not stop.is_set(): + err.set() + print("\n\nCommunication was closed, reason:", e) + stop.set() + + stop = threading.Event() + err = threading.Event() + threading.Thread(target=send_keyboard_input, args=(sock, stop, err)).start() + threading.Thread(target=receive_rtt_data, args=(sock, stop, err)).start() + + try: + while not stop.is_set(): + # Wait for an event in the loop. Without timeout the + # KeyboardInterrupt would not be detected. + stop.wait(timeout=0.1) + except KeyboardInterrupt: + stop.set() + finally: + with contextlib.suppress(OSError): + sock.close() From 27e92b39abb670fefc63bf09684b0f835d7fce92 Mon Sep 17 00:00:00 2001 From: Marko Sagadin Date: Mon, 22 Sep 2025 21:46:51 +0200 Subject: [PATCH 2/3] scripts: pytest: add RTT support in pytest-twister-harness This commit introduces --device-rtt flag in the Pytest Twister harness, allowing RTT as a communication protocol between the host PC and the DUT. To demonstrate this, a new build configuration was added to the pytest/shell sample. By following the newly added instructions in the scripts/pylib/pytest-twister-harness/README.rst, users can first build the pytest/shell sample with the RTT shell and then run the pytest with specific flags to run the tests via the RTT. Two additional notes related to the changes: - The way the pty process was terminated had to be changed. west rtt command spawns two subprocesses, and the previous implementation only terminated a single one. The already provided terminate_process function was used to terminate all child processes that pty process started. - Previously, exec_command sent out a command string, followed by two newline characters. The first one was meant to execute the command on the device, the second one was meant to produce the next prompt, which would signal that execution of the command finished. In some instances, when RTT was used, the device ignored the second newline, and thus the prompt was not emitted, bringing the function to a halt, until the timeout expired. This was noticed with the `kernel version` command, the one that is used in one of the tests in pytest/shell. Weirdly enough, that was the only instance of the "newline ignoring" that could be found. Testing `kernel uptime`, for example, worked fine. The workaround was to send a command string with a single newline, wait for the command print and then send the second newline. It was confirmed that this approach works both for the UART and RTT. Signed-off-by: Marko Sagadin --- .../testsuite/pytest/shell/testcase.yaml | 6 ++ .../pylib/pytest-twister-harness/README.rst | 4 ++ .../twister_harness/device/device_adapter.py | 5 +- .../device/hardware_adapter.py | 68 +++++++++++++------ .../src/twister_harness/fixtures.py | 5 +- .../src/twister_harness/helpers/shell.py | 18 +++-- .../src/twister_harness/plugin.py | 11 ++- .../twister_harness/twister_harness_config.py | 10 ++- .../tests/device/hardware_adapter_test.py | 5 +- 9 files changed, 99 insertions(+), 33 deletions(-) diff --git a/samples/subsys/testsuite/pytest/shell/testcase.yaml b/samples/subsys/testsuite/pytest/shell/testcase.yaml index 60b80817620d8..2e7690076af26 100644 --- a/samples/subsys/testsuite/pytest/shell/testcase.yaml +++ b/samples/subsys/testsuite/pytest/shell/testcase.yaml @@ -18,6 +18,12 @@ tests: harness: pytest extra_configs: - CONFIG_SHELL_VT100_COLORS=n + sample.pytest.rtt: + harness: pytest + extra_configs: + - CONFIG_USE_SEGGER_RTT=y + - CONFIG_LOG_MODE_DEFERRED=y + - CONFIG_SHELL_BACKEND_RTT=y sample.harness.shell: harness: shell harness_config: diff --git a/scripts/pylib/pytest-twister-harness/README.rst b/scripts/pylib/pytest-twister-harness/README.rst index 81e8300d14095..01d50c5ea92e4 100644 --- a/scripts/pylib/pytest-twister-harness/README.rst +++ b/scripts/pylib/pytest-twister-harness/README.rst @@ -45,3 +45,7 @@ or build shell application by west and call pytest directly: # hardware west build -p -b nrf52840dk/nrf52840 pytest --twister-harness --device-type=hardware --device-serial=/dev/ttyACM0 --build-dir=build -p twister_harness.plugin + + # hardware over RTT + west build -p -b nrf52840dk/nrf52840 . -T sample.pytest.rtt + pytest --twister-harness --device-type=hardware --device-rtt=True --build-dir=build -p twister_harness.plugin 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 87ecac0f75a9d..c38ee6526a566 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 @@ -81,8 +81,9 @@ def launch(self) -> None: self._start_reader_thread() 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. + # For hardware devices with shared USB or software USB or RTT, 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: diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/device/hardware_adapter.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/device/hardware_adapter.py index d0d200d6225ff..3c26f9ad6dd5e 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/device/hardware_adapter.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/device/hardware_adapter.py @@ -32,7 +32,7 @@ def __init__(self, device_config: DeviceConfig) -> None: super().__init__(device_config) self._flashing_timeout: float = device_config.flash_timeout self._serial_connection: serial.Serial | None = None - self._serial_pty_proc: subprocess.Popen | None = None + self._proc: subprocess.Popen | None = None self._serial_buffer: bytearray = bytearray() self.device_log_path: Path = device_config.build_dir / 'device.log' @@ -76,6 +76,25 @@ def generate_command(self) -> None: command.extend(command_extra_args) self.command = command + def generate_rtt_command(self): + """Return command to connect to the device via RTT.""" + + command = ["west", "rtt", "--skip-rebuild", "-d", + str(self.device_config.build_dir)] + + rtt_runner = self.device_config.rtt_runner + if rtt_runner: + command.append("--runner") + command.append(rtt_runner) + + if rtt_runner in ("jlink", "pyocd"): + command.append("--dev-id") + command.append(self.device_config.id) + + # Since _start_pty expects a string, we need to convert the command list into + # one. + return " ".join(command) + def _prepare_runner_args(self) -> tuple[list[str], list[str]]: base_args: list[str] = [] extra_args: list[str] = [] @@ -154,7 +173,13 @@ def _flash_and_run(self) -> None: raise TwisterHarnessException(msg) def _connect_device(self) -> None: - serial_name = self._open_serial_pty() or self.device_config.serial + if self.device_config.serial_pty: + serial_name = self._open_pty(self.device_config.serial_pty) + elif self.device_config.use_rtt: + serial_name = self._open_pty(self.generate_rtt_command()) + else: + serial_name = self.device_config.serial + logger.debug('Opening serial connection for %s', serial_name) try: self._serial_connection = serial.Serial( @@ -167,17 +192,15 @@ def _connect_device(self) -> None: ) except serial.SerialException as exc: logger.exception('Cannot open connection: %s', exc) - self._close_serial_pty() + self._close_pty() raise self._serial_connection.flush() self._serial_connection.reset_input_buffer() self._serial_connection.reset_output_buffer() - def _open_serial_pty(self) -> str | None: - """Open a pty pair, run process and return tty name""" - if not self.device_config.serial_pty: - return None + def _open_pty(self, command: str) -> str | None: + """Open a pty pair, run process with command and return tty name.""" try: master, slave = pty.openpty() @@ -186,14 +209,14 @@ def _open_serial_pty(self) -> str | None: raise exc try: - self._serial_pty_proc = subprocess.Popen( - re.split(',| ', self.device_config.serial_pty), + self._proc = subprocess.Popen( + re.split(',| ', command), stdout=master, stdin=master, stderr=master ) except subprocess.CalledProcessError as exc: - logger.exception('Failed to run subprocess %s, error %s', self.device_config.serial_pty, str(exc)) + logger.exception('Failed to run subprocess %s, error %s', command, str(exc)) raise return os.ttyname(slave) @@ -201,17 +224,22 @@ def _disconnect_device(self) -> None: if self._serial_connection: serial_name = self._serial_connection.port self._serial_connection.close() - # self._serial_connection = None logger.debug('Closed serial connection for %s', serial_name) - self._close_serial_pty() + self._close_pty() + + def _close_pty(self) -> None: + """Terminate the process opened for serial pty script or RTT.""" + if self._proc: + terminate_process(self._proc) + self._proc.communicate(timeout=self.base_timeout) + + if self.device_config.serial_pty: + proc = self.device_config.serial_pty + else: + proc = "west rtt" - def _close_serial_pty(self) -> None: - """Terminate the process opened for serial pty script""" - if self._serial_pty_proc: - self._serial_pty_proc.terminate() - self._serial_pty_proc.communicate(timeout=self.base_timeout) - logger.debug('Process %s terminated', self.device_config.serial_pty) - self._serial_pty_proc = None + logger.debug('Process %s terminated', proc) + self._proc = None def _close_device(self) -> None: if self.device_config.post_script: @@ -282,7 +310,7 @@ def _flush_device_output(self) -> None: def _clear_internal_resources(self) -> None: super()._clear_internal_resources() self._serial_connection = None - self._serial_pty_proc = None + self._proc = None self._serial_buffer.clear() @staticmethod diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py index 765da163312ad..413f22a03a2e0 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py @@ -73,8 +73,11 @@ def dut(request: pytest.FixtureRequest, device_object: DeviceAdapter) -> Generat def shell(dut: DeviceAdapter) -> Shell: """Return ready to use shell interface""" shell = Shell(dut, timeout=20.0) + + symbol = 'CONFIG_SHELL_PROMPT_RTT' if dut.device_config.use_rtt else 'CONFIG_SHELL_PROMPT_UART' + if prompt := find_in_config(Path(dut.device_config.app_build_dir) / 'zephyr' / '.config', - 'CONFIG_SHELL_PROMPT_UART'): + symbol): shell.prompt = prompt logger.info('Wait for prompt') if not shell.wait_for_prompt(): diff --git a/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/shell.py b/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/shell.py index 0960db7743ae8..ceaa9f3d049be 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/shell.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/helpers/shell.py @@ -54,24 +54,30 @@ def exec_command( self, command: str, timeout: float | None = None, print_output: bool = True ) -> list[str]: """ - Send shell command to a device and return response. Passed command - is extended by double enter sings - first one to execute this command - on a device, second one to receive next prompt what is a signal that - execution was finished. Method returns printout of the executed command. + Send shell command to a device and return response. Method returns printout of + the executed command. """ timeout = timeout or self.base_timeout - command_ext = f'{command}\n\n' + command_ext = f'{command}\n' regex_prompt = re.escape(self.prompt) regex_command = f'.*{re.escape(command)}' + + # Execute command self._device.clear_buffer() self._device.write(command_ext.encode()) lines: list[str] = [] - # wait for device command print - it should be done immediately after sending command to device + # wait for device command print - it should be done immediately after sending + # command to device. lines.extend( self._device.readlines_until( regex=regex_command, timeout=1.0, print_output=print_output ) ) + + # Send single enter to get next prompt after command execution, as it signals + # that execution finished. + self._device.write(b"\n") + # wait for device command execution lines.extend( self._device.readlines_until( 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 0c471428b6db1..de539e587b781 100644 --- a/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py +++ b/scripts/pylib/pytest-twister-harness/src/twister_harness/plugin.py @@ -70,13 +70,17 @@ def pytest_addoption(parser: pytest.Parser): ) twister_harness_group.addoption( '--runner', - help='Use the specified west runner (pyocd, nrfjprog, etc.).' + help='Use the specified west runner (pyocd, nrfjprog, etc.) when running west flash.' ) twister_harness_group.addoption( '--runner-params', action='append', help='Use the specified west runner params.' ) + twister_harness_group.addoption( + '--rtt-runner', + help='Use the specified west runner (pyocd, nrfjprog, etc.) when running west rtt.' + ) twister_harness_group.addoption( '--device-id', help='ID of connected hardware device (for example 000682459367).' @@ -89,6 +93,11 @@ def pytest_addoption(parser: pytest.Parser): '--device-serial-pty', help='Script for controlling pseudoterminal.' ) + twister_harness_group.addoption( + '--device-rtt', + type=bool, + help='Use RTT as communication transport.', + ) twister_harness_group.addoption( '--flash-before', type=bool, 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 807b72f10ff6c..31bf6fa0406ca 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,8 @@ class DeviceConfig: baud: int = 115200 runner: str = '' runner_params: list[str] = field(default_factory=list, repr=False) + rtt_runner: str = '' + use_rtt: bool = False id: str = '' product: str = '' serial_pty: str = '' @@ -68,6 +70,10 @@ def create(cls, config: pytest.Config) -> TwisterHarnessConfig: runner_params: list[str] = [] if config.option.runner_params: runner_params = [w.strip() for w in config.option.runner_params] + if config.option.device_rtt: + flash_before = True + else: + flash_before = bool(config.option.flash_before) device_from_cli = DeviceConfig( type=config.option.device_type, build_dir=_cast_to_path(config.option.build_dir), @@ -78,10 +84,12 @@ def create(cls, config: pytest.Config) -> TwisterHarnessConfig: baud=config.option.device_serial_baud, runner=config.option.runner, runner_params=runner_params, + rtt_runner=config.option.rtt_runner, + use_rtt=config.option.device_rtt, id=config.option.device_id, product=config.option.device_product, serial_pty=config.option.device_serial_pty, - flash_before=bool(config.option.flash_before), + flash_before=flash_before, west_flash_extra_args=west_flash_extra_args, flash_command=flash_command, pre_script=_cast_to_path(config.option.pre_script), diff --git a/scripts/pylib/pytest-twister-harness/tests/device/hardware_adapter_test.py b/scripts/pylib/pytest-twister-harness/tests/device/hardware_adapter_test.py index eac79478074b9..958fe09c85abd 100644 --- a/scripts/pylib/pytest-twister-harness/tests/device/hardware_adapter_test.py +++ b/scripts/pylib/pytest-twister-harness/tests/device/hardware_adapter_test.py @@ -227,6 +227,7 @@ def test_if_hardware_adapter_uses_serial_pty( 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}') + monkeypatch.setattr('twister_harness.device.hardware_adapter.terminate_process', lambda x: None) serial_mock = mock.Mock() serial_mock.port = '/pty/ttytest/456' @@ -235,7 +236,7 @@ def test_if_hardware_adapter_uses_serial_pty( device._device_run.set() device.connect() assert device._serial_connection.port == '/pty/ttytest/456' # type: ignore[union-attr] - assert device._serial_pty_proc + assert device._proc patched_popen.assert_called_with( ['script.py'], stdout=123, @@ -244,7 +245,7 @@ def test_if_hardware_adapter_uses_serial_pty( ) device.disconnect() - assert not device._serial_pty_proc + assert not device._proc def test_if_hardware_adapter_properly_send_data_to_subprocess( From 29a116bbd2d59b23ea85f7dff17aac1880bdfe84 Mon Sep 17 00:00:00 2001 From: Marko Sagadin Date: Tue, 23 Sep 2025 15:17:58 +0200 Subject: [PATCH 3/3] scripts: twister: add support for device testing via RTT Add support for using RTT as one of the possible communication transports (besides UART and serial pty), when executing tests on hardware with Twister. Users can enable this by: - Adding both --device-testing and --device-rtt flags to the Twister command line, or - Setting 'rtt' to True in the hardware map YAML file. Implementation details: - RTT support uses the west rtt command to connect to the device. - If west rtt: should use a different runner, then --rtt-runner flag or rtt_runner field can be used. - Renaming changes in handlers.py were done to remove the ser/serial prefix from the variables and functions related to pty, since they are now shared by both serial pty and RTT. The current approach using the west rtt command has one limitation: it is currently not possible to test multiple devices that use RTT in parallel. west rtt command will always try a specific set of ports, so running this command while another one is executing doesn't work. Closes: #57120 Signed-off-by: Marko Sagadin --- doc/develop/test/twister.rst | 36 +++++ .../pylib/pytest-twister-harness/README.rst | 3 + .../pylib/twister/twisterlib/environment.py | 27 +++- scripts/pylib/twister/twisterlib/handlers.py | 129 +++++++++++------- .../pylib/twister/twisterlib/hardwaremap.py | 45 +++++- scripts/pylib/twister/twisterlib/harness.py | 6 + scripts/schemas/twister/hwmap-schema.yaml | 6 + scripts/tests/twister/test_environment.py | 8 +- scripts/tests/twister/test_handlers.py | 15 +- scripts/tests/twister/test_hardwaremap.py | 33 ++--- 10 files changed, 225 insertions(+), 83 deletions(-) diff --git a/doc/develop/test/twister.rst b/doc/develop/test/twister.rst index d70d062719633..58cdbf778bdef 100644 --- a/doc/develop/test/twister.rst +++ b/doc/develop/test/twister.rst @@ -1285,6 +1285,25 @@ In this case you can run twister with the following options: The script is user-defined and handles delivering the messages which can be used by twister to determine the test execution status. +To support devices that communicate via RTT, use the ``--device-rtt`` option. Twister +will connect to the device with ``west rtt`` command and capture the log messages. +In this case you can run twister with the following options: + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + scripts/twister --device-testing --device-rtt \ + -p nrf7002dk/nrf5340/cpuapp -T tests/kernel + + .. group-tab:: Windows + + .. note:: + + Not supported on Windows OS + The ``--device-flash-timeout`` option allows to set explicit timeout on the device flash operation, for example when device flashing takes significantly large time. @@ -1480,6 +1499,23 @@ work. It is equivalent to following west and twister commands. manually according to above example. This is because the serial port of the PTY is not fixed and being allocated in the system at runtime. +RTT support using ``--device-rtt`` can also be used in the +hardware map: + +.. code-block:: yaml + + - connected: true + id: 001050795550 + platform: nrf7002dk/nrf5340/cpuapp + product: J-Link + runner: nrfutil + rtt: true + rtt_runner: jlink + +If a different runner should be used for RTT connection than for flashing, specify +``rtt_runner`` field, like shown above. If a different runner is not needed, then +``rtt_runner`` can be omitted. + If west is not available or does not know how to flash your system, a custom flash command can be specified using the ``flash-command`` flag. The script is called with a ``--build-dir`` with the path of the current build, as well as a diff --git a/scripts/pylib/pytest-twister-harness/README.rst b/scripts/pylib/pytest-twister-harness/README.rst index 01d50c5ea92e4..099501125d710 100644 --- a/scripts/pylib/pytest-twister-harness/README.rst +++ b/scripts/pylib/pytest-twister-harness/README.rst @@ -26,6 +26,9 @@ Run exemplary test shell application by Twister: # hardware ./scripts/twister -p nrf52840dk/nrf52840 --device-testing --device-serial /dev/ttyACM0 -T samples/subsys/testsuite/pytest/shell + # hardware over RTT + ./scripts/twister -p nrf52840dk/nrf52840 --device-testing --device-rtt -T samples/subsys/testsuite/pytest/shell --test sample.pytest.rtt + or build shell application by west and call pytest directly: .. code-block:: sh diff --git a/scripts/pylib/twister/twisterlib/environment.py b/scripts/pylib/twister/twisterlib/environment.py index 47942ba1cd139..cfb099d6b2036 100644 --- a/scripts/pylib/twister/twisterlib/environment.py +++ b/scripts/pylib/twister/twisterlib/environment.py @@ -174,8 +174,9 @@ def add_parse_arguments(parser = None) -> argparse.ArgumentParser: run_group_option.add_argument( "--device-testing", action="store_true", - help="Test on device directly. Specify the serial device to " - "use with the --device-serial option.") + help="Test on device directly. Specify the serial device to use with " + "the --device-serial option or specify script to use with the " + "--device-serial-pty or specify to use with --device-rtt option.") run_group_option.add_argument("--generate-hardware-map", help="""Probe serial devices connected to this platform @@ -204,6 +205,15 @@ def add_parse_arguments(parser = None) -> argparse.ArgumentParser: --device-serial-pty