From e18aed2249ebedb7b7e2aa5f75845615b5585ca4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 6 Dec 2017 23:57:53 +0200 Subject: [PATCH 1/5] Refactor jump tests. --- Lib/test/test_sys_settrace.py | 446 ++++++++++++++-------------------- 1 file changed, 184 insertions(+), 262 deletions(-) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 51dd9d95b2de9d..85cb1a8a86a213 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -5,6 +5,8 @@ import sys import difflib import gc +from contextlib import nullcontext +from functools import wraps # A very basic example. If this fails, we're in deep trouble. def basic(): @@ -545,10 +547,10 @@ class JumpTracer: with the source and destination lines of the jump being defined by the 'jump' property of the function under test.""" - def __init__(self, function): + def __init__(self, function, jumpFrom, jumpTo): self.function = function - self.jumpFrom = function.jump[0] - self.jumpTo = function.jump[1] + self.jumpFrom = jumpFrom + self.jumpTo = jumpTo self.done = False def trace(self, frame, event, arg): @@ -564,194 +566,6 @@ def trace(self, frame, event, arg): 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] - -def jump_infinite_while_loop(output): - output.append(1) - while 1: - output.append(2) - output.append(3) - -jump_infinite_while_loop.jump = (3, 4) -jump_infinite_while_loop.output = [1, 3] - -# 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 as 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 as 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 as 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) as 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 as 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 as 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 as 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 as 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: @@ -759,17 +573,6 @@ def no_jump_to_non_integers(output): except ValueError as e: output.append('integer' in str(e)) -no_jump_to_non_integers.jump = (2, "Spam") -no_jump_to_non_integers.output = [True] - -def jump_across_with(output): - with open(support.TESTFN, "wb") as fp: - pass - with open(support.TESTFN, "wb") as fp: - pass -jump_across_with.jump = (1, 3) -jump_across_with.output = [] - # This verifies that you can't set f_lineno via _getframe or similar # trickery. def no_jump_without_trace_function(): @@ -797,59 +600,191 @@ def compare_jump_output(self, expected, received): "Expected: " + repr(expected) + "\n" + "Received: " + repr(received)) - def run_test(self, func): - tracer = JumpTracer(func) + def run_test(self, func, jumpFrom, jumpTo, expected): + tracer = JumpTracer(func, jumpFrom, jumpTo) 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_jump_infinite_while_loop(self): - self.run_test(jump_infinite_while_loop) - 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) + self.compare_jump_output(expected, output) + + def jump_test(jumpFrom, jumpTo, expected): + def decorator(func): + @wraps(func) + def test(self): + self.run_test(func, jumpFrom+1, jumpTo+1, expected) + test.__name__ = func.__name__ + return test + return decorator + + ## The first set of 'jump' tests are for things that are allowed: + + @jump_test(1, 3, [3]) + def test_01_jump_simple_forwards(output): + output.append(1) + output.append(2) + output.append(3) + + @jump_test(2, 1, [1, 1, 2]) + def test_02_jump_simple_backwards(output): + output.append(1) + output.append(2) + + @jump_test(3, 5, [2, 5]) + def test_03_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_test(6, 1, [1, 3, 5, 1, 3, 5, 6, 7]) + def test_04_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_test(1, 2, [3]) + def test_05_jump_to_codeless_line(output): + output.append(1) + # Jumping to this line should skip to the next one. + output.append(3) + + @jump_test(2, 2, [1, 2, 3]) + def test_06_jump_to_same_line(output): + output.append(1) + output.append(2) + output.append(3) + + # Tests jumping within a finally block, and over one. + @jump_test(4, 9, [2, 9]) + def test_07_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_test(3, 4, [1, 4]) + def test_jump_infinite_while_loop(output): + output.append(1) + while True: + output.append(3) + output.append(4) + + # The second set of 'jump' tests are for things that are not allowed: + + @jump_test(3, 6, [2, True]) + def test_08_no_jump_too_far_forwards(output): + try: + output.append(2) + output.append(3) + except ValueError as e: + output.append('after' in str(e)) + + @jump_test(3, -2, [2, True]) + def test_09_no_jump_too_far_backwards(output): + try: + output.append(2) + output.append(3) + except ValueError as e: + output.append('before' in str(e)) + + # Test each kind of 'except' line. + @jump_test(2, 3, [True]) + def test_10_no_jump_to_except_1(output): + try: + output.append(2) + except: + e = sys.exc_info()[1] + output.append('except' in str(e)) + + @jump_test(2, 3, [True]) + def test_11_no_jump_to_except_2(output): + try: + output.append(2) + except ValueError: + e = sys.exc_info()[1] + output.append('except' in str(e)) + + @jump_test(2, 3, [True]) + def test_12_no_jump_to_except_3(output): + try: + output.append(2) + except ValueError as e: + output.append('except' in str(e)) + + @jump_test(2, 3, [True]) + def test_13_no_jump_to_except_4(output): + try: + output.append(2) + except (ValueError, RuntimeError) as e: + output.append('except' in str(e)) + + @jump_test(2, 4, [True]) + def test_14_no_jump_forwards_into_block(output): + try: + output.append(2) + for i in 1, 2: + output.append(4) + except ValueError as e: + output.append('into' in str(e)) + + @jump_test(4, 3, [3, 3, True]) + def test_15_no_jump_backwards_into_block(output): + try: + for i in 1, 2: + output.append(3) + output.append(4) + except ValueError as e: + output.append('into' in str(e)) + + @jump_test(4, 6, [3, 6, True]) # The 'finally' still runs + def test_16_no_jump_into_finally_block(output): + try: + try: + output.append(3) + output.append(4) + finally: + output.append(6) + except ValueError as e: + output.append('finally' in str(e)) + + @jump_test(6, 2, [2, 4, True]) + def test_17_no_jump_out_of_finally_block(output): + try: + output.append(2) + try: + output.append(4) + finally: + output.append(6) + output.append(7) + except ValueError as e: + output.append('finally' in str(e)) + def test_18_no_jump_to_non_integers(self): - self.run_test(no_jump_to_non_integers) + self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) + def test_19_no_jump_without_trace_function(self): # Must set sys.settrace(None) in setUp(), else condition is not # triggered. no_jump_without_trace_function() - def test_jump_across_with(self): - self.addCleanup(support.unlink, support.TESTFN) - self.run_test(jump_across_with) + + @jump_test(2, 4, [2, 4]) + def test_jump_across_with(output): + output.append(2) + with nullcontext(): + output.append(3) + with nullcontext(): + output.append(4) def test_20_large_function(self): d = {} @@ -863,10 +798,7 @@ def test_20_large_function(self): output.append(x) # line 1007 return""" % ('\n' * 1000,), d) f = d['f'] - - f.jump = (2, 1007) - f.output = [0] - self.run_test(f) + self.run_test(f, 2, 1007, [0]) def test_jump_to_firstlineno(self): # This tests that PDB can jump back to the first line in a @@ -880,8 +812,7 @@ def test_jump_to_firstlineno(self): """, "", "exec") class fake_function: __code__ = code - jump = (2, 0) - tracer = JumpTracer(fake_function) + tracer = JumpTracer(fake_function, 2, 0) sys.settrace(tracer.trace) namespace = {"output": []} exec(code, namespace) @@ -889,14 +820,5 @@ class fake_function: self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) -def test_main(): - support.run_unittest( - TraceTestCase, - SkipLineEventsTraceTestCase, - TraceOpcodesTestCase, - RaisingTraceFuncTestCase, - JumpTestCase - ) - if __name__ == "__main__": - test_main() + unittest.main() From 5e9154999089980efe63fa648ef61afaf8f457df Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 23 Dec 2017 19:02:36 +0200 Subject: [PATCH 2/5] More refactoring and new tests. --- Lib/test/test_sys_settrace.py | 300 +++++++++++++++++++++++++--------- 1 file changed, 226 insertions(+), 74 deletions(-) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 85cb1a8a86a213..ad8cd7aee120f2 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -5,9 +5,19 @@ import sys import difflib import gc -from contextlib import nullcontext from functools import wraps +class tracecontext: + def __init__(self, output, value): + self.output = output + self.value = value + + def __enter__(self): + self.output.append(self.value) + + def __exit__(self, *exc_info): + self.output.append(-self.value) + # A very basic example. If this fails, we're in deep trouble. def basic(): return 1 @@ -600,38 +610,41 @@ def compare_jump_output(self, expected, received): "Expected: " + repr(expected) + "\n" + "Received: " + repr(received)) - def run_test(self, func, jumpFrom, jumpTo, expected): + def run_test(self, func, jumpFrom, jumpTo, expected, error=None): tracer = JumpTracer(func, jumpFrom, jumpTo) sys.settrace(tracer.trace) output = [] - func(output) + if error is None: + func(output) + else: + with self.assertRaisesRegex(*error): + func(output) sys.settrace(None) self.compare_jump_output(expected, output) - def jump_test(jumpFrom, jumpTo, expected): + def jump_test(jumpFrom, jumpTo, expected, error=None): def decorator(func): @wraps(func) def test(self): - self.run_test(func, jumpFrom+1, jumpTo+1, expected) - test.__name__ = func.__name__ + self.run_test(func, jumpFrom+1, jumpTo+1, expected, error) return test return decorator ## The first set of 'jump' tests are for things that are allowed: @jump_test(1, 3, [3]) - def test_01_jump_simple_forwards(output): + def test_jump_simple_forwards(output): output.append(1) output.append(2) output.append(3) @jump_test(2, 1, [1, 1, 2]) - def test_02_jump_simple_backwards(output): + def test_jump_simple_backwards(output): output.append(1) output.append(2) @jump_test(3, 5, [2, 5]) - def test_03_jump_out_of_block_forwards(output): + def test_jump_out_of_block_forwards(output): for i in 1, 2: output.append(2) for j in [3]: # Also tests jumping over a block @@ -639,7 +652,7 @@ def test_03_jump_out_of_block_forwards(output): output.append(5) @jump_test(6, 1, [1, 3, 5, 1, 3, 5, 6, 7]) - def test_04_jump_out_of_block_backwards(output): + def test_jump_out_of_block_backwards(output): output.append(1) for i in [1]: output.append(3) @@ -649,20 +662,20 @@ def test_04_jump_out_of_block_backwards(output): output.append(7) @jump_test(1, 2, [3]) - def test_05_jump_to_codeless_line(output): + def test_jump_to_codeless_line(output): output.append(1) # Jumping to this line should skip to the next one. output.append(3) @jump_test(2, 2, [1, 2, 3]) - def test_06_jump_to_same_line(output): + def test_jump_to_same_line(output): output.append(1) output.append(2) output.append(3) # Tests jumping within a finally block, and over one. @jump_test(4, 9, [2, 9]) - def test_07_jump_in_nested_finally(output): + def test_jump_in_nested_finally(output): try: output.append(2) finally: @@ -680,113 +693,252 @@ def test_jump_infinite_while_loop(output): output.append(3) output.append(4) - # The second set of 'jump' tests are for things that are not allowed: + @jump_test(2, 3, [1, 3]) + def test_jump_forwards_out_of_with_block(output): + with tracecontext(output, 1): + output.append(2) + output.append(3) - @jump_test(3, 6, [2, True]) - def test_08_no_jump_too_far_forwards(output): + @jump_test(3, 1, [1, 2, 1, 2, 3, -2]) + def test_jump_backwards_out_of_with_block(output): + output.append(1) + with tracecontext(output, 2): + output.append(3) + + @jump_test(2, 5, [5]) + def test_jump_forwards_out_of_try_finally_block(output): try: output.append(2) + finally: + output.append(4) + output.append(5) + + @jump_test(3, 1, [1, 1, 3, 5]) + def test_jump_backwards_out_of_try_finally_block(output): + output.append(1) + try: output.append(3) - except ValueError as e: - output.append('after' in str(e)) + finally: + output.append(5) - @jump_test(3, -2, [2, True]) - def test_09_no_jump_too_far_backwards(output): + @jump_test(2, 6, [6]) + def test_jump_forwards_out_of_try_except_block(output): try: output.append(2) + except: + output.append(4) + raise + output.append(6) + + @jump_test(3, 1, [1, 1, 3]) + def test_jump_backwards_out_of_try_except_block(output): + output.append(1) + try: output.append(3) - except ValueError as e: - output.append('before' in str(e)) + except: + output.append(5) + raise + + @jump_test(5, 7, [4, 7, 8]) + def test_jump_between_except_blocks(output): + try: + 1/0 + except ZeroDivisionError: + output.append(4) + output.append(5) + except FloatingPointError: + output.append(7) + output.append(8) + + @jump_test(5, 6, [4, 6, 7]) + def test_jump_within_except_block(output): + try: + 1/0 + except: + output.append(4) + output.append(5) + output.append(6) + output.append(7) + + # The second set of 'jump' tests are for things that are not allowed: + + @jump_test(2, 3, [1], (ValueError, 'after')) + def test_no_jump_too_far_forwards(output): + output.append(1) + output.append(2) + + @jump_test(2, -2, [1], (ValueError, 'before')) + def test_no_jump_too_far_backwards(output): + output.append(1) + output.append(2) # Test each kind of 'except' line. - @jump_test(2, 3, [True]) - def test_10_no_jump_to_except_1(output): + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_1(output): try: output.append(2) except: - e = sys.exc_info()[1] - output.append('except' in str(e)) + output.append(4) + raise - @jump_test(2, 3, [True]) - def test_11_no_jump_to_except_2(output): + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_2(output): try: output.append(2) except ValueError: - e = sys.exc_info()[1] - output.append('except' in str(e)) + output.append(4) + raise - @jump_test(2, 3, [True]) - def test_12_no_jump_to_except_3(output): + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_3(output): try: output.append(2) except ValueError as e: - output.append('except' in str(e)) + output.append(4) + raise e - @jump_test(2, 3, [True]) - def test_13_no_jump_to_except_4(output): + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_4(output): try: output.append(2) except (ValueError, RuntimeError) as e: - output.append('except' in str(e)) + output.append(4) + raise e - @jump_test(2, 4, [True]) - def test_14_no_jump_forwards_into_block(output): + @jump_test(1, 3, [], (ValueError, 'into')) + def test_no_jump_forwards_into_for_block(output): + output.append(1) + for i in 1, 2: + output.append(3) + + @jump_test(3, 2, [2, 2], (ValueError, 'into')) + def test_no_jump_backwards_into_for_block(output): + for i in 1, 2: + output.append(2) + output.append(3) + + @jump_test(2, 4, [], (ValueError, 'into')) + def test_no_jump_forwards_into_while_block(output): + i = 1 + output.append(2) + while i <= 2: + output.append(4) + i += 1 + + @jump_test(5, 3, [3, 3], (ValueError, 'into')) + def test_no_jump_backwards_into_while_block(output): + i = 1 + while i <= 2: + output.append(3) + i += 1 + output.append(5) + + @jump_test(1, 3, [], (ValueError, 'into')) + def test_no_jump_forwards_into_with_block(output): + output.append(1) + with tracecontext(output, 2): + output.append(3) + + @jump_test(3, 2, [1, 2, -1], (ValueError, 'into')) + def test_no_jump_backwards_into_with_block(output): + with tracecontext(output, 1): + output.append(2) + output.append(3) + + @jump_test(1, 3, [], (ValueError, 'into')) + def test_no_jump_forwards_into_try_finally_block(output): + output.append(1) + try: + output.append(3) + finally: + output.append(5) + + @jump_test(5, 2, [2, 4], (ValueError, 'into')) + def test_no_jump_backwards_into_try_finally_block(output): try: output.append(2) - for i in 1, 2: - output.append(4) - except ValueError as e: - output.append('into' in str(e)) + finally: + output.append(4) + output.append(5) - @jump_test(4, 3, [3, 3, True]) - def test_15_no_jump_backwards_into_block(output): + @jump_test(1, 3, [], (ValueError, 'into')) + def test_no_jump_forwards_into_try_except_block(output): + output.append(1) try: - for i in 1, 2: - output.append(3) + output.append(3) + except: + output.append(5) + raise + + @jump_test(6, 2, [2], (ValueError, 'into')) + def test_no_jump_backwards_into_try_except_block(output): + try: + output.append(2) + except: output.append(4) - except ValueError as e: - output.append('into' in str(e)) + raise + output.append(6) - @jump_test(4, 6, [3, 6, True]) # The 'finally' still runs - def test_16_no_jump_into_finally_block(output): + @jump_test(5, 7, [4], (ValueError, 'into')) + def test_no_jump_between_except_blocks_2(output): try: - try: - output.append(3) - output.append(4) - finally: - output.append(6) - except ValueError as e: - output.append('finally' in str(e)) + 1/0 + except ZeroDivisionError: + output.append(4) + output.append(5) + except FloatingPointError as e: + output.append(7) + output.append(8) - @jump_test(6, 2, [2, 4, True]) - def test_17_no_jump_out_of_finally_block(output): + @jump_test(3, 5, [2, 5], (ValueError, 'finally')) + def test_no_jump_into_finally_block(output): try: output.append(2) - try: - output.append(4) - finally: - output.append(6) - output.append(7) - except ValueError as e: - output.append('finally' in str(e)) + output.append(3) + finally: + output.append(5) - def test_18_no_jump_to_non_integers(self): + @jump_test(1, 5, [], (ValueError, 'finally')) + def test_no_jump_into_finally_block_2(output): + output.append(1) + try: + output.append(3) + finally: + output.append(5) + + @jump_test(5, 1, [1, 3], (ValueError, 'finally')) + def test_no_jump_out_of_finally_block(output): + output.append(1) + try: + output.append(3) + finally: + output.append(5) + + def test_no_jump_to_non_integers(self): self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) - def test_19_no_jump_without_trace_function(self): + def test_no_jump_without_trace_function(self): # Must set sys.settrace(None) in setUp(), else condition is not # triggered. no_jump_without_trace_function() - @jump_test(2, 4, [2, 4]) + @jump_test(2, 4, [1, 4, 5, -4]) def test_jump_across_with(output): - output.append(2) - with nullcontext(): + output.append(1) + with tracecontext(output, 2): output.append(3) - with nullcontext(): - output.append(4) + with tracecontext(output, 4): + output.append(5) + + @jump_test(3, 5, [1, 2, -2], (ValueError, 'into')) + def test_jump_across_with_2(output): + output.append(1) + with tracecontext(output, 2): + output.append(3) + with tracecontext(output, 4): + output.append(5) - def test_20_large_function(self): + def test_large_function(self): d = {} exec("""def f(output): # line 0 x = 0 # line 1 From 9c834de1087c17e611b0b4529a0f769d7dd010b3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2017 18:06:07 +0200 Subject: [PATCH 3/5] Add complex tests. --- Lib/test/test_sys_settrace.py | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index ad8cd7aee120f2..bdd54010ba5846 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -686,6 +686,32 @@ def test_jump_in_nested_finally(output): output.append(8) output.append(9) + @jump_test(6, 7, [2, 7], (ZeroDivisionError, '')) + def test_jump_in_nested_finally_2(output): + try: + output.append(2) + 1/0 + return + finally: + output.append(6) + output.append(7) + output.append(8) + + @jump_test(6, 11, [2, 11], (ZeroDivisionError, '')) + def test_jump_in_nested_finally_3(output): + try: + output.append(2) + 1/0 + return + finally: + output.append(6) + try: + output.append(8) + finally: + output.append(10) + output.append(11) + output.append(12) + @jump_test(3, 4, [1, 4]) def test_jump_infinite_while_loop(output): output.append(1) @@ -760,6 +786,57 @@ def test_jump_within_except_block(output): output.append(6) output.append(7) + @jump_test(8, 11, [1, 3, 5, 11, 12]) + def test_jump_out_of_complex_nested_blocks(output): + output.append(1) + for i in [1]: + output.append(3) + for j in [1, 2]: + output.append(5) + try: + for k in [1, 2]: + output.append(8) + finally: + output.append(10) + output.append(11) + output.append(12) + + @jump_test(3, 5, [1, 2, 5]) + def test_jump_out_of_with_assignment(output): + output.append(1) + with tracecontext(output, 2) \ + as x: + output.append(4) + output.append(5) + + @jump_test(3, 6, [1, 6, 8, 9]) + def test_jump_over_return_in_try_finally_block(output): + output.append(1) + try: + output.append(3) + if not output: # always false + return + output.append(6) + finally: + output.append(8) + output.append(9) + + @jump_test(5, 8, [1, 3, 8, 10, 11, 13]) + def test_jump_over_break_in_try_finally_block(output): + output.append(1) + while True: + output.append(3) + try: + output.append(5) + if not output: # always false + break + output.append(8) + finally: + output.append(10) + output.append(11) + break + output.append(13) + # The second set of 'jump' tests are for things that are not allowed: @jump_test(2, 3, [1], (ValueError, 'after')) From 23143bef5cfd20625a445d9ddb6892c20dc1dd00 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 26 Dec 2017 22:32:15 +0200 Subject: [PATCH 4/5] Address review comments. --- Lib/test/test_sys_settrace.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index bdd54010ba5846..3ade693368cd0c 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -8,6 +8,7 @@ from functools import wraps class tracecontext: + """Contex manager that traces its enter and exit.""" def __init__(self, output, value): self.output = output self.value = value @@ -553,9 +554,7 @@ def g(frame, event, arg): # 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.""" + """Defines a trace function that jumps from one place to another.""" def __init__(self, function, jumpFrom, jumpTo): self.function = function @@ -596,7 +595,7 @@ def no_jump_without_trace_function(): raise else: # Something's wrong - the expected exception wasn't raised. - raise RuntimeError("Trace-function-less jump failed to fail") + raise AssertionError("Trace-function-less jump failed to fail") class JumpTestCase(unittest.TestCase): @@ -623,9 +622,13 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None): self.compare_jump_output(expected, output) def jump_test(jumpFrom, jumpTo, expected, error=None): + """Decorator that creates a test that makes a jump + from one place to another in the following code. + """ def decorator(func): @wraps(func) def test(self): + # +1 to compensate a decorator line self.run_test(func, jumpFrom+1, jumpTo+1, expected, error) return test return decorator @@ -956,6 +959,7 @@ def test_no_jump_backwards_into_try_except_block(output): raise output.append(6) + # 'except' with a variable creates an implicit finally block @jump_test(5, 7, [4], (ValueError, 'into')) def test_no_jump_between_except_blocks_2(output): try: @@ -967,13 +971,15 @@ def test_no_jump_between_except_blocks_2(output): output.append(7) output.append(8) - @jump_test(3, 5, [2, 5], (ValueError, 'finally')) + @jump_test(3, 6, [2, 5, 6], (ValueError, 'finally')) def test_no_jump_into_finally_block(output): try: output.append(2) output.append(3) - finally: + finally: # still executed if the jump is failed output.append(5) + output.append(6) + output.append(7) @jump_test(1, 5, [], (ValueError, 'finally')) def test_no_jump_into_finally_block_2(output): From 54652bd8483fa9ac1a60b59e47899765d66f48f8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 27 Dec 2017 00:07:44 +0200 Subject: [PATCH 5/5] Reorder tests. --- Lib/test/test_sys_settrace.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 3ade693368cd0c..4d88ae5fc2e32f 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -997,14 +997,6 @@ def test_no_jump_out_of_finally_block(output): finally: output.append(5) - def test_no_jump_to_non_integers(self): - self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) - - def test_no_jump_without_trace_function(self): - # Must set sys.settrace(None) in setUp(), else condition is not - # triggered. - no_jump_without_trace_function() - @jump_test(2, 4, [1, 4, 5, -4]) def test_jump_across_with(output): output.append(1) @@ -1021,6 +1013,14 @@ def test_jump_across_with_2(output): with tracecontext(output, 4): output.append(5) + def test_no_jump_to_non_integers(self): + self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) + + def test_no_jump_without_trace_function(self): + # Must set sys.settrace(None) in setUp(), else condition is not + # triggered. + no_jump_without_trace_function() + def test_large_function(self): d = {} exec("""def f(output): # line 0