diff -r 031fc0231f3d Lib/test/test_traceback.py --- a/Lib/test/test_traceback.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/test/test_traceback.py Fri Jan 16 20:27:11 2015 +0200 @@ -4,6 +4,7 @@ from io import StringIO import sys import unittest import re +from test import support from test.support import run_unittest, Error, captured_output from test.support import TESTFN, unlink, cpython_only from test.script_helper import assert_python_ok @@ -447,6 +448,126 @@ class CExcReportingTests(BaseExceptionRe return s.getvalue() +class LimitTests(unittest.TestCase): + + ''' Tests for limit argument. + It's enough to test extact_tb, extract_stack and format_exception ''' + + def last_raises1(self): + raise Exception('Last raised') + + def last_raises2(self): + self.last_raises1() + + def last_raises3(self): + self.last_raises2() + + def last_raises4(self): + self.last_raises3() + + def last_raises5(self): + self.last_raises4() + + def last_returns_frame1(self): + return sys._getframe() + + def last_returns_frame2(self): + return self.last_returns_frame1() + + def last_returns_frame3(self): + return self.last_returns_frame2() + + def last_returns_frame4(self): + return self.last_returns_frame3() + + def last_returns_frame5(self): + return self.last_returns_frame4() + + def test_extract_stack(self): + frame = self.last_returns_frame5() + def extract(**kwargs): + return traceback.extract_stack(frame, **kwargs) + def assertEqualExcept(actual, expected, ignore): + self.assertEqual(actual[:ignore], expected[:ignore]) + self.assertEqual(actual[ignore+1:], expected[ignore+1:]) + self.assertEqual(len(actual), len(expected)) + + with support.swap_attr(sys, 'tracebacklimit', 1000): + nolim = extract() + self.assertGreater(len(nolim), 5) + self.assertEqual(extract(limit=2), nolim[-2:]) + assertEqualExcept(extract(limit=100), nolim[-100:], -5-1) + self.assertEqual(extract(limit=-2), nolim[:2]) + assertEqualExcept(extract(limit=-100), nolim[:100], len(nolim)-5-1) + self.assertEqual(extract(limit=0), []) + del sys.tracebacklimit + assertEqualExcept(extract(), nolim, -5-1) + sys.tracebacklimit = 2 + self.assertEqual(extract(), nolim[-2:]) + self.assertEqual(extract(limit=3), nolim[-3:]) + self.assertEqual(extract(limit=-3), nolim[:3]) + sys.tracebacklimit = 0 + self.assertEqual(extract(), []) + sys.tracebacklimit = -1 + self.assertEqual(extract(), []) + + def test_extract_tb(self): + try: + self.last_raises5() + except Exception: + exc_type, exc_value, tb = sys.exc_info() + def extract(**kwargs): + return traceback.extract_tb(tb, **kwargs) + + with support.swap_attr(sys, 'tracebacklimit', 1000): + nolim = extract() + self.assertEqual(len(nolim), 5+1) + self.assertEqual(extract(limit=2), nolim[:2]) + self.assertEqual(extract(limit=10), nolim) + self.assertEqual(extract(limit=-2), nolim[-2:]) + self.assertEqual(extract(limit=-10), nolim) + self.assertEqual(extract(limit=0), []) + del sys.tracebacklimit + self.assertEqual(extract(), nolim) + sys.tracebacklimit = 2 + self.assertEqual(extract(), nolim[:2]) + self.assertEqual(extract(limit=3), nolim[:3]) + self.assertEqual(extract(limit=-3), nolim[-3:]) + sys.tracebacklimit = 0 + self.assertEqual(extract(), []) + sys.tracebacklimit = -1 + self.assertEqual(extract(), []) + + def test_format_exception(self): + try: + self.last_raises5() + except Exception: + exc_type, exc_value, tb = sys.exc_info() + # [1:-1] to exclude "Traceback (...)" header and + # exception type and value + def extract(**kwargs): + return traceback.format_exception(exc_type, exc_value, tb, **kwargs)[1:-1] + + with support.swap_attr(sys, 'tracebacklimit', 1000): + nolim = extract() + self.assertEqual(len(nolim), 5+1) + self.assertEqual(extract(limit=2), nolim[:2]) + self.assertEqual(extract(limit=10), nolim) + self.assertEqual(extract(limit=-2), nolim[-2:]) + self.assertEqual(extract(limit=-10), nolim) + self.assertEqual(extract(limit=0), []) + del sys.tracebacklimit + self.assertEqual(extract(), nolim) + sys.tracebacklimit = 2 + self.assertEqual(extract(), nolim[:2]) + self.assertEqual(extract(limit=3), nolim[:3]) + self.assertEqual(extract(limit=-3), nolim[-3:]) + sys.tracebacklimit = 0 + self.assertEqual(extract(), []) + sys.tracebacklimit = -1 + self.assertEqual(extract(), []) + + class MiscTracebackCases(unittest.TestCase): # # Check non-printing functions in traceback module diff -r 031fc0231f3d Lib/traceback.py --- a/Lib/traceback.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/traceback.py Fri Jan 16 20:27:11 2015 +0200 @@ -1,8 +1,9 @@ """Extract, format and print information about Python stack traces.""" +import collections +import itertools import linecache import sys -import operator __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -45,38 +46,31 @@ def format_list(extracted_list): # Printing and Extracting Tracebacks. # -# extractor takes curr and needs to return a tuple of: -# - Frame object -# - Line number -# - Next item (same type as curr) -# In practice, curr is either a traceback or a frame. -def _extract_tb_or_stack_iter(curr, limit, extractor): +def _extract_tb_or_stack_iter(it, limit): if limit is None: limit = getattr(sys, 'tracebacklimit', None) + if limit is not None: + it = itertools.islice(it, max(limit, 0)) + elif limit >= 0: + it = itertools.islice(it, limit) + else: # negative limit + it = collections.deque(it, maxlen=-limit) - n = 0 - while curr is not None and (limit is None or n < limit): - f, lineno, next_item = extractor(curr) + for f, lineno in it: co = f.f_code filename = co.co_filename name = co.co_name - linecache.checkcache(filename) line = linecache.getline(filename, lineno, f.f_globals) + yield (filename, lineno, name, line.strip() if line else None) - if line: - line = line.strip() - else: - line = None - - yield (filename, lineno, name, line) - curr = next_item - n += 1 +def _tb_frame_lineno_iter(tb): + while tb is not None: + yield tb.tb_frame, tb.tb_lineno + tb = tb.tb_next def _extract_tb_iter(tb, limit): - return _extract_tb_or_stack_iter( - tb, limit, - operator.attrgetter("tb_frame", "tb_lineno", "tb_next")) + return _extract_tb_or_stack_iter(_tb_frame_lineno_iter(tb), limit) def print_tb(tb, limit=None, file=None): """Print up to 'limit' stack trace entries from the traceback 'tb'. @@ -267,9 +261,13 @@ def print_last(limit=None, file=None, ch # Printing and Extracting Stacks. # +def _stack_frame_lineno_iter(f): + while f is not None: + yield f, f.f_lineno + f = f.f_back + def _extract_stack_iter(f, limit=None): - return _extract_tb_or_stack_iter( - f, limit, lambda f: (f, f.f_lineno, f.f_back)) + return _extract_tb_or_stack_iter(_stack_frame_lineno_iter(f), limit) def _get_stack(f): if f is None: