"""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()