diff options
author | Michael W. Hudson <mwh@python.net> | 2002-12-17 16:15:34 (GMT) |
---|---|---|
committer | Michael W. Hudson <mwh@python.net> | 2002-12-17 16:15:34 (GMT) |
commit | cfd3884882bd7fbcadb7d89508c4af70569f87a0 (patch) | |
tree | 8634f770b3d214fb0952bdde8c0f2aeb6c512ecb /Lib | |
parent | f680cc460c06d87e9cc1beafafb4a017712f8868 (diff) | |
download | cpython-cfd3884882bd7fbcadb7d89508c4af70569f87a0.zip cpython-cfd3884882bd7fbcadb7d89508c4af70569f87a0.tar.gz cpython-cfd3884882bd7fbcadb7d89508c4af70569f87a0.tar.bz2 |
This is Richie Hindle's patch
[ 643835 ] Set Next Statement for Python debuggers
with a few tweaks by me: adding an unsigned or two, mentioning that
not all jumps are allowed in the doc for pdb, adding a NEWS item and
a note to whatsnew, and AuCTeX doing something cosmetic to libpdb.tex.
Diffstat (limited to 'Lib')
-rwxr-xr-x | Lib/pdb.py | 26 | ||||
-rw-r--r-- | Lib/test/test_trace.py | 289 |
2 files changed, 315 insertions, 0 deletions
@@ -506,6 +506,25 @@ class Pdb(bdb.Bdb, cmd.Cmd): return 1 do_c = do_cont = do_continue + def do_jump(self, arg): + if self.curindex + 1 != len(self.stack): + print "*** You can only jump within the bottom frame" + return + try: + arg = int(arg) + except ValueError: + print "*** The 'jump' command requires a line number." + else: + try: + # Do the jump, fix up our copy of the stack, and display the + # new position + self.curframe.f_lineno = arg + self.stack[self.curindex] = self.stack[self.curindex][0], arg + self.print_stack_entry(self.stack[self.curindex]) + except ValueError, e: + print '*** Jump failed:', e + do_j = do_jump + def do_quit(self, arg): self.set_quit() return 1 @@ -805,6 +824,13 @@ Continue execution until the current function returns.""" print """c(ont(inue)) Continue execution, only stop when a breakpoint is encountered.""" + def help_jump(self): + self.help_j() + + def help_j(self): + print """j(ump) lineno +Set the next line that will be executed.""" + def help_list(self): self.help_l() diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py index faee713..f973a19 100644 --- a/Lib/test/test_trace.py +++ b/Lib/test/test_trace.py @@ -221,9 +221,298 @@ class RaisingTraceFuncTestCase(unittest.TestCase): def test_exception(self): self.run_test_for_event('exception') + +# 'Jump' tests: assigning to frame.f_lineno within a trace function +# moves the execution position - it's how debuggers implement a Jump +# command (aka. "Set next statement"). + +class JumpTracer: + """Defines a trace function that jumps from one place to another, + with the source and destination lines of the jump being defined by + the 'jump' property of the function under test.""" + + def __init__(self, function): + self.function = function + self.jumpFrom = function.jump[0] + self.jumpTo = function.jump[1] + self.done = False + + def trace(self, frame, event, arg): + if not self.done and frame.f_code == self.function.func_code: + firstLine = frame.f_code.co_firstlineno + if frame.f_lineno == firstLine + self.jumpFrom: + # Cope with non-integer self.jumpTo (because of + # no_jump_to_non_integers below). + try: + frame.f_lineno = firstLine + self.jumpTo + except TypeError: + frame.f_lineno = self.jumpTo + self.done = True + return self.trace + +# The first set of 'jump' tests are for things that are allowed: + +def jump_simple_forwards(output): + output.append(1) + output.append(2) + output.append(3) + +jump_simple_forwards.jump = (1, 3) +jump_simple_forwards.output = [3] + +def jump_simple_backwards(output): + output.append(1) + output.append(2) + +jump_simple_backwards.jump = (2, 1) +jump_simple_backwards.output = [1, 1, 2] + +def jump_out_of_block_forwards(output): + for i in 1, 2: + output.append(2) + for j in [3]: # Also tests jumping over a block + output.append(4) + output.append(5) + +jump_out_of_block_forwards.jump = (3, 5) +jump_out_of_block_forwards.output = [2, 5] + +def jump_out_of_block_backwards(output): + output.append(1) + for i in [1]: + output.append(3) + for j in [2]: # Also tests jumping over a block + output.append(5) + output.append(6) + output.append(7) + +jump_out_of_block_backwards.jump = (6, 1) +jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7] + +def jump_to_codeless_line(output): + output.append(1) + # Jumping to this line should skip to the next one. + output.append(3) + +jump_to_codeless_line.jump = (1, 2) +jump_to_codeless_line.output = [3] + +def jump_to_same_line(output): + output.append(1) + output.append(2) + output.append(3) + +jump_to_same_line.jump = (2, 2) +jump_to_same_line.output = [1, 2, 3] + +# Tests jumping within a finally block, and over one. +def jump_in_nested_finally(output): + try: + output.append(2) + finally: + output.append(4) + try: + output.append(6) + finally: + output.append(8) + output.append(9) + +jump_in_nested_finally.jump = (4, 9) +jump_in_nested_finally.output = [2, 9] + +# The second set of 'jump' tests are for things that are not allowed: + +def no_jump_too_far_forwards(output): + try: + output.append(2) + output.append(3) + except ValueError, e: + output.append('after' in str(e)) + +no_jump_too_far_forwards.jump = (3, 6) +no_jump_too_far_forwards.output = [2, True] + +def no_jump_too_far_backwards(output): + try: + output.append(2) + output.append(3) + except ValueError, e: + output.append('before' in str(e)) + +no_jump_too_far_backwards.jump = (3, -1) +no_jump_too_far_backwards.output = [2, True] + +# Test each kind of 'except' line. +def no_jump_to_except_1(output): + try: + output.append(2) + except: + e = sys.exc_info()[1] + output.append('except' in str(e)) + +no_jump_to_except_1.jump = (2, 3) +no_jump_to_except_1.output = [True] + +def no_jump_to_except_2(output): + try: + output.append(2) + except ValueError: + e = sys.exc_info()[1] + output.append('except' in str(e)) + +no_jump_to_except_2.jump = (2, 3) +no_jump_to_except_2.output = [True] + +def no_jump_to_except_3(output): + try: + output.append(2) + except ValueError, e: + output.append('except' in str(e)) + +no_jump_to_except_3.jump = (2, 3) +no_jump_to_except_3.output = [True] + +def no_jump_to_except_4(output): + try: + output.append(2) + except (ValueError, RuntimeError), e: + output.append('except' in str(e)) + +no_jump_to_except_4.jump = (2, 3) +no_jump_to_except_4.output = [True] + +def no_jump_forwards_into_block(output): + try: + output.append(2) + for i in 1, 2: + output.append(4) + except ValueError, e: + output.append('into' in str(e)) + +no_jump_forwards_into_block.jump = (2, 4) +no_jump_forwards_into_block.output = [True] + +def no_jump_backwards_into_block(output): + try: + for i in 1, 2: + output.append(3) + output.append(4) + except ValueError, e: + output.append('into' in str(e)) + +no_jump_backwards_into_block.jump = (4, 3) +no_jump_backwards_into_block.output = [3, 3, True] + +def no_jump_into_finally_block(output): + try: + try: + output.append(3) + x = 1 + finally: + output.append(6) + except ValueError, e: + output.append('finally' in str(e)) + +no_jump_into_finally_block.jump = (4, 6) +no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs + +def no_jump_out_of_finally_block(output): + try: + try: + output.append(3) + finally: + output.append(5) + output.append(6) + except ValueError, e: + output.append('finally' in str(e)) + +no_jump_out_of_finally_block.jump = (5, 1) +no_jump_out_of_finally_block.output = [3, True] + +# This verifies the line-numbers-must-be-integers rule. +def no_jump_to_non_integers(output): + try: + output.append(2) + except ValueError, e: + output.append('integer' in str(e)) + +no_jump_to_non_integers.jump = (2, "Spam") +no_jump_to_non_integers.output = [True] + +# This verifies that you can't set f_lineno via _getframe or similar +# trickery. +def no_jump_without_trace_function(): + try: + previous_frame = sys._getframe().f_back + previous_frame.f_lineno = previous_frame.f_lineno + except ValueError, e: + # This is the exception we wanted; make sure the error message + # talks about trace functions. + if 'trace' not in str(e): + raise + else: + # Something's wrong - the expected exception wasn't raised. + raise RuntimeError, "Trace-function-less jump failed to fail" + + +class JumpTestCase(unittest.TestCase): + def compare_jump_output(self, expected, received): + if received != expected: + self.fail( "Outputs don't match:\n" + + "Expected: " + repr(expected) + "\n" + + "Received: " + repr(received)) + + def run_test(self, func): + tracer = JumpTracer(func) + sys.settrace(tracer.trace) + output = [] + func(output) + sys.settrace(None) + self.compare_jump_output(func.output, output) + + def test_01_jump_simple_forwards(self): + self.run_test(jump_simple_forwards) + def test_02_jump_simple_backwards(self): + self.run_test(jump_simple_backwards) + def test_03_jump_out_of_block_forwards(self): + self.run_test(jump_out_of_block_forwards) + def test_04_jump_out_of_block_backwards(self): + self.run_test(jump_out_of_block_backwards) + def test_05_jump_to_codeless_line(self): + self.run_test(jump_to_codeless_line) + def test_06_jump_to_same_line(self): + self.run_test(jump_to_same_line) + def test_07_jump_in_nested_finally(self): + self.run_test(jump_in_nested_finally) + def test_08_no_jump_too_far_forwards(self): + self.run_test(no_jump_too_far_forwards) + def test_09_no_jump_too_far_backwards(self): + self.run_test(no_jump_too_far_backwards) + def test_10_no_jump_to_except_1(self): + self.run_test(no_jump_to_except_1) + def test_11_no_jump_to_except_2(self): + self.run_test(no_jump_to_except_2) + def test_12_no_jump_to_except_3(self): + self.run_test(no_jump_to_except_3) + def test_13_no_jump_to_except_4(self): + self.run_test(no_jump_to_except_4) + def test_14_no_jump_forwards_into_block(self): + self.run_test(no_jump_forwards_into_block) + def test_15_no_jump_backwards_into_block(self): + self.run_test(no_jump_backwards_into_block) + def test_16_no_jump_into_finally_block(self): + self.run_test(no_jump_into_finally_block) + def test_17_no_jump_out_of_finally_block(self): + self.run_test(no_jump_out_of_finally_block) + def test_18_no_jump_to_non_integers(self): + self.run_test(no_jump_to_non_integers) + def test_19_no_jump_without_trace_function(self): + no_jump_without_trace_function() + def test_main(): test_support.run_unittest(TraceTestCase) test_support.run_unittest(RaisingTraceFuncTestCase) + test_support.run_unittest(JumpTestCase) if __name__ == "__main__": test_main() |