watlowlib.testing¶
Public testing surface — FakeTransport, FakeSlave, fixture
records (StdBusRound, ModbusRound, Fixture), load_fixture, and
controller_from_fixture. See Testing.
Public surface¶
watlowlib.testing ¶
Public testing surface for downstream packages.
The test seam is a contract: anyone consuming :mod:watlowlib can
import watlowlib.testing and write facade-level tests against
captured PM3 round-trips without touching a serial port.
What's here:
- :class:
FakeTransport— re-export of the in-process scripted transport (fixture-replay grade: ordered queue + unmatched-write capture). - :class:
FakeSlave— stub :class:anymodbus.Slavefor the Modbus facade path (the equivalent shape for variants that emit a :class:ModbusOprather than wire bytes). - :class:
StdBusRound/ :class:ModbusRound/ :class:Fixture— typed records for one captured round-trip and a captured scenario. - :func:
load_fixture— JSONL file → :class:Fixture. - :func:
controller_from_fixture— JSONL file → opened :class:Controllerready for facade-level assertions.
Fixture file format is one JSON object per line. The first line may be a header recording the protocol and serial framing; everything else is a round.
Std Bus round::
{
"protocol": "stdbus",
"label": "read_pv",
"request_hex": "55FF051000...",
"response_hex": "55FF0600...",
}
Modbus round::
{
"protocol": "modbus_rtu",
"label": "read_pv",
"method": "read_holding_registers",
"address": 360,
"count": 2,
"response_words": [17299, 29054],
}
{
"protocol": "modbus_rtu",
"label": "set_setpoint",
"method": "write_registers",
"address": 2160,
"values": [17348, 0],
}
Optional header ("kind": "header") sets address, baudrate, and
parity for the whole capture::
{
"kind": "header",
"protocol": "stdbus",
"address": 1,
"baudrate": 38400,
"parity": "none",
}
FakeSlave ¶
Scripted :class:anymodbus.Slave stand-in for tests.
Mirrors the surface :class:watlowlib.protocol.modbus.client.ModbusProtocolClient
actually calls (read_holding_registers,
read_input_registers, write_register, write_registers)
and records every call. Tests assert on :attr:reads and
:attr:writes to verify the :class:ModbusOp lowered correctly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
script
|
Mapping[tuple[str, int], ScriptedSlaveEntry] | None
|
|
None
|
Source code in src/watlowlib/transport/fake.py
add_script ¶
Register or overwrite a scripted reply for (method, address).
FakeTransport ¶
Scripted :class:Transport for tests.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
script
|
Mapping[bytes, ScriptedReply] | None
|
Mapping of |
None
|
label
|
str
|
Identifier used in errors. |
'fake://test'
|
latency_s
|
float
|
Per-operation artificial delay, useful for simulating a slow device. |
0.0
|
Source code in src/watlowlib/transport/fake.py
unmatched_writes
property
¶
Writes that didn't match any scripted reply, in order.
A test can assert transport.unmatched_writes == () to catch
accidentally-unscripted traffic — the corresponding read would
have timed out, but a precise assertion fails faster and points
at the right call.
add_script ¶
extend_queue ¶
feed ¶
Push unsolicited bytes into the read buffer.
Useful for simulating a device that left chatter on the line which the protocol client has to drain on recovery.
Source code in src/watlowlib/transport/fake.py
force_read_timeout ¶
force_write_timeout ¶
Fixture
dataclass
¶
Fixture(
protocol,
address=1,
serial_settings=(
lambda: SerialSettings(port="fake://fixture")
)(),
stdbus_rounds=(),
modbus_rounds=(),
)
A captured scenario — one wire protocol, many rounds.
fake_slave ¶
Build a :class:FakeSlave scripted with the Modbus rounds.
Read rounds populate the (method, address) → response_words
script entry; write rounds need no entry — :class:FakeSlave
treats unscripted writes as silent successes and records them
on :attr:FakeSlave.writes so tests can assert on the lowered
register words.
Source code in src/watlowlib/testing.py
fake_transport ¶
Build a :class:FakeTransport scripted with the Std Bus rounds.
Each captured request maps to its captured response in the dict-based script, so a test that issues the same parameter read twice gets the same response both times — appropriate for facade smoke tests where ordering doesn't matter.
Source code in src/watlowlib/testing.py
ModbusRound
dataclass
¶
One captured Modbus call.
For reads response_words carries the register tuple the slave
returned; values is empty. For writes values carries the
register words the controller sent and response_words is empty
(a successful write returns no payload).
StdBusRound
dataclass
¶
One captured Std Bus request/response pair.
controller_from_fixture
async
¶
Build an unopened :class:Controller scripted by path.
Returned in unopened form so the caller drives lifecycle through
async with. Std Bus fixtures wire through :class:FakeTransport;
Modbus fixtures wire through :class:FakeSlave (the
:class:Transport shim only carries lifecycle, since the Modbus
protocol client talks to its slave provider directly).
Source code in src/watlowlib/testing.py
load_fixture ¶
Parse path as a JSONL fixture and return a :class:Fixture.
The first line may be a header ({"kind": "header", ...}) that
sets address and the serial framing for the capture. Subsequent
lines are rounds; their protocol field must agree with the
header (or with the first round, if the header is omitted).
Raises:
| Type | Description |
|---|---|
ValueError
|
malformed JSONL, missing fields, or a row whose
|