Author: Armin Rigo <ar...@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') _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit