openocd-python/tests/test_connection.py
Ryan Malloy 7e1eac5e2d Add openocd-python: typed async-first Python bindings for OpenOCD
Standalone PyPI package providing structured access to the full OpenOCD
command surface via the TCL RPC protocol (port 6666). Async-first API
with sync wrappers for every method.

Subsystems: target control, memory read/write, CPU registers, flash
programming, JTAG chain/scan/boundary, breakpoints/watchpoints, SVD
peripheral decoding, RTT channels, transport/adapter config.

79 tests passing against a mock TCL RPC server.
2026-02-12 17:55:58 -07:00

114 lines
3.3 KiB
Python

"""Tests for the TclRpcConnection class."""
from __future__ import annotations
import asyncio
import pytest
from openocd.connection.tcl_rpc import TclRpcConnection
from openocd.errors import ConnectionError, TimeoutError
async def test_connect_to_mock_server(mock_ocd):
"""Verify we can open a connection to the mock server."""
host, port, _server = mock_ocd
conn = TclRpcConnection(timeout=5.0)
await conn.connect(host, port)
assert conn._writer is not None
assert conn._reader is not None
await conn.close()
async def test_send_and_receive(connection):
"""Send a command and verify we get the expected response."""
resp = await connection.send("targets")
assert "stm32f1x.cpu" in resp
assert "halted" in resp
async def test_separator_framing(mock_ocd):
"""Verify the \\x1a framing works for multiple sequential commands."""
host, port, _server = mock_ocd
conn = TclRpcConnection(timeout=5.0)
await conn.connect(host, port)
# Send several commands in sequence; each should get its own response
resp1 = await conn.send("halt")
resp2 = await conn.send("reg pc")
resp3 = await conn.send("targets")
# halt returns empty
assert resp1 == ""
# reg pc returns a value
assert "0x08001234" in resp2
# targets returns the state table
assert "stm32f1x.cpu" in resp3
await conn.close()
async def test_connection_error_no_server():
"""Connecting to a port with no listener should raise ConnectionError."""
conn = TclRpcConnection(timeout=1.0)
with pytest.raises(ConnectionError):
await conn.connect("127.0.0.1", 1) # port 1 is unlikely to be open
async def test_send_before_connect_raises():
"""Sending a command before connect() should raise ConnectionError."""
conn = TclRpcConnection()
with pytest.raises(ConnectionError, match="Not connected"):
await conn.send("targets")
async def test_timeout_on_hung_server():
"""A server that never sends \\x1a should trigger a TimeoutError."""
# Start a server that accepts connections but never responds
async def _hang(reader, writer):
# Read the command but never reply
await reader.read(4096)
await asyncio.sleep(60)
server = await asyncio.start_server(_hang, "127.0.0.1", 0)
await server.start_serving()
sock = server.sockets[0]
host, port = sock.getsockname()[:2]
conn = TclRpcConnection(timeout=0.3)
await conn.connect(host, port)
with pytest.raises(TimeoutError):
await conn.send("targets")
await conn.close()
server.close()
await server.wait_closed()
async def test_close_idempotent(connection):
"""Calling close() multiple times should not raise."""
await connection.close()
await connection.close() # second call is a no-op
async def test_concurrent_commands(mock_ocd):
"""Multiple coroutines sharing one connection should serialize properly."""
host, port, _server = mock_ocd
conn = TclRpcConnection(timeout=5.0)
await conn.connect(host, port)
async def _do_command(cmd: str) -> str:
return await conn.send(cmd)
results = await asyncio.gather(
_do_command("reg pc"),
_do_command("reg sp"),
_do_command("reg lr"),
)
assert "0x08001234" in results[0]
assert "0x20005000" in results[1]
assert "0x08000100" in results[2]
await conn.close()