From 13c475385bc7ca46050d5162dfe277d0b9a8094f Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Sun, 25 Jul 2010 15:02:55 +0000 Subject: Issue #9315: Renamed test_trace to test_sys_settrace and test_profilehooks to test_sys_setprofile so that test_trace can be used for testing the trace module and for naming consistency. --- Lib/test/test_profilehooks.py | 385 -------------------- Lib/test/test_sys_setprofile.py | 385 ++++++++++++++++++++ Lib/test/test_sys_settrace.py | 790 ++++++++++++++++++++++++++++++++++++++++ Lib/test/test_trace.py | 790 ---------------------------------------- 4 files changed, 1175 insertions(+), 1175 deletions(-) delete mode 100644 Lib/test/test_profilehooks.py create mode 100644 Lib/test/test_sys_setprofile.py create mode 100644 Lib/test/test_sys_settrace.py delete mode 100644 Lib/test/test_trace.py diff --git a/Lib/test/test_profilehooks.py b/Lib/test/test_profilehooks.py deleted file mode 100644 index 54267a0..0000000 --- a/Lib/test/test_profilehooks.py +++ /dev/null @@ -1,385 +0,0 @@ -import pprint -import sys -import unittest - -from test import support - -class TestGetProfile(unittest.TestCase): - def setUp(self): - sys.setprofile(None) - - def tearDown(self): - sys.setprofile(None) - - def test_empty(self): - assert sys.getprofile() is None - - def test_setget(self): - def fn(*args): - pass - - sys.setprofile(fn) - assert sys.getprofile() == fn - -class HookWatcher: - def __init__(self): - self.frames = [] - self.events = [] - - def callback(self, frame, event, arg): - if (event == "call" - or event == "return" - or event == "exception"): - self.add_event(event, frame) - - def add_event(self, event, frame=None): - """Add an event to the log.""" - if frame is None: - frame = sys._getframe(1) - - try: - frameno = self.frames.index(frame) - except ValueError: - frameno = len(self.frames) - self.frames.append(frame) - - self.events.append((frameno, event, ident(frame))) - - def get_events(self): - """Remove calls to add_event().""" - disallowed = [ident(self.add_event.__func__), ident(ident)] - self.frames = None - - return [item for item in self.events if item[2] not in disallowed] - - -class ProfileSimulator(HookWatcher): - def __init__(self, testcase): - self.testcase = testcase - self.stack = [] - HookWatcher.__init__(self) - - def callback(self, frame, event, arg): - # Callback registered with sys.setprofile()/sys.settrace() - self.dispatch[event](self, frame) - - def trace_call(self, frame): - self.add_event('call', frame) - self.stack.append(frame) - - def trace_return(self, frame): - self.add_event('return', frame) - self.stack.pop() - - def trace_exception(self, frame): - self.testcase.fail( - "the profiler should never receive exception events") - - def trace_pass(self, frame): - pass - - dispatch = { - 'call': trace_call, - 'exception': trace_exception, - 'return': trace_return, - 'c_call': trace_pass, - 'c_return': trace_pass, - 'c_exception': trace_pass, - } - - -class TestCaseBase(unittest.TestCase): - def check_events(self, callable, expected): - events = capture_events(callable, self.new_watcher()) - if events != expected: - self.fail("Expected events:\n%s\nReceived events:\n%s" - % (pprint.pformat(expected), pprint.pformat(events))) - - -class ProfileHookTestCase(TestCaseBase): - def new_watcher(self): - return HookWatcher() - - def test_simple(self): - def f(p): - pass - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_exception(self): - def f(p): - 1/0 - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_caught_exception(self): - def f(p): - try: 1/0 - except: pass - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_caught_nested_exception(self): - def f(p): - try: 1/0 - except: pass - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_nested_exception(self): - def f(p): - 1/0 - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - # This isn't what I expected: - # (0, 'exception', protect_ident), - # I expected this again: - (1, 'return', f_ident), - ]) - - def test_exception_in_except_clause(self): - def f(p): - 1/0 - def g(p): - try: - f(p) - except: - try: f(p) - except: pass - f_ident = ident(f) - g_ident = ident(g) - self.check_events(g, [(1, 'call', g_ident), - (2, 'call', f_ident), - (2, 'return', f_ident), - (3, 'call', f_ident), - (3, 'return', f_ident), - (1, 'return', g_ident), - ]) - - def test_exception_propogation(self): - def f(p): - 1/0 - def g(p): - try: f(p) - finally: p.add_event("falling through") - f_ident = ident(f) - g_ident = ident(g) - self.check_events(g, [(1, 'call', g_ident), - (2, 'call', f_ident), - (2, 'return', f_ident), - (1, 'falling through', g_ident), - (1, 'return', g_ident), - ]) - - def test_raise_twice(self): - def f(p): - try: 1/0 - except: 1/0 - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_raise_reraise(self): - def f(p): - try: 1/0 - except: raise - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_raise(self): - def f(p): - raise Exception() - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_distant_exception(self): - def f(): - 1/0 - def g(): - f() - def h(): - g() - def i(): - h() - def j(p): - i() - f_ident = ident(f) - g_ident = ident(g) - h_ident = ident(h) - i_ident = ident(i) - j_ident = ident(j) - self.check_events(j, [(1, 'call', j_ident), - (2, 'call', i_ident), - (3, 'call', h_ident), - (4, 'call', g_ident), - (5, 'call', f_ident), - (5, 'return', f_ident), - (4, 'return', g_ident), - (3, 'return', h_ident), - (2, 'return', i_ident), - (1, 'return', j_ident), - ]) - - def test_generator(self): - def f(): - for i in range(2): - yield i - def g(p): - for i in f(): - pass - f_ident = ident(f) - g_ident = ident(g) - self.check_events(g, [(1, 'call', g_ident), - # call the iterator twice to generate values - (2, 'call', f_ident), - (2, 'return', f_ident), - (2, 'call', f_ident), - (2, 'return', f_ident), - # once more; returns end-of-iteration with - # actually raising an exception - (2, 'call', f_ident), - (2, 'return', f_ident), - (1, 'return', g_ident), - ]) - - def test_stop_iteration(self): - def f(): - for i in range(2): - yield i - raise StopIteration - def g(p): - for i in f(): - pass - f_ident = ident(f) - g_ident = ident(g) - self.check_events(g, [(1, 'call', g_ident), - # call the iterator twice to generate values - (2, 'call', f_ident), - (2, 'return', f_ident), - (2, 'call', f_ident), - (2, 'return', f_ident), - # once more to hit the raise: - (2, 'call', f_ident), - (2, 'return', f_ident), - (1, 'return', g_ident), - ]) - - -class ProfileSimulatorTestCase(TestCaseBase): - def new_watcher(self): - return ProfileSimulator(self) - - def test_simple(self): - def f(p): - pass - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_basic_exception(self): - def f(p): - 1/0 - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_caught_exception(self): - def f(p): - try: 1/0 - except: pass - f_ident = ident(f) - self.check_events(f, [(1, 'call', f_ident), - (1, 'return', f_ident), - ]) - - def test_distant_exception(self): - def f(): - 1/0 - def g(): - f() - def h(): - g() - def i(): - h() - def j(p): - i() - f_ident = ident(f) - g_ident = ident(g) - h_ident = ident(h) - i_ident = ident(i) - j_ident = ident(j) - self.check_events(j, [(1, 'call', j_ident), - (2, 'call', i_ident), - (3, 'call', h_ident), - (4, 'call', g_ident), - (5, 'call', f_ident), - (5, 'return', f_ident), - (4, 'return', g_ident), - (3, 'return', h_ident), - (2, 'return', i_ident), - (1, 'return', j_ident), - ]) - - -def ident(function): - if hasattr(function, "f_code"): - code = function.f_code - else: - code = function.__code__ - return code.co_firstlineno, code.co_name - - -def protect(f, p): - try: f(p) - except: pass - -protect_ident = ident(protect) - - -def capture_events(callable, p=None): - try: - sys.setprofile() - except TypeError: - pass - else: - raise support.TestFailed( - 'sys.setprofile() did not raise TypeError') - - if p is None: - p = HookWatcher() - sys.setprofile(p.callback) - protect(callable, p) - sys.setprofile(None) - return p.get_events()[1:-1] - - -def show_events(callable): - import pprint - pprint.pprint(capture_events(callable)) - - -def test_main(): - support.run_unittest( - TestGetProfile, - ProfileHookTestCase, - ProfileSimulatorTestCase - ) - - -if __name__ == "__main__": - test_main() diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py new file mode 100644 index 0000000..54267a0 --- /dev/null +++ b/Lib/test/test_sys_setprofile.py @@ -0,0 +1,385 @@ +import pprint +import sys +import unittest + +from test import support + +class TestGetProfile(unittest.TestCase): + def setUp(self): + sys.setprofile(None) + + def tearDown(self): + sys.setprofile(None) + + def test_empty(self): + assert sys.getprofile() is None + + def test_setget(self): + def fn(*args): + pass + + sys.setprofile(fn) + assert sys.getprofile() == fn + +class HookWatcher: + def __init__(self): + self.frames = [] + self.events = [] + + def callback(self, frame, event, arg): + if (event == "call" + or event == "return" + or event == "exception"): + self.add_event(event, frame) + + def add_event(self, event, frame=None): + """Add an event to the log.""" + if frame is None: + frame = sys._getframe(1) + + try: + frameno = self.frames.index(frame) + except ValueError: + frameno = len(self.frames) + self.frames.append(frame) + + self.events.append((frameno, event, ident(frame))) + + def get_events(self): + """Remove calls to add_event().""" + disallowed = [ident(self.add_event.__func__), ident(ident)] + self.frames = None + + return [item for item in self.events if item[2] not in disallowed] + + +class ProfileSimulator(HookWatcher): + def __init__(self, testcase): + self.testcase = testcase + self.stack = [] + HookWatcher.__init__(self) + + def callback(self, frame, event, arg): + # Callback registered with sys.setprofile()/sys.settrace() + self.dispatch[event](self, frame) + + def trace_call(self, frame): + self.add_event('call', frame) + self.stack.append(frame) + + def trace_return(self, frame): + self.add_event('return', frame) + self.stack.pop() + + def trace_exception(self, frame): + self.testcase.fail( + "the profiler should never receive exception events") + + def trace_pass(self, frame): + pass + + dispatch = { + 'call': trace_call, + 'exception': trace_exception, + 'return': trace_return, + 'c_call': trace_pass, + 'c_return': trace_pass, + 'c_exception': trace_pass, + } + + +class TestCaseBase(unittest.TestCase): + def check_events(self, callable, expected): + events = capture_events(callable, self.new_watcher()) + if events != expected: + self.fail("Expected events:\n%s\nReceived events:\n%s" + % (pprint.pformat(expected), pprint.pformat(events))) + + +class ProfileHookTestCase(TestCaseBase): + def new_watcher(self): + return HookWatcher() + + def test_simple(self): + def f(p): + pass + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_exception(self): + def f(p): + 1/0 + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_caught_exception(self): + def f(p): + try: 1/0 + except: pass + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_caught_nested_exception(self): + def f(p): + try: 1/0 + except: pass + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_nested_exception(self): + def f(p): + 1/0 + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + # This isn't what I expected: + # (0, 'exception', protect_ident), + # I expected this again: + (1, 'return', f_ident), + ]) + + def test_exception_in_except_clause(self): + def f(p): + 1/0 + def g(p): + try: + f(p) + except: + try: f(p) + except: pass + f_ident = ident(f) + g_ident = ident(g) + self.check_events(g, [(1, 'call', g_ident), + (2, 'call', f_ident), + (2, 'return', f_ident), + (3, 'call', f_ident), + (3, 'return', f_ident), + (1, 'return', g_ident), + ]) + + def test_exception_propogation(self): + def f(p): + 1/0 + def g(p): + try: f(p) + finally: p.add_event("falling through") + f_ident = ident(f) + g_ident = ident(g) + self.check_events(g, [(1, 'call', g_ident), + (2, 'call', f_ident), + (2, 'return', f_ident), + (1, 'falling through', g_ident), + (1, 'return', g_ident), + ]) + + def test_raise_twice(self): + def f(p): + try: 1/0 + except: 1/0 + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_raise_reraise(self): + def f(p): + try: 1/0 + except: raise + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_raise(self): + def f(p): + raise Exception() + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_distant_exception(self): + def f(): + 1/0 + def g(): + f() + def h(): + g() + def i(): + h() + def j(p): + i() + f_ident = ident(f) + g_ident = ident(g) + h_ident = ident(h) + i_ident = ident(i) + j_ident = ident(j) + self.check_events(j, [(1, 'call', j_ident), + (2, 'call', i_ident), + (3, 'call', h_ident), + (4, 'call', g_ident), + (5, 'call', f_ident), + (5, 'return', f_ident), + (4, 'return', g_ident), + (3, 'return', h_ident), + (2, 'return', i_ident), + (1, 'return', j_ident), + ]) + + def test_generator(self): + def f(): + for i in range(2): + yield i + def g(p): + for i in f(): + pass + f_ident = ident(f) + g_ident = ident(g) + self.check_events(g, [(1, 'call', g_ident), + # call the iterator twice to generate values + (2, 'call', f_ident), + (2, 'return', f_ident), + (2, 'call', f_ident), + (2, 'return', f_ident), + # once more; returns end-of-iteration with + # actually raising an exception + (2, 'call', f_ident), + (2, 'return', f_ident), + (1, 'return', g_ident), + ]) + + def test_stop_iteration(self): + def f(): + for i in range(2): + yield i + raise StopIteration + def g(p): + for i in f(): + pass + f_ident = ident(f) + g_ident = ident(g) + self.check_events(g, [(1, 'call', g_ident), + # call the iterator twice to generate values + (2, 'call', f_ident), + (2, 'return', f_ident), + (2, 'call', f_ident), + (2, 'return', f_ident), + # once more to hit the raise: + (2, 'call', f_ident), + (2, 'return', f_ident), + (1, 'return', g_ident), + ]) + + +class ProfileSimulatorTestCase(TestCaseBase): + def new_watcher(self): + return ProfileSimulator(self) + + def test_simple(self): + def f(p): + pass + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_basic_exception(self): + def f(p): + 1/0 + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_caught_exception(self): + def f(p): + try: 1/0 + except: pass + f_ident = ident(f) + self.check_events(f, [(1, 'call', f_ident), + (1, 'return', f_ident), + ]) + + def test_distant_exception(self): + def f(): + 1/0 + def g(): + f() + def h(): + g() + def i(): + h() + def j(p): + i() + f_ident = ident(f) + g_ident = ident(g) + h_ident = ident(h) + i_ident = ident(i) + j_ident = ident(j) + self.check_events(j, [(1, 'call', j_ident), + (2, 'call', i_ident), + (3, 'call', h_ident), + (4, 'call', g_ident), + (5, 'call', f_ident), + (5, 'return', f_ident), + (4, 'return', g_ident), + (3, 'return', h_ident), + (2, 'return', i_ident), + (1, 'return', j_ident), + ]) + + +def ident(function): + if hasattr(function, "f_code"): + code = function.f_code + else: + code = function.__code__ + return code.co_firstlineno, code.co_name + + +def protect(f, p): + try: f(p) + except: pass + +protect_ident = ident(protect) + + +def capture_events(callable, p=None): + try: + sys.setprofile() + except TypeError: + pass + else: + raise support.TestFailed( + 'sys.setprofile() did not raise TypeError') + + if p is None: + p = HookWatcher() + sys.setprofile(p.callback) + protect(callable, p) + sys.setprofile(None) + return p.get_events()[1:-1] + + +def show_events(callable): + import pprint + pprint.pprint(capture_events(callable)) + + +def test_main(): + support.run_unittest( + TestGetProfile, + ProfileHookTestCase, + ProfileSimulatorTestCase + ) + + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py new file mode 100644 index 0000000..43134e9 --- /dev/null +++ b/Lib/test/test_sys_settrace.py @@ -0,0 +1,790 @@ +# Testing the line trace facility. + +from test import support +import unittest +import sys +import difflib +import gc + +# A very basic example. If this fails, we're in deep trouble. +def basic(): + return 1 + +basic.events = [(0, 'call'), + (1, 'line'), + (1, 'return')] + +# Many of the tests below are tricky because they involve pass statements. +# If there is implicit control flow around a pass statement (in an except +# clause or else caluse) under what conditions do you set a line number +# following that clause? + + +# The entire "while 0:" statement is optimized away. No code +# exists for it, so the line numbers skip directly from "del x" +# to "x = 1". +def arigo_example(): + x = 1 + del x + while 0: + pass + x = 1 + +arigo_example.events = [(0, 'call'), + (1, 'line'), + (2, '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'), + (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'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (6, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (2, 'line'), + (2, 'return')] + +def no_pop_blocks(): + y = 1 + while not y: + bla + x = 1 + +no_pop_blocks.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (4, 'line'), + (4, 'return')] + +def called(): # line -3 + x = 1 + +def call(): # line 0 + called() + +call.events = [(0, 'call'), + (1, 'line'), + (-3, 'call'), + (-2, 'line'), + (-2, 'return'), + (1, 'return')] + +def raises(): + raise Exception + +def test_raise(): + try: + raises() + except Exception as exc: + x = 1 + +test_raise.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (-3, 'call'), + (-2, 'line'), + (-2, 'exception'), + (-2, 'return'), + (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 as exc: + pass + +settrace_and_raise.events = [(2, 'exception'), + (3, 'line'), + (4, 'line'), + (4, 'return')] + +# implicit return example +# This test is interesting because of the else: pass +# part of the code. The code generate for the true +# part of the if contains a jump past the else branch. +# The compiler then generates an implicit "return None" +# Internally, the compiler visits the pass statement +# and stores its line number for use on the next instruction. +# The next instruction is the implicit return None. +def ireturn_example(): + a = 5 + b = 5 + if a == b: + b = a+1 + else: + pass + +ireturn_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (6, 'line'), + (6, 'return')] + +# Tight loop with while(1) example (SF #765624) +def tightloop_example(): + items = range(0, 3) + try: + i = 0 + while 1: + b = items[i]; i+=1 + except IndexError: + pass + +tightloop_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'exception'), + (6, 'line'), + (7, 'line'), + (7, 'return')] + +def tighterloop_example(): + items = range(1, 4) + try: + i = 0 + while 1: i = items[i] + except IndexError: + pass + +tighterloop_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'exception'), + (5, 'line'), + (6, 'line'), + (6, 'return')] + +def generator_function(): + try: + yield True + "continued" + finally: + "finally" +def generator_example(): + # any() will leave the generator before its end + x = any(generator_function()) + + # the following lines were not traced + for x in range(10): + y = x + +generator_example.events = ([(0, 'call'), + (2, 'line'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'return'), + (-4, 'call'), + (-4, 'exception'), + (-1, 'line'), + (-1, 'return')] + + [(5, 'line'), (6, 'line')] * 10 + + [(5, 'line'), (5, 'return')]) + + +class Tracer: + def __init__(self): + self.events = [] + def trace(self, frame, event, arg): + self.events.append((frame.f_lineno, event)) + return self.trace + def traceWithGenexp(self, frame, event, arg): + (o for o in [1]) + self.events.append((frame.f_lineno, event)) + return self.trace + +class TraceTestCase(unittest.TestCase): + + # Disable gc collection when tracing, otherwise the + # deallocators may be traced as well. + def setUp(self): + self.using_gc = gc.isenabled() + gc.disable() + + def tearDown(self): + if self.using_gc: + gc.enable() + + 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([str(x) for x in expected_events], + [str(x) for x in events]))) + + def run_and_compare(self, func, events): + tracer = Tracer() + sys.settrace(tracer.trace) + func() + sys.settrace(None) + self.compare_events(func.__code__.co_firstlineno, + tracer.events, events) + + def run_test(self, func): + self.run_and_compare(func, func.events) + + def run_test2(self, func): + tracer = Tracer() + func(tracer.trace) + sys.settrace(None) + self.compare_events(func.__code__.co_firstlineno, + tracer.events, func.events) + + def set_and_retrieve_none(self): + sys.settrace(None) + assert sys.gettrace() is None + + def set_and_retrieve_func(self): + def fn(*args): + pass + + sys.settrace(fn) + try: + assert sys.gettrace() is fn + finally: + sys.settrace(None) + + def test_01_basic(self): + self.run_test(basic) + def test_02_arigo(self): + self.run_test(arigo_example) + def test_03_one_instr(self): + self.run_test(one_instr_line) + def test_04_no_pop_blocks(self): + self.run_test(no_pop_blocks) + def test_05_no_pop_tops(self): + self.run_test(no_pop_tops) + def test_06_call(self): + self.run_test(call) + def test_07_raise(self): + self.run_test(test_raise) + + def test_08_settrace_and_return(self): + self.run_test2(settrace_and_return) + def test_09_settrace_and_raise(self): + self.run_test2(settrace_and_raise) + def test_10_ireturn(self): + self.run_test(ireturn_example) + def test_11_tightloop(self): + self.run_test(tightloop_example) + def test_12_tighterloop(self): + self.run_test(tighterloop_example) + + def test_13_genexp(self): + self.run_test(generator_example) + # issue1265: if the trace function contains a generator, + # and if the traced function contains another generator + # that is not completely exhausted, the trace stopped. + # Worse: the 'finally' clause was not invoked. + tracer = Tracer() + sys.settrace(tracer.traceWithGenexp) + generator_example() + sys.settrace(None) + self.compare_events(generator_example.__code__.co_firstlineno, + tracer.events, generator_example.events) + + def test_14_onliner_if(self): + def onliners(): + if True: False + else: True + return 0 + self.run_and_compare( + onliners, + [(0, 'call'), + (1, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_15_loops(self): + # issue1750076: "while" expression is skipped by debugger + def for_example(): + for x in range(2): + pass + self.run_and_compare( + for_example, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (1, 'return')]) + + def while_example(): + # While expression should be traced on every loop + x = 2 + while x > 0: + x -= 1 + self.run_and_compare( + while_example, + [(0, 'call'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_16_blank_lines(self): + namespace = {} + exec("def f():\n" + "\n" * 256 + " pass", namespace) + self.run_and_compare( + namespace["f"], + [(0, 'call'), + (257, 'line'), + (257, 'return')]) + + +class RaisingTraceFuncTestCase(unittest.TestCase): + def trace(self, frame, event, arg): + """A trace function that raises an exception in response to a + specific trace event.""" + if event == self.raiseOnEvent: + raise ValueError # just something that isn't RuntimeError + else: + return self.trace + + def f(self): + """The function to trace; raises an exception if that's the case + we're testing, so that the 'exception' trace event fires.""" + if self.raiseOnEvent == 'exception': + x = 0 + y = 1/x + else: + return 1 + + def run_test_for_event(self, event): + """Tests that an exception raised in response to the given event is + handled OK.""" + self.raiseOnEvent = event + try: + for i in range(sys.getrecursionlimit() + 1): + sys.settrace(self.trace) + try: + self.f() + except ValueError: + pass + else: + self.fail("exception not thrown!") + except RuntimeError: + self.fail("recursion counter not reset") + + # Test the handling of exceptions raised by each kind of trace event. + def test_call(self): + self.run_test_for_event('call') + def test_line(self): + self.run_test_for_event('line') + def test_return(self): + self.run_test_for_event('return') + def test_exception(self): + self.run_test_for_event('exception') + + def test_trash_stack(self): + def f(): + for i in range(5): + print(i) # line tracing will raise an exception at this line + + def g(frame, why, extra): + if (why == 'line' and + frame.f_lineno == f.__code__.co_firstlineno + 2): + raise RuntimeError("i am crashing") + return g + + sys.settrace(g) + try: + f() + except RuntimeError: + # the test is really that this doesn't segfault: + import gc + gc.collect() + else: + self.fail("exception not propagated") + + +# '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.__code__: + firstLine = frame.f_code.co_firstlineno + if event == 'line' and 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 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: + output.append(2) + 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] + +# 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") + + +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_20_large_function(self): + d = {} + exec("""def f(output): # line 0 + x = 0 # line 1 + y = 1 # line 2 + ''' # line 3 + %s # lines 4-1004 + ''' # line 1005 + x += 1 # line 1006 + output.append(x) # line 1007 + return""" % ('\n' * 1000,), d) + f = d['f'] + + f.jump = (2, 1007) + f.output = [0] + self.run_test(f) + + def test_jump_to_firstlineno(self): + # This tests that PDB can jump back to the first line in a + # file. See issue #1689458. It can only be triggered in a + # function call if the function is defined on a single line. + code = compile(""" +# Comments don't count. +output.append(2) # firstlineno is here. +output.append(3) +output.append(4) +""", "", "exec") + class fake_function: + __code__ = code + jump = (2, 0) + tracer = JumpTracer(fake_function) + sys.settrace(tracer.trace) + namespace = {"output": []} + exec(code, namespace) + sys.settrace(None) + 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() diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py deleted file mode 100644 index 43134e9..0000000 --- a/Lib/test/test_trace.py +++ /dev/null @@ -1,790 +0,0 @@ -# Testing the line trace facility. - -from test import support -import unittest -import sys -import difflib -import gc - -# A very basic example. If this fails, we're in deep trouble. -def basic(): - return 1 - -basic.events = [(0, 'call'), - (1, 'line'), - (1, 'return')] - -# Many of the tests below are tricky because they involve pass statements. -# If there is implicit control flow around a pass statement (in an except -# clause or else caluse) under what conditions do you set a line number -# following that clause? - - -# The entire "while 0:" statement is optimized away. No code -# exists for it, so the line numbers skip directly from "del x" -# to "x = 1". -def arigo_example(): - x = 1 - del x - while 0: - pass - x = 1 - -arigo_example.events = [(0, 'call'), - (1, 'line'), - (2, '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'), - (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'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (6, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (2, 'line'), - (2, 'return')] - -def no_pop_blocks(): - y = 1 - while not y: - bla - x = 1 - -no_pop_blocks.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (4, 'line'), - (4, 'return')] - -def called(): # line -3 - x = 1 - -def call(): # line 0 - called() - -call.events = [(0, 'call'), - (1, 'line'), - (-3, 'call'), - (-2, 'line'), - (-2, 'return'), - (1, 'return')] - -def raises(): - raise Exception - -def test_raise(): - try: - raises() - except Exception as exc: - x = 1 - -test_raise.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (-3, 'call'), - (-2, 'line'), - (-2, 'exception'), - (-2, 'return'), - (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 as exc: - pass - -settrace_and_raise.events = [(2, 'exception'), - (3, 'line'), - (4, 'line'), - (4, 'return')] - -# implicit return example -# This test is interesting because of the else: pass -# part of the code. The code generate for the true -# part of the if contains a jump past the else branch. -# The compiler then generates an implicit "return None" -# Internally, the compiler visits the pass statement -# and stores its line number for use on the next instruction. -# The next instruction is the implicit return None. -def ireturn_example(): - a = 5 - b = 5 - if a == b: - b = a+1 - else: - pass - -ireturn_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (6, 'line'), - (6, 'return')] - -# Tight loop with while(1) example (SF #765624) -def tightloop_example(): - items = range(0, 3) - try: - i = 0 - while 1: - b = items[i]; i+=1 - except IndexError: - pass - -tightloop_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'exception'), - (6, 'line'), - (7, 'line'), - (7, 'return')] - -def tighterloop_example(): - items = range(1, 4) - try: - i = 0 - while 1: i = items[i] - except IndexError: - pass - -tighterloop_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'exception'), - (5, 'line'), - (6, 'line'), - (6, 'return')] - -def generator_function(): - try: - yield True - "continued" - finally: - "finally" -def generator_example(): - # any() will leave the generator before its end - x = any(generator_function()) - - # the following lines were not traced - for x in range(10): - y = x - -generator_example.events = ([(0, 'call'), - (2, 'line'), - (-6, 'call'), - (-5, 'line'), - (-4, 'line'), - (-4, 'return'), - (-4, 'call'), - (-4, 'exception'), - (-1, 'line'), - (-1, 'return')] + - [(5, 'line'), (6, 'line')] * 10 + - [(5, 'line'), (5, 'return')]) - - -class Tracer: - def __init__(self): - self.events = [] - def trace(self, frame, event, arg): - self.events.append((frame.f_lineno, event)) - return self.trace - def traceWithGenexp(self, frame, event, arg): - (o for o in [1]) - self.events.append((frame.f_lineno, event)) - return self.trace - -class TraceTestCase(unittest.TestCase): - - # Disable gc collection when tracing, otherwise the - # deallocators may be traced as well. - def setUp(self): - self.using_gc = gc.isenabled() - gc.disable() - - def tearDown(self): - if self.using_gc: - gc.enable() - - 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([str(x) for x in expected_events], - [str(x) for x in events]))) - - def run_and_compare(self, func, events): - tracer = Tracer() - sys.settrace(tracer.trace) - func() - sys.settrace(None) - self.compare_events(func.__code__.co_firstlineno, - tracer.events, events) - - def run_test(self, func): - self.run_and_compare(func, func.events) - - def run_test2(self, func): - tracer = Tracer() - func(tracer.trace) - sys.settrace(None) - self.compare_events(func.__code__.co_firstlineno, - tracer.events, func.events) - - def set_and_retrieve_none(self): - sys.settrace(None) - assert sys.gettrace() is None - - def set_and_retrieve_func(self): - def fn(*args): - pass - - sys.settrace(fn) - try: - assert sys.gettrace() is fn - finally: - sys.settrace(None) - - def test_01_basic(self): - self.run_test(basic) - def test_02_arigo(self): - self.run_test(arigo_example) - def test_03_one_instr(self): - self.run_test(one_instr_line) - def test_04_no_pop_blocks(self): - self.run_test(no_pop_blocks) - def test_05_no_pop_tops(self): - self.run_test(no_pop_tops) - def test_06_call(self): - self.run_test(call) - def test_07_raise(self): - self.run_test(test_raise) - - def test_08_settrace_and_return(self): - self.run_test2(settrace_and_return) - def test_09_settrace_and_raise(self): - self.run_test2(settrace_and_raise) - def test_10_ireturn(self): - self.run_test(ireturn_example) - def test_11_tightloop(self): - self.run_test(tightloop_example) - def test_12_tighterloop(self): - self.run_test(tighterloop_example) - - def test_13_genexp(self): - self.run_test(generator_example) - # issue1265: if the trace function contains a generator, - # and if the traced function contains another generator - # that is not completely exhausted, the trace stopped. - # Worse: the 'finally' clause was not invoked. - tracer = Tracer() - sys.settrace(tracer.traceWithGenexp) - generator_example() - sys.settrace(None) - self.compare_events(generator_example.__code__.co_firstlineno, - tracer.events, generator_example.events) - - def test_14_onliner_if(self): - def onliners(): - if True: False - else: True - return 0 - self.run_and_compare( - onliners, - [(0, 'call'), - (1, 'line'), - (3, 'line'), - (3, 'return')]) - - def test_15_loops(self): - # issue1750076: "while" expression is skipped by debugger - def for_example(): - for x in range(2): - pass - self.run_and_compare( - for_example, - [(0, 'call'), - (1, 'line'), - (2, 'line'), - (1, 'line'), - (2, 'line'), - (1, 'line'), - (1, 'return')]) - - def while_example(): - # While expression should be traced on every loop - x = 2 - while x > 0: - x -= 1 - self.run_and_compare( - while_example, - [(0, 'call'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (3, 'line'), - (4, 'line'), - (3, 'line'), - (3, 'return')]) - - def test_16_blank_lines(self): - namespace = {} - exec("def f():\n" + "\n" * 256 + " pass", namespace) - self.run_and_compare( - namespace["f"], - [(0, 'call'), - (257, 'line'), - (257, 'return')]) - - -class RaisingTraceFuncTestCase(unittest.TestCase): - def trace(self, frame, event, arg): - """A trace function that raises an exception in response to a - specific trace event.""" - if event == self.raiseOnEvent: - raise ValueError # just something that isn't RuntimeError - else: - return self.trace - - def f(self): - """The function to trace; raises an exception if that's the case - we're testing, so that the 'exception' trace event fires.""" - if self.raiseOnEvent == 'exception': - x = 0 - y = 1/x - else: - return 1 - - def run_test_for_event(self, event): - """Tests that an exception raised in response to the given event is - handled OK.""" - self.raiseOnEvent = event - try: - for i in range(sys.getrecursionlimit() + 1): - sys.settrace(self.trace) - try: - self.f() - except ValueError: - pass - else: - self.fail("exception not thrown!") - except RuntimeError: - self.fail("recursion counter not reset") - - # Test the handling of exceptions raised by each kind of trace event. - def test_call(self): - self.run_test_for_event('call') - def test_line(self): - self.run_test_for_event('line') - def test_return(self): - self.run_test_for_event('return') - def test_exception(self): - self.run_test_for_event('exception') - - def test_trash_stack(self): - def f(): - for i in range(5): - print(i) # line tracing will raise an exception at this line - - def g(frame, why, extra): - if (why == 'line' and - frame.f_lineno == f.__code__.co_firstlineno + 2): - raise RuntimeError("i am crashing") - return g - - sys.settrace(g) - try: - f() - except RuntimeError: - # the test is really that this doesn't segfault: - import gc - gc.collect() - else: - self.fail("exception not propagated") - - -# '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.__code__: - firstLine = frame.f_code.co_firstlineno - if event == 'line' and 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 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: - output.append(2) - 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] - -# 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") - - -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_20_large_function(self): - d = {} - exec("""def f(output): # line 0 - x = 0 # line 1 - y = 1 # line 2 - ''' # line 3 - %s # lines 4-1004 - ''' # line 1005 - x += 1 # line 1006 - output.append(x) # line 1007 - return""" % ('\n' * 1000,), d) - f = d['f'] - - f.jump = (2, 1007) - f.output = [0] - self.run_test(f) - - def test_jump_to_firstlineno(self): - # This tests that PDB can jump back to the first line in a - # file. See issue #1689458. It can only be triggered in a - # function call if the function is defined on a single line. - code = compile(""" -# Comments don't count. -output.append(2) # firstlineno is here. -output.append(3) -output.append(4) -""", "", "exec") - class fake_function: - __code__ = code - jump = (2, 0) - tracer = JumpTracer(fake_function) - sys.settrace(tracer.trace) - namespace = {"output": []} - exec(code, namespace) - sys.settrace(None) - 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() -- cgit v0.12