[Lldb-commits] [lldb] r208061 - gdbremote testing: added regex support to match packets and propagate/test against previously stored matches.

Todd Fiala todd.fiala at gmail.com
Mon May 5 23:15:23 PDT 2014


Author: tfiala
Date: Tue May  6 01:15:23 2014
New Revision: 208061

URL: http://llvm.org/viewvc/llvm-project?rev=208061&view=rev
Log:
gdbremote testing: added regex support to match packets and propagate/test against previously stored matches.

Added a test validating that $qC after an inferior launch via $A
returns a thread id that an immediately followig $? reports for the
active thread. This is currently skipped on debugserver (the thread
ids don't match) and isn't yet implemented in TOT for llgs.

Added:
    lldb/trunk/test/tools/lldb-gdbserver/test/
    lldb/trunk/test/tools/lldb-gdbserver/test/test_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=208061&r1=208060&r2=208061&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py Tue May  6 01:15:23 2014
@@ -99,6 +99,14 @@ class LldbGdbServerTestCase(TestBase):
              "read packet: +"],
             True)
 
+    def add_verified_launch_packets(self, launch_args):
+        self.test_sequence.add_log_lines(
+            ["read packet: %s" % build_gdbremote_A_packet(launch_args),
+             "send packet: $OK#00",
+             "read packet: $qLaunchSuccess#a5",
+             "send packet: $OK#00"],
+            True)
+
     def expect_gdbremote_sequence(self):
         expect_lldb_gdbserver_replay(self, self.sock, self.test_sequence, self._TIMEOUT_SECONDS, self.logger)
 
@@ -210,14 +218,12 @@ class LldbGdbServerTestCase(TestBase):
         launch_args = [os.path.abspath('a.out')]
 
         self.add_no_ack_remote_stream()
+        self.add_verified_launch_packets(launch_args)
         self.test_sequence.add_log_lines(
-            ["read packet: %s" % build_gdbremote_A_packet(launch_args),
-             "send packet: $OK#00",
-             "read packet: $qLaunchSuccess#a5",
-             "send packet: $OK#00",
-             "read packet: $vCont;c#00",
+            ["read packet: $vCont;c#00",
              "send packet: $W00#00"],
             True)
+
         self.expect_gdbremote_sequence()
 
     @debugserver_test
@@ -245,14 +251,12 @@ class LldbGdbServerTestCase(TestBase):
         launch_args = [os.path.abspath('a.out'), "retval:%d" % RETVAL]
 
         self.add_no_ack_remote_stream()
+        self.add_verified_launch_packets(launch_args)
         self.test_sequence.add_log_lines(
-            ["lldb-gdbserver <   0> read packet: %s" % build_gdbremote_A_packet(launch_args),
-             "lldb-gdbserver <   6> send packet: $OK#00",
-             "lldb-gdbserver <  18> read packet: $qLaunchSuccess#a5",
-             "lldb-gdbserver <   6> send packet: $OK#00",
-             "lldb-gdbserver <   5> read packet: $vCont;c#00",
-             "lldb-gdbserver <   7> send packet: $W{0:02x}#00".format(RETVAL)],
+            ["read packet: $vCont;c#00",
+             "send packet: $W{0:02x}#00".format(RETVAL)],
             True)
+
         self.expect_gdbremote_sequence()
 
     @debugserver_test
@@ -278,14 +282,11 @@ class LldbGdbServerTestCase(TestBase):
         launch_args = [os.path.abspath('a.out'), "hello, world"]
 
         self.add_no_ack_remote_stream()
+        self.add_verified_launch_packets(launch_args)
         self.test_sequence.add_log_lines(
-            ["lldb-gdbserver <   0> read packet: %s" % build_gdbremote_A_packet(launch_args),
-             "lldb-gdbserver <   6> send packet: $OK#00",
-             "lldb-gdbserver <  18> read packet: $qLaunchSuccess#a5",
-             "lldb-gdbserver <   6> send packet: $OK#00",
-             "lldb-gdbserver <   5> read packet: $vCont;c#00",
-             "lldb-gdbserver <   7> send packet: $O{}#00".format(gdbremote_hex_encode_string("hello, world\r\n")),
-             "lldb-gdbserver <   7> send packet: $W00#00"],
+            ["read packet: $vCont;c#00",
+             "send packet: $O{}#00".format(gdbremote_hex_encode_string("hello, world\r\n")),
+             "send packet: $W00#00"],
             True)
         self.expect_gdbremote_sequence()
 
@@ -304,6 +305,40 @@ class LldbGdbServerTestCase(TestBase):
         self.buildDwarf()
         self.inferior_print_exit()
 
+    def first_launch_stop_reply_thread_matches_first_qC(self):
+        server = self.start_server()
+        self.assertIsNotNone(server)
+
+        # build launch args
+        launch_args = [os.path.abspath('a.out'), "hello, world"]
+
+        self.add_no_ack_remote_stream()
+        self.add_verified_launch_packets(launch_args)
+        self.test_sequence.add_log_lines(
+            ["read packet: $qC#00",
+             { "direction":"send", "regex":r"^\$QC([0-9a-fA-F]+)#", "capture":{1:"thread_id"} },
+             "read packet: $?#00",
+             { "direction":"send", "regex":r"^\$T[0-9a-fA-F]{2}thread:([0-9a-fA-F]+)", "expect_captures":{1:"thread_id"} }],
+            True)
+        self.expect_gdbremote_sequence()
+
+    @debugserver_test
+    @dsym_test
+    @unittest2.expectedFailure() # Possible bug.
+    def test_first_launch_stop_reply_thread_matches_first_qC_debugserver_dsym(self):
+        self.init_debugserver_test()
+        self.buildDsym()
+        self.first_launch_stop_reply_thread_matches_first_qC()
+
+    @llgs_test
+    @dwarf_test
+    @unittest2.expectedFailure()
+    def test_first_launch_stop_reply_thread_matches_first_qC_llgs_dwarf(self):
+        self.init_llgs_test()
+        self.buildDwarf()
+        self.first_launch_stop_reply_thread_matches_first_qC()
+
+
 
 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=208061&r1=208060&r2=208061&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py Tue May  6 01:15:23 2014
@@ -132,23 +132,15 @@ def expect_lldb_gdbserver_replay(
 
         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.
+        test_sequence: a GdbRemoteTestSequence instance that describes
+            the messages sent to the gdb remote and the responses
+            expected from it.
 
         timeout_seconds: any response taking more than this number of
            seconds will cause an exception to be raised.
 
+        logger: a Python logger instance.
+
     Returns:
         None if no issues.  Raises an exception if the expected communication does not
         occur.
@@ -156,6 +148,7 @@ def expect_lldb_gdbserver_replay(
     """
     received_lines = []
     receive_buffer = ''
+    context = {}
 
     for sequence_entry in test_sequence.entries:
         if sequence_entry.is_send_to_remote:
@@ -178,7 +171,7 @@ def expect_lldb_gdbserver_replay(
                 if time.time() > timeout_time:
                     raise Exception(
                         'timed out after {} seconds while waiting for llgs to respond with: {}, currently received: {}'.format(
-                            timeout_seconds, sequence_entry.exact_playload, receive_buffer))
+                            timeout_seconds, sequence_entry.exact_payload, receive_buffer))
                 can_read, _, _ = select.select([sock], [], [], 0)
                 if can_read and sock in can_read:
                     new_bytes = sock.recv(4096)
@@ -211,7 +204,7 @@ def expect_lldb_gdbserver_replay(
             # got a line - now try to match it against expected line
             if len(received_lines) > 0:
                 received_packet = received_lines.pop(0)
-                sequence_entry.assert_match(asserter, received_packet)
+                context = sequence_entry.assert_match(asserter, received_packet, context=context)
     return None
 
 
@@ -255,24 +248,108 @@ def build_gdbremote_A_packet(args_list):
 
 class GdbRemoteEntry(object):
 
-    def __init__(self, is_send_to_remote=True, exact_payload=None):
+    def __init__(self, is_send_to_remote=True, exact_payload=None, regex=None, capture=None, expect_captures=None):
+        """Create an entry representing one piece of the I/O to/from a gdb remote debug monitor.
+
+        Args:
+
+            is_send_to_remote: True if this entry is a message to be
+                sent to the gdbremote debug monitor; False if this
+                entry represents text to be matched against the reply
+                from the gdbremote debug monitor.
+
+            exact_payload: if not None, then this packet is an exact
+                send (when sending to the remote) or an exact match of
+                the response from the gdbremote. The checksums are
+                ignored on exact match requests since negotiation of
+                no-ack makes the checksum content essentially
+                undefined.
+
+            regex: currently only valid for receives from gdbremote.
+                When specified (and only if exact_payload is None),
+                indicates the gdbremote response must match the given
+                regex. Match groups in the regex can be used for two
+                different purposes: saving the match (see capture
+                arg), or validating that a match group matches a
+                previously established value (see expect_captures). It
+                is perfectly valid to have just a regex arg and to
+                specify neither capture or expect_captures args. This
+                arg only makes sense if exact_payload is not
+                specified.
+
+            capture: if specified, is a dictionary of regex match
+                group indices (should start with 1) to variable names
+                that will store the capture group indicated by the
+                index. For example, {1:"thread_id"} will store capture
+                group 1's content in the context dictionary where
+                "thread_id" is the key and the match group value is
+                the value. The value stored off can be used later in a
+                expect_captures expression. This arg only makes sense
+                when regex is specified.
+
+            expect_captures: if specified, is a dictionary of regex
+                match group indices (should start with 1) to variable
+                names, where the match group should match the value
+                existing in the context at the given variable name.
+                For example, {2:"thread_id"} indicates that the second
+                match group must match the value stored under the
+                context's previously stored "thread_id" key. This arg
+                only makes sense when regex is specified.
+        """
         self.is_send_to_remote = is_send_to_remote
-        self.exact_payload=exact_payload
+        self.exact_payload = exact_payload
+        self.regex = regex
+        self.capture = capture
+        self.expect_captures = expect_captures
 
     def is_send_to_remote(self):
         return self.is_send_to_remote
 
-    def assert_match(self, asserter, actual_packet):
+    def _assert_exact_payload_match(self, asserter, actual_packet):
+        assert_packets_equal(asserter, actual_packet, self.exact_payload)
+        return None
+
+    def _assert_regex_match(self, asserter, actual_packet, context):
+        # Ensure the actual packet matches from the start of the actual packet.
+        match = self.regex.match(actual_packet)
+        asserter.assertIsNotNone(match)
+
+        if self.capture:
+            # Handle captures.
+            for group_index, var_name in self.capture.items():
+                capture_text = match.group(group_index)
+                if not capture_text:
+                    raise Exception("No content for group index {}".format(group_index))
+                context[var_name] = capture_text
+
+        if self.expect_captures:
+            # Handle comparing matched groups to context dictionary entries.
+            for group_index, var_name in self.expect_captures.items():
+                capture_text = match.group(group_index)
+                if not capture_text:
+                    raise Exception("No content to expect for group index {}".format(group_index))
+                asserter.assertEquals(capture_text, context[var_name])
+
+        return context
+
+    def assert_match(self, asserter, actual_packet, context=None):
         # This only makes sense for matching lines coming from the
         # remote debug monitor.
         if self.is_send_to_remote:
             raise Exception("Attempted to match a packet being sent to the remote debug monitor, doesn't make sense.")
 
+        # Create a new context if needed.
+        if not context:
+            context = {}
+
         # If this is an exact payload, ensure they match exactly,
         # ignoring the packet checksum which is optional for no-ack
         # mode.
         if self.exact_payload:
-            assert_packets_equal(asserter, actual_packet, self.exact_payload)
+            self._assert_exact_payload_match(asserter, actual_packet)
+            return context
+        elif self.regex:
+            return self._assert_regex_match(asserter, actual_packet, context)
         else:
             raise Exception("Don't know how to match a remote-sent packet when exact_payload isn't specified.")
 
@@ -286,24 +363,48 @@ class GdbRemoteTestSequence(object):
 
     def add_log_lines(self, log_lines, remote_input_is_read):
         for line in log_lines:
-            if self.logger:
-                self.logger.debug("processing log line: {}".format(line))
-            match = self._LOG_LINE_REGEX.match(line)
-            if match:
-                playback_packet = match.group(2)
-                direction = match.group(1)
+            if type(line) == str:
+                # Handle log line import
+                if self.logger:
+                    self.logger.debug("processing log line: {}".format(line))
+                match = self._LOG_LINE_REGEX.match(line)
+                if match:
+                    playback_packet = match.group(2)
+                    direction = match.group(1)
+                    if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):
+                        # Handle as something to send to the remote debug monitor.
+                        if self.logger:
+                            self.logger.info("processed packet to send to remote: {}".format(playback_packet))
+                        self.entries.append(GdbRemoteEntry(is_send_to_remote=True, exact_payload=playback_packet))
+                    else:
+                        # Log line represents content to be expected from the remote debug monitor.
+                        if self.logger:
+                            self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet))
+                        self.entries.append(GdbRemoteEntry(is_send_to_remote=False,exact_payload=playback_packet))
+                else:
+                    raise Exception("failed to interpret log line: {}".format(line))
+            elif type(line) == dict:
+                # Handle more explicit control over details via dictionary.
+                direction = line.get("direction", None)
+                regex = line.get("regex", None)
+                capture = line.get("capture", None)
+                expect_captures = line.get("expect_captures", None)
+
+                # Compile the regex.
+                if regex and (type(regex) == str):
+                    regex = re.compile(regex)
+
                 if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):
                     # Handle as something to send to the remote debug monitor.
                     if self.logger:
-                        self.logger.info("processed packet to send to remote: {}".format(playback_packet))
-                    self.entries.append(GdbRemoteEntry(is_send_to_remote=True, exact_payload=playback_packet))
+                        self.logger.info("processed dict sequence to send to remote")
+                    self.entries.append(GdbRemoteEntry(is_send_to_remote=True, regex=regex, capture=capture, expect_captures=expect_captures))
                 else:
                     # Log line represents content to be expected from the remote debug monitor.
                     if self.logger:
-                        self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet))
-                    self.entries.append(GdbRemoteEntry(is_send_to_remote=False,exact_payload=playback_packet))
-            else:
-                raise Exception("failed to interpret log line: {}".format(line))
+                        self.logger.info("processed dict sequence to match receiving from remote")
+                    self.entries.append(GdbRemoteEntry(is_send_to_remote=False, regex=regex, capture=capture, expect_captures=expect_captures))
+
 
 if __name__ == '__main__':
     EXE_PATH = get_lldb_gdbserver_exe()

Added: lldb/trunk/test/tools/lldb-gdbserver/test/test_lldbgdbserverutils.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/test/test_lldbgdbserverutils.py?rev=208061&view=auto
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/test/test_lldbgdbserverutils.py (added)
+++ lldb/trunk/test/tools/lldb-gdbserver/test/test_lldbgdbserverutils.py Tue May  6 01:15:23 2014
@@ -0,0 +1,53 @@
+import os.path
+import re
+import sys
+
+# adjust path for embedded unittest2
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', '..'))
+import unittest2
+
+# adjust path for lldbgdbserverutils.py
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
+from lldbgdbserverutils import *
+
+
+class TestLldbGdbServerUtils(unittest2.TestCase):
+    def test_entry_exact_payload_match(self):
+        entry = GdbRemoteEntry(is_send_to_remote=False, exact_payload="$OK#9a")
+        entry.assert_match(self, "$OK#9a")
+
+    def test_entry_exact_payload_match_ignores_checksum(self):
+        entry = GdbRemoteEntry(is_send_to_remote=False, exact_payload="$OK#9a")
+        entry.assert_match(self, "$OK#00")
+
+    def test_entry_creates_context(self):
+        entry = GdbRemoteEntry(is_send_to_remote=False, exact_payload="$OK#9a")
+        context = entry.assert_match(self, "$OK#9a")
+        self.assertIsNotNone(context)
+
+    def test_entry_regex_matches(self):
+        entry = GdbRemoteEntry(is_send_to_remote=False, regex=re.compile(r"^\$QC([0-9a-fA-F]+)#"), capture={ 1:"thread_id" })
+        context = entry.assert_match(self, "$QC980#00")
+
+    def test_entry_regex_saves_match(self):
+        entry = GdbRemoteEntry(is_send_to_remote=False, regex=re.compile(r"^\$QC([0-9a-fA-F]+)#"), capture={ 1:"thread_id" })
+        context = entry.assert_match(self, "$QC980#00")
+        self.assertEquals(context["thread_id"], "980")
+
+    def test_entry_regex_expect_captures_success(self):
+        context = { "thread_id":"980" }
+        entry = GdbRemoteEntry(is_send_to_remote=False, regex=re.compile(r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+)"), expect_captures={ 2:"thread_id" })
+        entry.assert_match(self, "$T11thread:980;", context=context)
+
+    def test_entry_regex_expect_captures_raises_on_fail(self):
+        context = { "thread_id":"980" }
+        entry = GdbRemoteEntry(is_send_to_remote=False, regex=re.compile(r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+)"), expect_captures={ 2:"thread_id" })
+        try:
+            entry.assert_match(self, "$T11thread:970;", context=context)
+            self.fail()
+        except AssertionError:
+            # okay
+            return None
+
+if __name__ == '__main__':
+    unittest2.main()





More information about the lldb-commits mailing list