[Lldb-commits] [lldb] r206930 - Added a packet-playback test facility for lldb-gdbserver and a few starter tests.

Todd Fiala todd.fiala at gmail.com
Tue Apr 22 16:16:53 PDT 2014


Author: tfiala
Date: Tue Apr 22 18:16:53 2014
New Revision: 206930

URL: http://llvm.org/viewvc/llvm-project?rev=206930&view=rev
Log:
Added a packet-playback test facility for lldb-gdbserver and a few starter tests.

lldbgdbserverutils.py has a new expect_lldb_gdbserver_replay() method
that plays back gdb remote send/receive packets. These packets are the
log lines that come from running the 'log enable gdb-remote packets',
either from the lldb-gdbserver side or the lldb side. There's a flag
to flip which side is the send (lldb-gdbserver input or lldb-gdbserver
output).

This first checkin tests the initial gdbremote handshake, the ability
to turn on the no-ack mode communication style, thread suffix support,
and list threads in stop reply support. The last two are marked xfail
as top of tree does not yet support these.

 --This line, and those below, will be ignored--

M    test/tools/lldb-gdbserver/TestLldbGdbServer.py
M    test/tools/lldb-gdbserver/lldbgdbserverutils.py

Modified:
    lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py
    lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py

Modified: lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py?rev=206930&r1=206929&r2=206930&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py Tue Apr 22 18:16:53 2014
@@ -4,9 +4,11 @@ Test lldb-gdbserver operation
 
 import unittest2
 import pexpect
+import socket
 import sys
 from lldbtest import *
 from lldbgdbserverutils import *
+import logging
 
 class LldbGdbServerTestCase(TestBase):
 
@@ -14,13 +16,39 @@ class LldbGdbServerTestCase(TestBase):
 
     port = 12345
 
+    _TIMEOUT_SECONDS = 5
+
+    _GDBREMOTE_KILL_PACKET = "$k#6b"
+
+    _LOGGING_LEVEL = logging.WARNING
+#    _LOGGING_LEVEL = logging.DEBUG
+
     def setUp(self):
         TestBase.setUp(self)
         self.lldb_gdbserver_exe = get_lldb_gdbserver_exe()
         if not self.lldb_gdbserver_exe:
             self.skipTest("lldb_gdbserver exe not specified")
 
-    def test_exe_starts(self):
+        FORMAT = '%(asctime)-15s %(levelname)-8s %(message)s'
+        logging.basicConfig(format=FORMAT)
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(self._LOGGING_LEVEL)
+
+    def create_socket(self):
+        sock = socket.socket()
+
+        def shutdown_socket():
+            if sock:
+                # send the kill packet so lldb-gdbserver shuts down gracefully
+                sock.sendall(LldbGdbServerTestCase._GDBREMOTE_KILL_PACKET)
+                sock.close()
+
+        self.addTearDownHook(shutdown_socket)
+
+        sock.connect(('localhost', self.port))
+        return sock
+
+    def start_server(self):
         # start the server
         server = pexpect.spawn("{} localhost:{}".format(self.lldb_gdbserver_exe, self.port))
 
@@ -36,5 +64,58 @@ class LldbGdbServerTestCase(TestBase):
         # Wait until we receive the server ready message before continuing.
         server.expect_exact('Listening for a connection on localhost:{}'.format(self.port))
 
+        # Create a socket to talk to the server
+        self.sock = self.create_socket()
+
+        return server
+
+    def create_no_ack_remote_stream(self):
+       return [
+            "lldb-gdbserver <  19> read packet: +",
+            "lldb-gdbserver <  19> read packet: $QStartNoAckMode#b0",
+            "lldb-gdbserver <   1> send packet: +",
+            "lldb-gdbserver <   6> send packet: $OK#9a",
+            "lldb-gdbserver <   1> read packet: +"]
+
+
+    def test_exe_starts(self):
+        server = self.start_server()
+
+    def test_start_no_ack_mode(self):
+        server = self.start_server()
+        self.assertIsNotNone(server)
+
+        log_lines = self.create_no_ack_remote_stream()
+
+        expect_lldb_gdbserver_replay(self, self.sock, log_lines, True,
+                                     self._TIMEOUT_SECONDS, self.logger)
+
+    @unittest2.expectedFailure()
+    def test_thread_suffix_supported(self):
+        server = self.start_server()
+        self.assertIsNotNone(server)
+
+        log_lines = self.create_no_ack_remote_stream()
+        log_lines.append([
+            "lldb-gdbserver <  26> read packet: $QThreadSuffixSupported#e4",
+            "lldb-gdbserver <   6> send packet: $OK#9a"])
+
+        expect_lldb_gdbserver_replay(self, self.sock, log_lines, True,
+                                     self._TIMEOUT_SECONDS, self.logger)
+
+    @unittest2.expectedFailure()
+    def test_list_threads_in_stop_reply_supported(self):
+        server = self.start_server()
+        self.assertIsNotNone(server)
+
+        log_lines = self.create_no_ack_remote_stream()
+        log_lines.append([
+            "lldb-gdbserver <  27> read packet: $QListThreadsInStopReply#21",
+            "lldb-gdbserver <   6> send packet: $OK#9a"])
+
+        expect_lldb_gdbserver_replay(self, self.sock, log_lines, True,
+                                     self._TIMEOUT_SECONDS, self.logger)
+
+
 if __name__ == '__main__':
     unittest2.main()

Modified: lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py?rev=206930&r1=206929&r2=206930&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py Tue Apr 22 18:16:53 2014
@@ -1,25 +1,206 @@
+"""Module for supporting unit testing of the lldb-gdbserver debug monitor exe.
+"""
+
 import os
 import os.path
+import re
+import select
+import time
 
 def _get_lldb_gdbserver_from_lldb(lldb_exe):
-    lldb_gdbserver = os.path.join(os.path.dirname(lldb_exe), "lldb-gdbserver")
-    if os.path.exists(lldb_gdbserver):
-        return lldb_gdbserver
+    """Return the lldb-gdbserver exe path given the lldb exe path.
+
+    This method attempts to construct a valid lldb-gdbserver exe name
+    from a given lldb exe name.  It will return None if the synthesized
+    lldb-gdbserver name is not found to exist.
+
+    The lldb-gdbserver exe path is synthesized by taking the directory
+    of the lldb exe, and replacing the portion of the base name that
+    matches "lldb" (case insensitive) and replacing with "lldb-gdbserver".
+
+    Args:
+        lldb_exe: the path to an lldb executable.
+
+    Returns:
+        A path to the lldb-gdbserver exe if it is found to exist; otherwise,
+        returns None.
+    """
+
+    exe_dir = os.path.dirname(lldb_exe)
+    exe_base = os.path.basename(lldb_exe)
+
+    # we'll rebuild the filename by replacing lldb with
+    # lldb-gdbserver, keeping any prefix or suffix in place.
+    regex = re.compile(r"lldb", re.IGNORECASE)
+    new_base = regex.sub("lldb-gdbserver", exe_base)
+
+    lldb_gdbserver_exe = os.path.join(exe_dir, new_base)
+    if os.path.exists(lldb_gdbserver_exe):
+        return lldb_gdbserver_exe
     else:
         return None
 
+
 def get_lldb_gdbserver_exe():
-    # check for --lldb-gdbserver='{some-path}' in args
+    """Return the lldb-gdbserver exe path.
+
+    Returns:
+        A path to the lldb-gdbserver exe if it is found to exist; otherwise,
+        returns None.
+    """
     lldb_exe = os.environ["LLDB_EXEC"]
     if not lldb_exe:
         return None
     else:
         return _get_lldb_gdbserver_from_lldb(lldb_exe)
 
+
+_LOG_LINE_REGEX = re.compile(r'^(lldb-gdbserver|debugserver)\s+<\s*(\d+)>' +
+    '\s+(read|send)\s+packet:\s+(.+)$')
+
+
+def _is_packet_lldb_gdbserver_input(packet_type, llgs_input_is_read):
+    """Return whether a given packet is input for lldb-gdbserver.
+
+    Args:
+        packet_type: a string indicating 'send' or 'receive', from a
+            gdbremote packet protocol log.
+
+        llgs_input_is_read: true if lldb-gdbserver input (content sent to
+            lldb-gdbserver) is listed as 'read' or 'send' in the packet
+            log entry.
+
+    Returns:
+        True if the packet should be considered input for lldb-gdbserver; False
+        otherwise.
+    """
+    if packet_type == 'read':
+        # when llgs is the read side, then a read packet is meant for
+        # input to llgs (when captured from the llgs/debugserver exe).
+        return llgs_input_is_read
+    elif packet_type == 'send':
+        # when llgs is the send side, then a send packet is meant to
+        # be input to llgs (when captured from the lldb exe).
+        return not llgs_input_is_read
+    else:
+        # don't understand what type of packet this is
+        raise "Unknown packet type: {}".format(packet_type)
+
+
+_GDB_REMOTE_PACKET_REGEX = re.compile(r'^\$[^\#]*\#[0-9a-fA-F]{2}')
+
+
+def expect_lldb_gdbserver_replay(
+    asserter,
+    sock,
+    log_lines,
+    read_is_llgs_input,
+    timeout_seconds,
+    logger=None):
+    """Replay socket communication with lldb-gdbserver and verify responses.
+
+    Args:
+        asserter: the object providing assertEqual(first, second, msg=None), e.g. TestCase instance.
+
+        sock: the TCP socket connected to the lldb-gdbserver exe.
+
+        log_lines: an array of text lines output from packet logging
+           within lldb or lldb-gdbserver. Should look something like
+           this:
+
+           lldb-gdbserver <  19> read packet: $QStartNoAckMode#b0
+           lldb-gdbserver <   1> send packet: +
+           lldb-gdbserver <   6> send packet: $OK#9a
+           lldb-gdbserver <   1> read packet: +
+
+        read_is_llgs_input: True if packet logs list lldb-gdbserver
+           input as the read side. False if lldb-gdbserver input is
+           listed as the send side. Logs could be generated from
+           either side, and this just allows supporting either one.
+
+        timeout_seconds: any response taking more than this number of
+           seconds will cause an exception to be raised.
+
+    Returns:
+        None if no issues.  Raises an exception if the expected communication does not
+        occur.
+
+    """
+    received_lines = []
+    receive_buffer = ''
+
+    for packet in log_lines:
+        if logger:
+            logger.debug("processing log line: {}".format(packet))
+        match = _LOG_LINE_REGEX.match(packet)
+        if match:
+            if _is_packet_lldb_gdbserver_input(
+                    match.group(3),
+                    read_is_llgs_input):
+                # handle as something to send to lldb-gdbserver on
+                # socket.
+                if logger:
+                    logger.info("sending packet to llgs: {}".format(match.group(4)))
+                sock.sendall(match.group(4))
+            else:
+                # expect it as output from lldb-gdbserver received
+                # from socket.
+                if logger:
+                    logger.info("receiving packet from llgs, should match: {}".format(match.group(4)))
+                start_time = time.time()
+                timeout_time = start_time + timeout_seconds
+
+                # while we don't have a complete line of input, wait
+                # for it from socket.
+                while len(received_lines) < 1:
+                    # check for timeout
+                    if time.time() > timeout_time:
+                        raise Exception(
+                            'timed out after {} seconds while waiting for llgs to respond with: {}, currently received: {}'.format(
+                                timeout_seconds, match.group(4), receive_buffer))
+                    can_read, _, _ = select.select(
+                        [sock], [], [], 0)
+                    if can_read and sock in can_read:
+                        new_bytes = sock.recv(4096)
+                        if new_bytes and len(new_bytes) > 0:
+                            # read the next bits from the socket
+                            if logger:
+                                logger.debug("llgs responded with bytes: {}".format(new_bytes))
+                            receive_buffer += new_bytes
+
+                            # parse fully-formed packets into individual packets
+                            has_more = len(receive_buffer) > 0
+                            while has_more:
+                                if len(receive_buffer) <= 0:
+                                    has_more = False
+                                # handle '+' ack
+                                elif receive_buffer[0] == '+':
+                                    received_lines.append('+')
+                                    receive_buffer = receive_buffer[1:]
+                                    if logger:
+                                        logger.debug('parsed packet from llgs: +, new receive_buffer: {}'.format(receive_buffer))
+                                else:
+                                    packet_match = _GDB_REMOTE_PACKET_REGEX.match(receive_buffer)
+                                    if packet_match:
+                                        received_lines.append(packet_match.group(0))
+                                        receive_buffer = receive_buffer[len(packet_match.group(0)):]
+                                        if logger:
+                                            logger.debug('parsed packet from llgs: {}, new receive_buffer: {}'.format(packet_match.group(0), receive_buffer))
+                                    else:
+                                        has_more = False
+
+                # got a line - now try to match it against expected line
+                if len(received_lines) > 0:
+                    actual_receive = received_lines.pop(0)
+                    expected_receive = match.group(4)
+                    asserter.assertEqual(actual_receive, expected_receive)
+
+    return None
+
+
 if __name__ == '__main__':
-    import sys
-    exe = get_lldb_gdbserver_exe()
-    if exe:
-        print "lldb-gdbserver exe at: {}".format(exe)
+    EXE_PATH = get_lldb_gdbserver_exe()
+    if EXE_PATH:
+        print "lldb-gdbserver path detected: {}".format(EXE_PATH)
     else:
-        print "lldb-gdbserver not specified"
+        print "lldb-gdbserver could not be found"





More information about the lldb-commits mailing list