From: Michael W. Hudson Date: Wed, 2 Oct 2002 13:13:45 +0000 (+0000) Subject: Fix for the recursion_level bug Armin Rigo reported in sf X-Git-Tag: v2.2.2b1~83 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8b66d778157c5fd1d4db68822b6451abf792ce11;p=thirdparty%2FPython%2Fcpython.git Fix for the recursion_level bug Armin Rigo reported in sf patch #617312, both on the trunk and the 22-maint branch. Also added a test case, and ported the test_trace I wrote for HEAD to 2.2.2 (with all those horrible extra 'line' events ;-). --- diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py new file mode 100644 index 000000000000..a0483ebce947 --- /dev/null +++ b/Lib/test/test_trace.py @@ -0,0 +1,216 @@ +# Testing the line trace facility. + +import test_support +import unittest +import sys +import difflib + +# A very basic example. If this fails, we're in deep trouble. +def basic(): + return 1 + +basic.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (1, 'return')] + +# Armin Rigo's failing example: +def arigo_example(): + x = 1 + del x + while 0: + pass + x = 1 + +arigo_example.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (3, 'line'), + (5, 'line'), + (5, 'return')] + +# check that lines consisting of just one instruction get traced: +def one_instr_line(): + x = 1 + del x + x = 1 + +one_instr_line.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (3, 'return')] + +def no_pop_tops(): # 0 + x = 1 # 1 + for a in range(2): # 2 + if a: # 3 + x = 1 # 4 + else: # 5 + x = 1 # 6 + +no_pop_tops.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (2, 'line'), + (2, 'line'), + (3, 'line'), + (6, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (2, 'line'), + (2, 'return')] + +def no_pop_blocks(): + while 0: + bla + x = 1 + +no_pop_blocks.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (1, 'line'), + (3, 'line'), + (3, 'return')] + +def called(): # line -3 + x = 1 + +def call(): # line 0 + called() + +call.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (-3, 'call'), + (-3, 'line'), + (-2, 'line'), + (-2, 'return'), + (1, 'return')] + +def raises(): + raise Exception + +def test_raise(): + try: + raises() + except Exception, exc: + x = 1 + +test_raise.events = [(0, 'call'), + (0, 'line'), + (1, 'line'), + (2, 'line'), + (-3, 'call'), + (-3, 'line'), + (-2, 'line'), + (-2, 'exception'), + (2, 'exception'), + (3, 'line'), + (4, 'line'), + (4, 'return')] + +def _settrace_and_return(tracefunc): + sys.settrace(tracefunc) + sys._getframe().f_back.f_trace = tracefunc +def settrace_and_return(tracefunc): + _settrace_and_return(tracefunc) + +settrace_and_return.events = [(1, 'return')] + +def _settrace_and_raise(tracefunc): + sys.settrace(tracefunc) + sys._getframe().f_back.f_trace = tracefunc + raise RuntimeError +def settrace_and_raise(tracefunc): + try: + _settrace_and_raise(tracefunc) + except RuntimeError, exc: + pass + +settrace_and_raise.events = [(2, 'exception'), + (3, 'line'), + (4, 'line'), + (4, 'return')] + +class Tracer: + def __init__(self): + self.events = [] + def trace(self, frame, event, arg): + self.events.append((frame.f_lineno, event)) + return self.trace + +class TraceTestCase(unittest.TestCase): + def compare_events(self, line_offset, events, expected_events): + events = [(l - line_offset, e) for (l, e) in events] + if events != expected_events: + self.fail( + "events did not match expectation:\n" + + "\n".join(difflib.ndiff(map(str, expected_events), + map(str, events)))) + + + def run_test(self, func): + tracer = Tracer() + sys.settrace(tracer.trace) + func() + sys.settrace(None) + self.compare_events(func.func_code.co_firstlineno, + tracer.events, func.events) + + def run_test2(self, func): + tracer = Tracer() + func(tracer.trace) + sys.settrace(None) + self.compare_events(func.func_code.co_firstlineno, + tracer.events, func.events) + + def test_1_basic(self): + self.run_test(basic) + def test_2_arigo(self): + self.run_test(arigo_example) + def test_3_one_instr(self): + self.run_test(one_instr_line) + def test_4_no_pop_blocks(self): + self.run_test(no_pop_blocks) + def test_5_no_pop_tops(self): + self.run_test(no_pop_tops) + def test_6_call(self): + self.run_test(call) + def test_7_raise(self): + self.run_test(test_raise) + + def test_8_settrace_and_return(self): + self.run_test2(settrace_and_return) + def test_9_settrace_and_raise(self): + self.run_test2(settrace_and_raise) + +class RaisingTraceFuncTestCase(unittest.TestCase): + def test_it(self): + def tr(frame, event, arg): + raise ValueError # just something that isn't RuntimeError + def f(): + return 1 + try: + for i in xrange(sys.getrecursionlimit() + 1): + sys.settrace(tr) + try: + f() + except ValueError: + pass + else: + self.fail("exception not thrown!") + except RuntimeError: + self.fail("recursion counter not reset") + + +def test_main(): + test_support.run_unittest(TraceTestCase) + test_support.run_unittest(RaisingTraceFuncTestCase) + +if __name__ == "__main__": + test_main()