[Lldb-commits] [lldb] 8fa2394 - [lldb] Add a gdb_remote_client test for connecting to pty
Michał Górny via lldb-commits
lldb-commits at lists.llvm.org
Fri Oct 1 05:32:09 PDT 2021
Author: Michał Górny
Date: 2021-10-01T14:31:40+02:00
New Revision: 8fa2394bad433558f3083cee158043e2fb66d781
URL: https://github.com/llvm/llvm-project/commit/8fa2394bad433558f3083cee158043e2fb66d781
DIFF: https://github.com/llvm/llvm-project/commit/8fa2394bad433558f3083cee158043e2fb66d781.diff
LOG: [lldb] Add a gdb_remote_client test for connecting to pty
Add a minimal mock server utilizing a pty, and add a client test
connecting to that server.
Differential Revision: https://reviews.llvm.org/D110878
Added:
lldb/test/API/functionalities/gdb_remote_client/TestPty.py
Modified:
lldb/test/API/functionalities/gdb_remote_client/TestPlatformClient.py
lldb/test/API/functionalities/gdb_remote_client/TestProcessConnect.py
lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
Removed:
################################################################################
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestPlatformClient.py b/lldb/test/API/functionalities/gdb_remote_client/TestPlatformClient.py
index 30262fce74808..ff32a6e0093a5 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestPlatformClient.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestPlatformClient.py
@@ -50,7 +50,7 @@ def qsProcessInfo(self):
try:
self.runCmd("platform select remote-linux")
- self.runCmd("platform connect connect://" + self.server.get_connect_address())
+ self.runCmd("platform connect " + self.server.get_connect_url())
self.assertTrue(self.dbg.GetSelectedPlatform().IsConnected())
self.expect("platform process list -x",
substrs=["2 matching processes were found", "test_process", "another_test_process"])
@@ -84,8 +84,8 @@ def test_no_timeout(self):
self.runCmd("settings set plugin.process.gdb-remote.packet-timeout 30")
plat = lldb.SBPlatform("remote-linux")
try:
- self.assertSuccess(plat.ConnectRemote(lldb.SBPlatformConnectOptions("connect://"
- + self.server.get_connect_address())))
+ self.assertSuccess(plat.ConnectRemote(lldb.SBPlatformConnectOptions(
+ self.server.get_connect_url())))
self.assertEqual(plat.GetWorkingDirectory(), "/foo/bar")
finally:
plat.DisconnectRemote()
@@ -98,8 +98,8 @@ def test_timeout(self):
self.runCmd("settings set plugin.process.gdb-remote.packet-timeout 3")
plat = lldb.SBPlatform("remote-linux")
try:
- self.assertSuccess(plat.ConnectRemote(lldb.SBPlatformConnectOptions("connect://"
- + self.server.get_connect_address())))
+ self.assertSuccess(plat.ConnectRemote(lldb.SBPlatformConnectOptions(
+ self.server.get_connect_url())))
self.assertIsNone(plat.GetWorkingDirectory())
finally:
plat.DisconnectRemote()
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestProcessConnect.py b/lldb/test/API/functionalities/gdb_remote_client/TestProcessConnect.py
index 9ad498de15d6e..6f99c939562b6 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestProcessConnect.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestProcessConnect.py
@@ -39,8 +39,7 @@ def test_process_connect_sync(self):
self.dbg.SetAsync(False)
self.expect("platform select remote-gdb-server",
substrs=['Platform: remote-gdb-server', 'Connected: no'])
- self.expect("process connect connect://" +
- self.server.get_connect_address(),
+ self.expect("process connect " + self.server.get_connect_url(),
substrs=['Process', 'stopped'])
finally:
self.dbg.GetSelectedPlatform().DisconnectRemote()
@@ -52,8 +51,7 @@ def test_process_connect_async(self):
self.dbg.SetAsync(True)
self.expect("platform select remote-gdb-server",
substrs=['Platform: remote-gdb-server', 'Connected: no'])
- self.expect("process connect connect://" +
- self.server.get_connect_address(),
+ self.expect("process connect " + self.server.get_connect_url(),
matching=False,
substrs=['Process', 'stopped'])
lldbutil.expect_state_changes(self, self.dbg.GetListener(),
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestPty.py b/lldb/test/API/functionalities/gdb_remote_client/TestPty.py
new file mode 100644
index 0000000000000..46078b7a0adb3
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestPty.py
@@ -0,0 +1,35 @@
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from gdbclientutils import *
+
+
+ at skipIfWindows
+class TestPty(GDBRemoteTestBase):
+ mydir = TestBase.compute_mydir(__file__)
+ server_socket_class = PtyServerSocket
+
+ def test_process_connect_sync(self):
+ """Test the process connect command in synchronous mode"""
+ try:
+ self.dbg.SetAsync(False)
+ self.expect("platform select remote-gdb-server",
+ substrs=['Platform: remote-gdb-server', 'Connected: no'])
+ self.expect("process connect " + self.server.get_connect_url(),
+ substrs=['Process', 'stopped'])
+ finally:
+ self.dbg.GetSelectedPlatform().DisconnectRemote()
+
+ def test_process_connect_async(self):
+ """Test the process connect command in asynchronous mode"""
+ try:
+ self.dbg.SetAsync(True)
+ self.expect("platform select remote-gdb-server",
+ substrs=['Platform: remote-gdb-server', 'Connected: no'])
+ self.expect("process connect " + self.server.get_connect_url(),
+ matching=False,
+ substrs=['Process', 'stopped'])
+ lldbutil.expect_state_changes(self, self.dbg.GetListener(),
+ self.process(), [lldb.eStateStopped])
+ finally:
+ self.dbg.GetSelectedPlatform().DisconnectRemote()
diff --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
index 60dcf1111cc4f..c395bac2de70c 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
@@ -1,8 +1,12 @@
+import ctypes
import errno
+import io
import os
import os.path
+import pty
import threading
import socket
+import tty
import lldb
import binascii
import traceback
@@ -332,6 +336,110 @@ class UnexpectedPacketException(Exception):
pass
+class ServerSocket:
+ """
+ A wrapper class for TCP or pty-based server.
+ """
+
+ def get_connect_address(self):
+ """Get address for the client to connect to."""
+
+ def get_connect_url(self):
+ """Get URL suitable for process connect command."""
+
+ def close_server(self):
+ """Close all resources used by the server."""
+
+ def accept(self):
+ """Accept a single client connection to the server."""
+
+ def close_connection(self):
+ """Close all resources used by the accepted connection."""
+
+ def recv(self):
+ """Receive a data packet from the connected client."""
+
+ def sendall(self, data):
+ """Send the data to the connected client."""
+
+
+class TCPServerSocket(ServerSocket):
+ def __init__(self):
+ family, type, proto, _, addr = socket.getaddrinfo(
+ "localhost", 0, proto=socket.IPPROTO_TCP)[0]
+ self._server_socket = socket.socket(family, type, proto)
+ self._connection = None
+
+ self._server_socket.bind(addr)
+ self._server_socket.listen(1)
+
+ def get_connect_address(self):
+ return "[{}]:{}".format(*self._server_socket.getsockname())
+
+ def get_connect_url(self):
+ return "connect://" + self.get_connect_address()
+
+ def close_server(self):
+ self._server_socket.close()
+
+ def accept(self):
+ assert self._connection is None
+ # accept() is stubborn and won't fail even when the socket is
+ # shutdown, so we'll use a timeout
+ self._server_socket.settimeout(30.0)
+ client, client_addr = self._server_socket.accept()
+ # The connected client inherits its timeout from self._socket,
+ # but we'll use a blocking socket for the client
+ client.settimeout(None)
+ self._connection = client
+
+ def close_connection(self):
+ assert self._connection is not None
+ self._connection.close()
+ self._connection = None
+
+ def recv(self):
+ assert self._connection is not None
+ return self._connection.recv(4096)
+
+ def sendall(self, data):
+ assert self._connection is not None
+ return self._connection.sendall(data)
+
+
+class PtyServerSocket(ServerSocket):
+ def __init__(self):
+ master, slave = pty.openpty()
+ tty.setraw(master)
+ self._master = io.FileIO(master, 'r+b')
+ self._slave = io.FileIO(slave, 'r+b')
+
+ def get_connect_address(self):
+ libc = ctypes.CDLL(None)
+ libc.ptsname.argtypes = (ctypes.c_int,)
+ libc.ptsname.restype = ctypes.c_char_p
+ return libc.ptsname(self._master.fileno()).decode()
+
+ def get_connect_url(self):
+ return "file://" + self.get_connect_address()
+
+ def close_server(self):
+ self._slave.close()
+ self._master.close()
+
+ def recv(self):
+ try:
+ return self._master.read(4096)
+ except OSError as e:
+ # closing the pty results in EIO on Linux, convert it to EOF
+ if e.errno == errno.EIO:
+ return b''
+ raise
+
+ def sendall(self, data):
+ return self._master.write(data)
+
+
class MockGDBServer:
"""
A simple TCP-based GDB server that can test client behavior by receiving
@@ -343,48 +451,37 @@ class MockGDBServer:
responder = None
_socket = None
- _client = None
_thread = None
_receivedData = None
_receivedDataOffset = None
_shouldSendAck = True
- def __init__(self):
+ def __init__(self, socket_class):
+ self._socket_class = socket_class
self.responder = MockGDBServerResponder()
def start(self):
- family, type, proto, _, addr = socket.getaddrinfo("localhost", 0,
- proto=socket.IPPROTO_TCP)[0]
- self._socket = socket.socket(family, type, proto)
-
-
- self._socket.bind(addr)
- self._socket.listen(1)
-
+ self._socket = self._socket_class()
# Start a thread that waits for a client connection.
self._thread = threading.Thread(target=self._run)
self._thread.start()
def stop(self):
- self._socket.close()
+ self._socket.close_server()
self._thread.join()
self._thread = None
def get_connect_address(self):
- return "[{}]:{}".format(*self._socket.getsockname())
+ return self._socket.get_connect_address()
+
+ def get_connect_url(self):
+ return self._socket.get_connect_url()
def _run(self):
# For testing purposes, we only need to worry about one client
# connecting just one time.
try:
- # accept() is stubborn and won't fail even when the socket is
- # shutdown, so we'll use a timeout
- self._socket.settimeout(30.0)
- client, client_addr = self._socket.accept()
- self._client = client
- # The connected client inherits its timeout from self._socket,
- # but we'll use a blocking socket for the client
- self._client.settimeout(None)
+ self._socket.accept()
except:
return
self._shouldSendAck = True
@@ -393,14 +490,14 @@ def _run(self):
data = None
while True:
try:
- data = seven.bitcast_to_string(self._client.recv(4096))
+ data = seven.bitcast_to_string(self._socket.recv())
if data is None or len(data) == 0:
break
self._receive(data)
except Exception as e:
print("An exception happened when receiving the response from the gdb server. Closing the client...")
traceback.print_exc()
- self._client.close()
+ self._socket.close_connection()
break
def _receive(self, data):
@@ -415,7 +512,7 @@ def _receive(self, data):
self._handlePacket(packet)
packet = self._parsePacket()
except self.InvalidPacketException:
- self._client.close()
+ self._socket.close_connection()
def _parsePacket(self):
"""
@@ -492,7 +589,7 @@ def _handlePacket(self, packet):
# We'll handle the ack stuff here since it's not something any of the
# tests will be concerned about, and it'll get turned off quickly anyway.
if self._shouldSendAck:
- self._client.sendall(seven.bitcast_to_bytes('+'))
+ self._socket.sendall(seven.bitcast_to_bytes('+'))
if packet == "QStartNoAckMode":
self._shouldSendAck = False
response = "OK"
@@ -502,7 +599,7 @@ def _handlePacket(self, packet):
# Handle packet framing since we don't want to bother tests with it.
if response is not None:
framed = frame_packet(response)
- self._client.sendall(seven.bitcast_to_bytes(framed))
+ self._socket.sendall(seven.bitcast_to_bytes(framed))
PACKET_ACK = object()
PACKET_INTERRUPT = object()
@@ -510,6 +607,7 @@ def _handlePacket(self, packet):
class InvalidPacketException(Exception):
pass
+
class GDBRemoteTestBase(TestBase):
"""
Base class for GDB client tests.
@@ -522,10 +620,11 @@ class GDBRemoteTestBase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
mydir = TestBase.compute_mydir(__file__)
server = None
+ server_socket_class = TCPServerSocket
def setUp(self):
TestBase.setUp(self)
- self.server = MockGDBServer()
+ self.server = MockGDBServer(socket_class=self.server_socket_class)
self.server.start()
def tearDown(self):
@@ -559,7 +658,7 @@ def connect(self, target):
listener = self.dbg.GetListener()
error = lldb.SBError()
process = target.ConnectRemote(listener,
- "connect://" + self.server.get_connect_address(), "gdb-remote", error)
+ self.server.get_connect_url(), "gdb-remote", error)
self.assertTrue(error.Success(), error.description)
self.assertTrue(process, PROCESS_IS_VALID)
return process
@@ -599,8 +698,7 @@ class GDBPlatformClientTestBase(GDBRemoteTestBase):
def setUp(self):
super().setUp()
self.runCmd("platform select remote-gdb-server")
- self.runCmd("platform connect connect://" +
- self.server.get_connect_address())
+ self.runCmd("platform connect " + self.server.get_connect_url())
self.assertTrue(self.dbg.GetSelectedPlatform().IsConnected())
def tearDown(self):
More information about the lldb-commits
mailing list