[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