diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2017-12-27 19:31:47 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-27 19:31:47 (GMT) |
commit | ea98eba3465fff2b191610cea253d43000c84b4a (patch) | |
tree | 934979c5d1ca4f0487645c4b97b5bf1d23b7b818 | |
parent | 32518b439b9590cce0ef0639e558dc1ce2e152bb (diff) | |
download | cpython-ea98eba3465fff2b191610cea253d43000c84b4a.zip cpython-ea98eba3465fff2b191610cea253d43000c84b4a.tar.gz cpython-ea98eba3465fff2b191610cea253d43000c84b4a.tar.bz2 |
[3.6] bpo-32416: Refactor tests for the f_lineno setter and add new tests. (GH-4991). (#5016)
(cherry picked from commit 53f9135667226f33e049e327db60fb033afbd77a)
-rw-r--r-- | Lib/test/test_sys_settrace.py | 651 |
1 files changed, 405 insertions, 246 deletions
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index ec537be..49fbf4d 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -5,6 +5,19 @@ import unittest import sys import difflib import gc +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 + + 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(): @@ -495,14 +508,12 @@ class RaisingTraceFuncTestCase(unittest.TestCase): # 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): + 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): @@ -518,294 +529,453 @@ class JumpTracer: self.done = True return self.trace -# The first set of 'jump' tests are for things that are allowed: +# This verifies the line-numbers-must-be-integers rule. +def no_jump_to_non_integers(output): + try: + output.append(2) + except ValueError as e: + output.append('integer' in str(e)) -def jump_simple_forwards(output): - output.append(1) - output.append(2) - output.append(3) +# 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 as 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 AssertionError("Trace-function-less jump failed to fail") -jump_simple_forwards.jump = (1, 3) -jump_simple_forwards.output = [3] -def jump_simple_backwards(output): - output.append(1) - output.append(2) +class JumpTestCase(unittest.TestCase): + def setUp(self): + self.addCleanup(sys.settrace, sys.gettrace()) + sys.settrace(None) -jump_simple_backwards.jump = (2, 1) -jump_simple_backwards.output = [1, 1, 2] + 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 jump_out_of_block_forwards(output): - for i in 1, 2: + def run_test(self, func, jumpFrom, jumpTo, expected, error=None): + tracer = JumpTracer(func, jumpFrom, jumpTo) + sys.settrace(tracer.trace) + 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, 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 + + ## The first set of 'jump' tests are for things that are allowed: + + @jump_test(1, 3, [3]) + def test_jump_simple_forwards(output): + output.append(1) 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] + @jump_test(2, 1, [1, 1, 2]) + def test_jump_simple_backwards(output): + output.append(1) + output.append(2) -def jump_to_codeless_line(output): - output.append(1) - # Jumping to this line should skip to the next one. - output.append(3) + @jump_test(3, 5, [2, 5]) + 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 + output.append(4) + output.append(5) + + @jump_test(6, 1, [1, 3, 5, 1, 3, 5, 6, 7]) + def test_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_to_codeless_line.jump = (1, 2) -jump_to_codeless_line.output = [3] + @jump_test(1, 2, [3]) + def test_jump_to_codeless_line(output): + output.append(1) + # Jumping to this line should skip to the next one. + output.append(3) -def jump_to_same_line(output): - output.append(1) - output.append(2) - output.append(3) + @jump_test(2, 2, [1, 2, 3]) + def test_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. + @jump_test(4, 9, [2, 9]) + def test_jump_in_nested_finally(output): + try: + output.append(2) + finally: + output.append(4) + try: + output.append(6) + finally: + output.append(8) + output.append(9) -# Tests jumping within a finally block, and over one. -def jump_in_nested_finally(output): - try: - output.append(2) - finally: - output.append(4) + @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(8) - output.append(9) + 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) + while True: + output.append(3) + output.append(4) -jump_in_nested_finally.jump = (4, 9) -jump_in_nested_finally.output = [2, 9] + @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) -def jump_infinite_while_loop(output): - output.append(1) - while 1: - output.append(2) - output.append(3) + @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_infinite_while_loop.jump = (3, 4) -jump_infinite_while_loop.output = [1, 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) -# The second set of 'jump' tests are for things that are not allowed: + @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) + finally: + output.append(5) -def 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(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) -no_jump_too_far_forwards.jump = (3, 6) -no_jump_too_far_forwards.output = [2, True] + @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: + output.append(5) + raise -def no_jump_too_far_backwards(output): - try: - output.append(2) - output.append(3) - except ValueError as e: - output.append('before' in str(e)) + @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) -no_jump_too_far_backwards.jump = (3, -1) -no_jump_too_far_backwards.output = [2, True] + @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) -# 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)) + @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) -no_jump_to_except_1.jump = (2, 3) -no_jump_to_except_1.output = [True] + @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) -def no_jump_to_except_2(output): - try: + @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')) + def test_no_jump_too_far_forwards(output): + output.append(1) 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: + @jump_test(2, -2, [1], (ValueError, 'before')) + def test_no_jump_too_far_backwards(output): + output.append(1) 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] + # Test each kind of 'except' line. + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_1(output): + try: + output.append(2) + except: + output.append(4) + raise -def no_jump_to_except_4(output): - try: - output.append(2) - except (ValueError, RuntimeError) as e: - output.append('except' in str(e)) + @jump_test(2, 3, [4], (ValueError, 'except')) + def test_no_jump_to_except_2(output): + try: + output.append(2) + except ValueError: + output.append(4) + raise -no_jump_to_except_4.jump = (2, 3) -no_jump_to_except_4.output = [True] + @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(4) + raise e -def no_jump_forwards_into_block(output): - try: - output.append(2) - for i in 1, 2: + @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(4) - except ValueError as e: - output.append('into' in str(e)) + raise e -no_jump_forwards_into_block.jump = (2, 4) -no_jump_forwards_into_block.output = [True] + @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) -def no_jump_backwards_into_block(output): - try: + @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) - output.append(4) - except ValueError as e: - output.append('into' in str(e)) + i += 1 + output.append(5) -no_jump_backwards_into_block.jump = (4, 3) -no_jump_backwards_into_block.output = [3, 3, True] + @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) -def no_jump_into_finally_block(output): - try: + @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) - x = 1 finally: - output.append(6) - except ValueError as e: - output.append('finally' in str(e)) + output.append(5) -no_jump_into_finally_block.jump = (4, 6) -no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs + @jump_test(5, 2, [2, 4], (ValueError, 'into')) + def test_no_jump_backwards_into_try_finally_block(output): + try: + output.append(2) + finally: + output.append(4) + output.append(5) -def no_jump_out_of_finally_block(output): - try: + @jump_test(1, 3, [], (ValueError, 'into')) + def test_no_jump_forwards_into_try_except_block(output): + output.append(1) try: output.append(3) - finally: + except: output.append(5) - output.append(6) - except ValueError as e: - output.append('finally' in str(e)) + raise -no_jump_out_of_finally_block.jump = (5, 1) -no_jump_out_of_finally_block.output = [3, True] + @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) + raise + output.append(6) -# This verifies the line-numbers-must-be-integers rule. -def no_jump_to_non_integers(output): - try: - output.append(2) - except ValueError as e: - output.append('integer' in str(e)) + # '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: + 1/0 + except ZeroDivisionError: + output.append(4) + output.append(5) + except FloatingPointError as e: + output.append(7) + output.append(8) -no_jump_to_non_integers.jump = (2, "Spam") -no_jump_to_non_integers.output = [True] + @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: # still executed if the jump is failed + output.append(5) + output.append(6) + output.append(7) -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 = [] + @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) -# 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 as 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") + @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) + @jump_test(2, 4, [1, 4, 5, -4]) + def test_jump_across_with(output): + output.append(1) + with tracecontext(output, 2): + output.append(3) + with tracecontext(output, 4): + output.append(5) -class JumpTestCase(unittest.TestCase): - def setUp(self): - self.addCleanup(sys.settrace, sys.gettrace()) - sys.settrace(None) + @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 compare_jump_output(self, expected, received): - if received != expected: - self.fail( "Outputs don't match:\n" + - "Expected: " + repr(expected) + "\n" + - "Received: " + repr(received)) + def test_no_jump_to_non_integers(self): + self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) - 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_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) - 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): + 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_jump_across_with(self): - self.addCleanup(support.unlink, support.TESTFN) - self.run_test(jump_across_with) - def test_20_large_function(self): + def test_large_function(self): d = {} exec("""def f(output): # line 0 x = 0 # line 1 @@ -817,10 +987,7 @@ class JumpTestCase(unittest.TestCase): 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 @@ -834,8 +1001,7 @@ output.append(4) """, "<fake module>", "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) @@ -843,12 +1009,5 @@ output.append(4) self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) -def test_main(): - support.run_unittest( - TraceTestCase, - RaisingTraceFuncTestCase, - JumpTestCase - ) - if __name__ == "__main__": - test_main() + unittest.main() |