pytest-embedded-serial: Serial Port Testing for Pytest Embedded
pytest-embedded-serial is a pytest plugin that extends the core pytest-embedded framework, enabling robust testing of embedded devices over serial communication. It provides fixtures and functionalities to interact with serial ports, facilitating automated tests for embedded systems. Currently at version 2.7.0, it is part of the pytest-embedded ecosystem maintained by Espressif Systems, which typically follows a regular release cadence with semantic versioning.
Common errors
-
serial.serialutil.SerialException: [Errno 13] could not open port /dev/ttyUSB0: [Errno 13] Permission denied: '/dev/ttyUSB0'
cause The current user does not have read/write permissions for the specified serial port. This is a common issue on Linux-based systems where serial ports are owned by the 'dialout' or 'uucp' group.fixAdd your user to the appropriate group (e.g., 'dialout') and then log out and back in for the changes to take effect. On Linux: `sudo usermod -a -G dialout $USER`. Ensure the port path is correct (`/dev/ttyUSB0` or `COMx` on Windows). -
serial.serialutil.SerialException: No such file or directory: '/dev/ttyS99'
cause The specified serial port does not exist or is not connected. This could be due to an incorrect port name, a disconnected device, or a missing driver.fixVerify the correct serial port name for your operating system (e.g., check device manager on Windows, `ls /dev/tty*` on Linux). Ensure the device is properly connected and its drivers are installed. -
pexpect.exceptions.TIMEOUT: Timeout exceeded in command 'dut.expect('...')'cause The expected pattern was not received from the serial device within the allotted timeout period. This could be due to the device not responding, an incorrect expected pattern, or a too-short timeout.fixIncrease the `timeout` parameter in `dut.expect()` or `dut.expect_exact()` calls. Verify the expected pattern matches the device's actual output, considering line endings or other formatting. Debug the device's firmware to ensure it sends the expected response.
Warnings
- breaking Python 3.7, 3.8, and 3.9 are no longer supported by the pytest-embedded ecosystem, including pytest-embedded-serial. Projects using these older Python versions must upgrade to Python 3.10 or newer.
- gotcha File descriptor leaks can occur when using `pytest-embedded-serial` in conjunction with `pytest-lazy-fixture` in older versions of `pytest-embedded`, leading to resource exhaustion.
- gotcha The `pytest-embedded` framework (and by extension `pytest-embedded-serial`) uses internal threading. When conducting performance tests, using Python's `threading` module might lead to performance degradation due to the Global Interpreter Lock (GIL).
Install
-
pip install pytest-embedded-serial
Imports
- Dut
from pytest_embedded import Dut
- SerialDut
from pytest_embedded.dut import SerialDut
from pytest_embedded_serial.dut import SerialDut
Quickstart
import pytest
from pytest_embedded import Dut
import os
# Assuming a device is connected via serial port
# You can specify the port via CLI: pytest --port /dev/ttyUSB0 (or COMx on Windows)
# Or set it via an environment variable or pytest.ini
def test_serial_communication(dut: Dut):
# Ensure the 'serial' service is enabled, e.g., via CLI: pytest --embedded-services serial
# or by default if pytest-embedded-serial is installed.
# Example: Write data to the serial port and expect a response
# Replace with actual device interaction logic
dut.write('hello device')
dut.expect_exact('echo: hello device', timeout=5)
print("Device responded to 'hello device'.")
# Accessing underlying serial object (for advanced use cases)
# The dut.serial object is an instance of Serial from pytest_embedded_serial.serial
# It's generally preferred to use dut.write and dut.expect for standard interactions.
# serial_port = dut.serial
# print(f"Connected to serial port: {serial_port.port} at baudrate: {serial_port.baud}")
dut.write('get status')
dut.expect(r'Status: (\w+)', timeout=5)
status = dut.match.group(1).decode('utf-8')
print(f"Device status: {status}")
assert status == 'OK'