summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/traceback.rst52
-rw-r--r--Lib/test/test_traceback.py127
-rw-r--r--Lib/traceback.py14
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS3
5 files changed, 170 insertions, 27 deletions
diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
index c6b5d0c..8d216d0 100644
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -22,15 +22,20 @@ The module defines the following functions:
.. function:: print_tb(traceback, limit=None, file=None)
- Print up to *limit* stack trace entries from *traceback*. If *limit* is omitted
- or ``None``, all entries are printed. If *file* is omitted or ``None``, the
- output goes to ``sys.stderr``; otherwise it should be an open file or file-like
- object to receive the output.
+ Print up to *limit* stack trace entries from *traceback* (starting from
+ the caller's frame) if *limit* is positive. Otherwise, print the last
+ ``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries
+ are printed. If *file* is omitted or ``None``, the output goes to
+ ``sys.stderr``; otherwise it should be an open file or file-like object
+ to receive the output.
+
+ .. versionchanged:: 3.5
+ Added negative *limit* support.
.. function:: print_exception(type, value, traceback, limit=None, file=None, chain=True)
- Print exception information and up to *limit* stack trace entries from
+ Print exception information and stack trace entries from
*traceback* to *file*. This differs from :func:`print_tb` in the following
ways:
@@ -41,6 +46,7 @@ The module defines the following functions:
prints the line where the syntax error occurred with a caret indicating the
approximate position of the error.
+ The optional *limit* argument has the same meaning as for :func:`print_tb`.
If *chain* is true (the default), then chained exceptions (the
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
printed as well, like the interpreter itself does when printing an unhandled
@@ -49,33 +55,41 @@ The module defines the following functions:
.. function:: print_exc(limit=None, file=None, chain=True)
- This is a shorthand for ``print_exception(*sys.exc_info())``.
+ This is a shorthand for ``print_exception(*sys.exc_info(), limit, file,
+ chain)``.
.. function:: print_last(limit=None, file=None, chain=True)
This is a shorthand for ``print_exception(sys.last_type, sys.last_value,
- sys.last_traceback, limit, file)``. In general it will work only after
- an exception has reached an interactive prompt (see :data:`sys.last_type`).
+ sys.last_traceback, limit, file, chain)``. In general it will work only
+ after an exception has reached an interactive prompt (see
+ :data:`sys.last_type`).
.. function:: print_stack(f=None, limit=None, file=None)
- This function prints a stack trace from its invocation point. The optional *f*
- argument can be used to specify an alternate stack frame to start. The optional
- *limit* and *file* arguments have the same meaning as for
- :func:`print_exception`.
+ Print up to *limit* stack trace entries (starting from the invocation
+ point) if *limit* is positive. Otherwise, print the last ``abs(limit)``
+ entries. If *limit* is omitted or ``None``, all entries are printed.
+ The optional *f* argument can be used to specify an alternate stack frame
+ to start. The optional *file* argument has the same meaning as for
+ :func:`print_tb`.
+
+ .. versionchanged:: 3.5
+ Added negative *limit* support.
.. function:: extract_tb(traceback, limit=None)
- Return a list of up to *limit* "pre-processed" stack trace entries extracted
- from the traceback object *traceback*. It is useful for alternate formatting of
- stack traces. If *limit* is omitted or ``None``, all entries are extracted. A
- "pre-processed" stack trace entry is a 4-tuple (*filename*, *line number*,
- *function name*, *text*) representing the information that is usually printed
- for a stack trace. The *text* is a string with leading and trailing whitespace
- stripped; if the source is not available it is ``None``.
+ Return a list of "pre-processed" stack trace entries extracted from the
+ traceback object *traceback*. It is useful for alternate formatting of
+ stack traces. The optional *limit* argument has the same meaning as for
+ :func:`print_tb`. A "pre-processed" stack trace entry is a 4-tuple
+ (*filename*, *line number*, *function name*, *text*) representing the
+ information that is usually printed for a stack trace. The *text* is a
+ string with leading and trailing whitespace stripped; if the source is
+ not available it is ``None``.
.. function:: extract_stack(f=None, limit=None)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 2fa85f5..9364695 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -6,6 +6,7 @@ import linecache
import sys
import unittest
import re
+from test import support
from test.support import TESTFN, Error, captured_output, unlink, cpython_only
from test.script_helper import assert_python_ok
import textwrap
@@ -453,6 +454,126 @@ class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
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
@@ -592,16 +713,14 @@ class TestStack(unittest.TestCase):
traceback.walk_stack(None), capture_locals=True, limit=1)
s = some_inner(3, 4)
self.assertEqual(
- [' File "' + __file__ + '", line 592, '
- 'in some_inner\n'
+ [' File "%s", line %d, in some_inner\n'
' traceback.walk_stack(None), capture_locals=True, limit=1)\n'
' a = 1\n'
' b = 2\n'
' k = 3\n'
- ' v = 4\n'
+ ' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 4)
], s.format())
-
class TestTracebackException(unittest.TestCase):
def test_smoke(self):
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 8a554cf..61c9fb5 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -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',
@@ -315,12 +316,17 @@ class StackSummary(list):
"""
if limit is None:
limit = getattr(sys, 'tracebacklimit', None)
+ if limit is not None and limit < 0:
+ limit = 0
+ if limit is not None:
+ if limit >= 0:
+ frame_gen = itertools.islice(frame_gen, limit)
+ else:
+ frame_gen = collections.deque(frame_gen, maxlen=-limit)
result = klass()
fnames = set()
- for pos, (f, lineno) in enumerate(frame_gen):
- if limit is not None and pos >= limit:
- break
+ for f, lineno in frame_gen:
co = f.f_code
filename = co.co_filename
name = co.co_name
diff --git a/Misc/ACKS b/Misc/ACKS
index ca0fcab..e34d711 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -712,6 +712,7 @@ Anton Kasyanov
Lou Kates
Makoto Kato
Hiroaki Kawai
+Dmitry Kazakov
Brian Kearns
Sebastien Keim
Ryan Kelly
diff --git a/Misc/NEWS b/Misc/NEWS
index 6a3c30b..f2a8594 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -25,6 +25,9 @@ Core and Builtins
Library
-------
+- Issue #22619: Added negative limit support in the traceback module.
+ Based on patch by Dmitry Kazakov.
+
- Issue #24094: Fix possible crash in json.encode with poorly behaved dict
subclasses.