[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