[pypy-commit] pypy reverse-debugger: Breakpoints at given line numbers: starting

arigo pypy.commits at gmail.com
Wed Aug 31 10:54:15 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: reverse-debugger
Changeset: r86779:7166e102d539
Date: 2016-08-31 16:05 +0200
http://bitbucket.org/pypy/pypy/changeset/7166e102d539/

Log:	Breakpoints at given line numbers: starting

diff --git a/pypy/interpreter/reverse_debugging.py b/pypy/interpreter/reverse_debugging.py
--- a/pypy/interpreter/reverse_debugging.py
+++ b/pypy/interpreter/reverse_debugging.py
@@ -1,5 +1,5 @@
 import sys
-from rpython.rlib import revdb
+from rpython.rlib import revdb, rpath, rstring
 from rpython.rlib.debug import make_sure_not_resized
 from rpython.rlib.objectmodel import specialize, we_are_translated
 from rpython.rtyper.annlowlevel import cast_gcref_to_instance
@@ -14,6 +14,7 @@
     standard_code = True
     breakpoint_stack_id = 0
     breakpoint_funcnames = None
+    breakpoint_filelines = None
     printed_objects = {}
     metavars = []
     watch_progs = []
@@ -512,22 +513,101 @@
 lambda_locals = lambda: command_locals
 
 
+def valid_identifier(s):
+    if not s:
+        return False
+    if s[0].isdigit():
+        return False
+    for c in s:
+        if not (c.isalnum() or c == '_'):
+            return False
+    return True
+
+def add_breakpoint_funcname(name, i):
+    if dbstate.breakpoint_funcnames is None:
+        dbstate.breakpoint_funcnames = {}
+    dbstate.breakpoint_funcnames[name] = i
+
+def add_breakpoint_fileline(filename, lineno, i):
+    if dbstate.breakpoint_filelines is None:
+        dbstate.breakpoint_filelines = {}
+    linenos = dbstate.breakpoint_filelines.setdefault(filename, {})
+    linenos[lineno] = i
+
+def add_breakpoint(name, i):
+    # if it is empty, complain
+    if not name:
+        revdb.send_output("Empty breakpoint name\n")
+        return
+    # if it is surrounded by < >, it is the name of a code object
+    if name.startswith('<') and name.endswith('>'):
+        add_breakpoint_funcname(name, i)
+        return
+    # if it has no ':', it can be a valid identifier (which we
+    # register as a function name), or a lineno
+    original_name = name
+    if ':' not in name:
+        try:
+            lineno = int(name)
+        except ValueError:
+            if not valid_identifier(name):
+                revdb.send_output(
+                    'Note: "%s()" doesn''t look like a function name. '
+                    'Setting breakpoint anyway\n' % (name,))
+            add_breakpoint_funcname(name, i)
+            return
+        # "number" does the same as ":number"
+        filename = ''
+    else:
+        # if it has a ':', it must end in ':lineno'
+        j = name.rfind(':')
+        try:
+            lineno = int(name[j+1:])
+        except ValueError:
+            revdb.send_output('"%s": expected a line number after colon\n' % (
+                name,))
+            return
+        filename = name[:j]
+
+    # the text before must be a pathname, possibly a relative one,
+    # or be escaped by < >.  if it isn't, make it absolute and normalized
+    # and warn if it doesn't end in '.py'.
+    if filename == '':
+        frame = fetch_cur_frame()
+        if frame is None:
+            return
+        filename = frame.getcode().co_filename
+    elif filename.startswith('<') and filename.endswith('>'):
+        pass    # use unmodified
+    elif not filename.lower().endswith('.py'):
+        # use unmodified, but warn
+        revdb.send_output(
+            'Note: "%s" doesn''t look like a co_filename. '
+            'Setting breakpoint anyway\n' % (filename,))
+    elif '\x00' not in filename:
+        filename = rstring.assert_str0(filename)
+        filename = rpath.rabspath(filename)
+        filename = rpath.rnormpath(filename)
+
+    add_breakpoint_fileline(filename, lineno, i)
+    name = '%s:%d' % (filename, lineno)
+    if name != original_name:
+        revdb.send_change_breakpoint(i, name)
+
 def command_breakpoints(cmd, extra):
     space = dbstate.space
     dbstate.breakpoint_stack_id = cmd.c_arg1
     revdb.set_thread_breakpoint(cmd.c_arg2)
-    funcnames = None
+    dbstate.breakpoint_funcnames = None
+    dbstate.breakpoint_filelines = None
     watch_progs = []
     with non_standard_code:
         for i, kind, name in revdb.split_breakpoints_arg(extra):
             if kind == 'B':
-                if funcnames is None:
-                    funcnames = {}
-                funcnames[name] = i
+                add_breakpoint(name, i)
             elif kind == 'W':
                 code = interp_marshal.loads(space, space.wrap(name))
                 watch_progs.append((code, i, ''))
-    dbstate.breakpoint_funcnames = funcnames
     dbstate.watch_progs = watch_progs[:]
 lambda_breakpoints = lambda: command_breakpoints
 
diff --git a/pypy/interpreter/test/test_reverse_debugging.py b/pypy/interpreter/test/test_reverse_debugging.py
--- a/pypy/interpreter/test/test_reverse_debugging.py
+++ b/pypy/interpreter/test/test_reverse_debugging.py
@@ -1,5 +1,7 @@
 import dis
 from pypy.interpreter.reverse_debugging import *
+from pypy.interpreter import reverse_debugging
+from rpython.rlib import revdb, rpath
 from hypothesis import given, strategies, example
 
 
@@ -34,3 +36,83 @@
         assert lstart[index] == chr(next_start - index
                                     if next_start - index <= 255
                                     else 255)
+
+class FakeFrame:
+    def __init__(self, code):
+        self.__code = code
+    def getcode(self):
+        return self.__code
+
+class FakeCode:
+    def __init__(self, co_filename):
+        self.co_filename = co_filename
+
+def check_add_breakpoint(input, curfilename=None,
+                         expected_funcname=None,
+                         expected_fileline=None,
+                         expected_output=None,
+                         expected_chbkpt=None):
+    dbstate.__dict__.clear()
+    prev = revdb.send_answer, reverse_debugging.fetch_cur_frame
+    try:
+        messages = []
+        def got_message(cmd, arg1=0, arg2=0, arg3=0, extra=""):
+            messages.append((cmd, arg1, arg2, arg3, extra))
+        def my_cur_frame():
+            assert curfilename is not None
+            return FakeFrame(FakeCode(curfilename))
+        revdb.send_answer = got_message
+        reverse_debugging.fetch_cur_frame = my_cur_frame
+        add_breakpoint(input, 5)
+    finally:
+        revdb.send_answer, reverse_debugging.fetch_cur_frame = prev
+
+    if expected_funcname is None:
+        assert dbstate.breakpoint_funcnames is None
+    else:
+        assert dbstate.breakpoint_funcnames == {expected_funcname: 5}
+
+    if expected_fileline is None:
+        assert dbstate.breakpoint_filelines is None
+    else:
+        filename, lineno = expected_fileline
+        assert dbstate.breakpoint_filelines == {filename: {lineno: 5}}
+
+    got_output = None
+    got_chbkpt = None
+    if messages:
+        assert len(messages) <= 1
+        if messages[0][0] == revdb.ANSWER_TEXT:
+            got_output = messages[0][-1]
+        if messages[0][0] == revdb.ANSWER_CHBKPT:
+            assert messages[0][1] == 5
+            got_chbkpt = messages[0][-1]
+
+    assert got_output == expected_output
+    assert got_chbkpt == expected_chbkpt
+
+def fullpath(path):
+    return rpath.rnormpath(rpath.rabspath(path))
+
+def test_add_breakpoint():
+    check_add_breakpoint('', expected_output="Empty breakpoint name\n")
+    check_add_breakpoint('foo42', expected_funcname="foo42")
+    check_add_breakpoint('foo.bar', expected_funcname="foo.bar",
+        expected_output='Note: "foo.bar()" doesn''t look like a function name.'
+                        ' Setting breakpoint anyway\n')
+    check_add_breakpoint('<foo.bar>', expected_funcname="<foo.bar>")
+    check_add_breakpoint('42', curfilename='abcd',
+                         expected_fileline=('abcd', 42),
+                         expected_chbkpt='abcd:42')
+    check_add_breakpoint(':42', curfilename='abcd',
+                         expected_fileline=('abcd', 42),
+                         expected_chbkpt='abcd:42')
+    check_add_breakpoint('abcd:42', expected_fileline=('abcd', 42),
+        expected_output='Note: "abcd" doesnt look like a co_filename.'
+                        ' Setting breakpoint anyway\n')
+    full = fullpath('abcd.py')
+    check_add_breakpoint('abcd.py:42',
+                         expected_fileline=(full, 42),
+                         expected_chbkpt='%s:42' % full)
+    check_add_breakpoint('%s:42' % full,
+                         expected_fileline=(full, 42))
diff --git a/rpython/rlib/revdb.py b/rpython/rlib/revdb.py
--- a/rpython/rlib/revdb.py
+++ b/rpython/rlib/revdb.py
@@ -24,6 +24,7 @@
 ANSWER_STACKID  = 21
 ANSWER_NEXTNID  = 22
 ANSWER_WATCH    = 23
+ANSWER_CHBKPT   = 24
 
 
 def stop_point(place=0):
@@ -54,6 +55,9 @@
 def send_linecache(filename, linenum, strip=True):
     send_answer(ANSWER_LINECACHE, linenum, int(strip), extra=filename)
 
+def send_change_breakpoint(breakpointnum, newtext):
+    send_answer(ANSWER_CHBKPT, breakpointnum, extra=newtext)
+
 def current_time():
     """For RPython debug commands: returns the current time."""
     return llop.revdb_get_value(lltype.SignedLongLong, 'c')


More information about the pypy-commit mailing list