mirror of https://github.com/python/cpython
Issue #22619: Added negative limit support in the traceback module.
Based on patch by Dmitry Kazakov.
This commit is contained in:
parent
9a578d9ee6
commit
24559e4834
|
@ -22,15 +22,20 @@ The module defines the following functions:
|
||||||
|
|
||||||
.. function:: print_tb(traceback, limit=None, file=None)
|
.. function:: print_tb(traceback, limit=None, file=None)
|
||||||
|
|
||||||
Print up to *limit* stack trace entries from *traceback*. If *limit* is omitted
|
Print up to *limit* stack trace entries from *traceback* (starting from
|
||||||
or ``None``, all entries are printed. If *file* is omitted or ``None``, the
|
the caller's frame) if *limit* is positive. Otherwise, print the last
|
||||||
output goes to ``sys.stderr``; otherwise it should be an open file or file-like
|
``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries
|
||||||
object to receive the output.
|
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)
|
.. 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
|
*traceback* to *file*. This differs from :func:`print_tb` in the following
|
||||||
ways:
|
ways:
|
||||||
|
|
||||||
|
@ -41,6 +46,7 @@ The module defines the following functions:
|
||||||
prints the line where the syntax error occurred with a caret indicating the
|
prints the line where the syntax error occurred with a caret indicating the
|
||||||
approximate position of the error.
|
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
|
If *chain* is true (the default), then chained exceptions (the
|
||||||
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
|
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
|
||||||
printed as well, like the interpreter itself does when printing an unhandled
|
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)
|
.. 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)
|
.. function:: print_last(limit=None, file=None, chain=True)
|
||||||
|
|
||||||
This is a shorthand for ``print_exception(sys.last_type, sys.last_value,
|
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
|
sys.last_traceback, limit, file, chain)``. In general it will work only
|
||||||
an exception has reached an interactive prompt (see :data:`sys.last_type`).
|
after an exception has reached an interactive prompt (see
|
||||||
|
:data:`sys.last_type`).
|
||||||
|
|
||||||
|
|
||||||
.. function:: print_stack(f=None, limit=None, file=None)
|
.. function:: print_stack(f=None, limit=None, file=None)
|
||||||
|
|
||||||
This function prints a stack trace from its invocation point. The optional *f*
|
Print up to *limit* stack trace entries (starting from the invocation
|
||||||
argument can be used to specify an alternate stack frame to start. The optional
|
point) if *limit* is positive. Otherwise, print the last ``abs(limit)``
|
||||||
*limit* and *file* arguments have the same meaning as for
|
entries. If *limit* is omitted or ``None``, all entries are printed.
|
||||||
:func:`print_exception`.
|
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)
|
.. function:: extract_tb(traceback, limit=None)
|
||||||
|
|
||||||
Return a list of up to *limit* "pre-processed" stack trace entries extracted
|
Return a list of "pre-processed" stack trace entries extracted from the
|
||||||
from the traceback object *traceback*. It is useful for alternate formatting of
|
traceback object *traceback*. It is useful for alternate formatting of
|
||||||
stack traces. If *limit* is omitted or ``None``, all entries are extracted. A
|
stack traces. The optional *limit* argument has the same meaning as for
|
||||||
"pre-processed" stack trace entry is a 4-tuple (*filename*, *line number*,
|
:func:`print_tb`. A "pre-processed" stack trace entry is a 4-tuple
|
||||||
*function name*, *text*) representing the information that is usually printed
|
(*filename*, *line number*, *function name*, *text*) representing the
|
||||||
for a stack trace. The *text* is a string with leading and trailing whitespace
|
information that is usually printed for a stack trace. The *text* is a
|
||||||
stripped; if the source is not available it is ``None``.
|
string with leading and trailing whitespace stripped; if the source is
|
||||||
|
not available it is ``None``.
|
||||||
|
|
||||||
|
|
||||||
.. function:: extract_stack(f=None, limit=None)
|
.. function:: extract_stack(f=None, limit=None)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import linecache
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
import re
|
import re
|
||||||
|
from test import support
|
||||||
from test.support import TESTFN, Error, captured_output, unlink, cpython_only
|
from test.support import TESTFN, Error, captured_output, unlink, cpython_only
|
||||||
from test.script_helper import assert_python_ok
|
from test.script_helper import assert_python_ok
|
||||||
import textwrap
|
import textwrap
|
||||||
|
@ -453,6 +454,126 @@ class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
|
||||||
return s.getvalue()
|
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):
|
class MiscTracebackCases(unittest.TestCase):
|
||||||
#
|
#
|
||||||
# Check non-printing functions in traceback module
|
# Check non-printing functions in traceback module
|
||||||
|
@ -592,16 +713,14 @@ class TestStack(unittest.TestCase):
|
||||||
traceback.walk_stack(None), capture_locals=True, limit=1)
|
traceback.walk_stack(None), capture_locals=True, limit=1)
|
||||||
s = some_inner(3, 4)
|
s = some_inner(3, 4)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[' File "' + __file__ + '", line 592, '
|
[' File "%s", line %d, in some_inner\n'
|
||||||
'in some_inner\n'
|
|
||||||
' traceback.walk_stack(None), capture_locals=True, limit=1)\n'
|
' traceback.walk_stack(None), capture_locals=True, limit=1)\n'
|
||||||
' a = 1\n'
|
' a = 1\n'
|
||||||
' b = 2\n'
|
' b = 2\n'
|
||||||
' k = 3\n'
|
' k = 3\n'
|
||||||
' v = 4\n'
|
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 4)
|
||||||
], s.format())
|
], s.format())
|
||||||
|
|
||||||
|
|
||||||
class TestTracebackException(unittest.TestCase):
|
class TestTracebackException(unittest.TestCase):
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
"""Extract, format and print information about Python stack traces."""
|
"""Extract, format and print information about Python stack traces."""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import itertools
|
||||||
import linecache
|
import linecache
|
||||||
import sys
|
import sys
|
||||||
import operator
|
|
||||||
|
|
||||||
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||||
'format_exception_only', 'format_list', 'format_stack',
|
'format_exception_only', 'format_list', 'format_stack',
|
||||||
|
@ -315,12 +316,17 @@ class StackSummary(list):
|
||||||
"""
|
"""
|
||||||
if limit is None:
|
if limit is None:
|
||||||
limit = getattr(sys, 'tracebacklimit', 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()
|
result = klass()
|
||||||
fnames = set()
|
fnames = set()
|
||||||
for pos, (f, lineno) in enumerate(frame_gen):
|
for f, lineno in frame_gen:
|
||||||
if limit is not None and pos >= limit:
|
|
||||||
break
|
|
||||||
co = f.f_code
|
co = f.f_code
|
||||||
filename = co.co_filename
|
filename = co.co_filename
|
||||||
name = co.co_name
|
name = co.co_name
|
||||||
|
|
|
@ -712,6 +712,7 @@ Anton Kasyanov
|
||||||
Lou Kates
|
Lou Kates
|
||||||
Makoto Kato
|
Makoto Kato
|
||||||
Hiroaki Kawai
|
Hiroaki Kawai
|
||||||
|
Dmitry Kazakov
|
||||||
Brian Kearns
|
Brian Kearns
|
||||||
Sebastien Keim
|
Sebastien Keim
|
||||||
Ryan Kelly
|
Ryan Kelly
|
||||||
|
|
|
@ -25,6 +25,9 @@ Core and Builtins
|
||||||
Library
|
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
|
- Issue #24094: Fix possible crash in json.encode with poorly behaved dict
|
||||||
subclasses.
|
subclasses.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue