116 lines
3.3 KiB
Python
116 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()
|