Working with DUTs

A DUT (“device under test”) wraps a serial connection and exposes a small, consistent API for writing commands and waiting for expected output. The entry point is dut_wrapper, which adapts several input types into a DutBase object.

Creating a DUT

dut_wrapper accepts different kinds of input:

from serial import Serial

from esptest.all import DutConfig, dut_wrapper

# 1. From an already-open pyserial connection
ser = Serial('/dev/ttyUSB0', 115200, timeout=0.01)
dut = dut_wrapper(ser, name='DUT', log_file='./dut_logs/dut.log')

# 2. From a device path string
dut = dut_wrapper('/dev/ttyUSB0', name='DUT')

# 3. From a DutConfig (most flexible)
config = DutConfig(name='DUT', device='/dev/ttyUSB0', baudrate=115200)
dut = dut_wrapper(config)

Use it as a context manager so the port and the background reader thread are released when the block exits:

with dut_wrapper(ser, 'DUT', './dut_logs/dut.log') as dut:
    ...

Sending data and matching output

import re

with dut_wrapper(ser, 'DUT') as dut:
    dut.flush_data()                 # drop buffered output
    dut.write('restart\r\n')         # raw write
    dut.write_line('reboot', end='\r\n')  # write + line ending

    # str/bytes pattern: waits for the text, returns None
    dut.expect('main_task: Returned from app_main', timeout=5)

    # compiled pattern: returns the re.Match object
    match = dut.expect(re.compile(r'offset (0x\w+)'), timeout=5)
    print(match.group(1))

Reading buffered data directly:

text = dut.read_all_data()     # decoded str, flushes by default
raw = dut.read_all_bytes()     # bytes, keeps the cache by default
cache = dut.data_cache         # current cache without flushing

If expect does not find the pattern within timeout seconds it raises TimeoutError.

Configuring a DUT with DutConfig

DutConfig is a dataclass describing how the DUT should be created. Commonly used fields:

Field

Purpose

name

DUT name (defaults to the device/port base name)

device

Serial device, e.g. /dev/ttyUSB0, COM3

baudrate

Console baudrate (0 = derive from bin path or default 115200)

serial_configs

Extra pyserial kwargs, e.g. {'timeout': 0.1}

bin_path

Firmware path; used to derive chip/stub/baudrate

log_file

Log file path (auto-generated under log_path if empty)

monitors

List of DataMonitor objects attached to the port

pexpect_timeout

Default timeout for expect

config = DutConfig(
    name='JAP1',
    device='/dev/ttyUSB0',
    baudrate=115200,
    serial_configs={'timeout': 0.1},
)
with dut_wrapper(config) as dut:
    dut.write_line('help', end='\r\n')
    dut.expect('ready', timeout=5)

Custom DUT classes

You can attach mixins by passing a custom class to wrap_cls. It must derive from the DUT interface:

from esptest.adapter.dut.esp_dut import EspDut

class MyDut(EspDut):
    def hello(self) -> None:
        self.write_line('hello')

with dut_wrapper(config, wrap_cls=MyDut) as dut:
    dut.hello()

Attaching monitors

Pass DataMonitor instances through DutConfig.monitors to react to output as it streams in (for example capturing a reset reason). See Streaming data monitor for the full pattern.