--- /dev/null
+"""Extract, format and print information about Python stack traces."""
+
+import sys
+import operator
+
+import linecache2 as linecache
+from six import u, PY2
+
+__all__ = ['extract_stack', 'extract_tb', 'format_exception',
+ 'format_exception_only', 'format_list', 'format_stack',
+ 'format_tb', 'print_exc', 'format_exc', 'print_exception',
+ 'print_last', 'print_stack', 'print_tb',
+ 'clear_frames']
+
+#
+# Formatting and printing lists of traceback lines.
+#
+
+def print_list(extracted_list, file=None):
+ """Print the list of tuples as returned by extract_tb() or
+ extract_stack() as a formatted stack trace to the given file."""
+ if file is None:
+ file = sys.stderr
+ for item in StackSummary.from_list(extracted_list).format():
+ file.write(item)
+
+def format_list(extracted_list):
+ """Format a list of traceback entry tuples for printing.
+
+ Given a list of tuples as returned by extract_tb() or
+ extract_stack(), return a list of strings ready for printing.
+ Each string in the resulting list corresponds to the item with the
+ same index in the argument list. Each string ends in a newline;
+ the strings may contain internal newlines as well, for those items
+ whose source text line is not None.
+ """
+ return StackSummary.from_list(extracted_list).format()
+
+#
+# Printing and Extracting Tracebacks.
+#
+
+def print_tb(tb, limit=None, file=None):
+ """Print up to 'limit' stack trace entries from the traceback 'tb'.
+
+ If 'limit' is omitted or None, all entries are printed. If 'file'
+ is omitted or None, the output goes to sys.stderr; otherwise
+ 'file' should be an open file or file-like object with a write()
+ method.
+ """
+ print_list(extract_tb(tb, limit=limit), file=file)
+
+def format_tb(tb, limit=None):
+ """A shorthand for 'format_list(extract_tb(tb, limit))'."""
+ return extract_tb(tb, limit=limit).format()
+
+def extract_tb(tb, limit=None):
+ """Return list of up to limit pre-processed entries from traceback.
+
+ This 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 quadruple (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 StackSummary.extract(walk_tb(tb), limit=limit)
+
+#
+# Exception formatting and output.
+#
+
+_cause_message = (
+ "\nThe above exception was the direct cause "
+ "of the following exception:\n\n")
+
+_context_message = (
+ "\nDuring handling of the above exception, "
+ "another exception occurred:\n\n")
+
+
+def print_exception(etype, value, tb, limit=None, file=None, chain=True):
+ """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
+
+ This differs from print_tb() in the following ways: (1) if
+ traceback is not None, it prints a header "Traceback (most recent
+ call last):"; (2) it prints the exception type and value after the
+ stack trace; (3) if type is SyntaxError and value has the
+ appropriate format, it prints the line where the syntax error
+ occurred with a caret on the next line indicating the approximate
+ position of the error.
+ """
+ # format_exception has ignored etype for some time, and code such as cgitb
+ # passes in bogus values as a result. For compatibility with such code we
+ # ignore it here (rather than in the new TracebackException API).
+ if file is None:
+ file = sys.stderr
+ for line in TracebackException(
+ type(value), value, tb, limit=limit).format(chain=chain):
+ file.write(line)
+
+
+def format_exception(etype, value, tb, limit=None, chain=True):
+ """Format a stack trace and the exception information.
+
+ The arguments have the same meaning as the corresponding arguments
+ to print_exception(). The return value is a list of strings, each
+ ending in a newline and some containing internal newlines. When
+ these lines are concatenated and printed, exactly the same text is
+ printed as does print_exception().
+ """
+ # format_exception has ignored etype for some time, and code such as cgitb
+ # passes in bogus values as a result. For compatibility with such code we
+ # ignore it here (rather than in the new TracebackException API).
+ return list(TracebackException(
+ type(value), value, tb, limit=limit).format(chain=chain))
+
+
+def format_exception_only(etype, value):
+ """Format the exception part of a traceback.
+
+ The arguments are the exception type and value such as given by
+ sys.last_type and sys.last_value. The return value is a list of
+ strings, each ending in a newline.
+
+ Normally, the list contains a single string; however, for
+ SyntaxError exceptions, it contains several lines that (when
+ printed) display detailed information about where the syntax
+ error occurred.
+
+ The message indicating which exception occurred is always the last
+ string in the list.
+
+ """
+ return list(TracebackException(etype, value, None).format_exception_only())
+
+
+# -- not offical API but folk probably use these two functions.
+
+def _format_final_exc_line(etype, value):
+ valuestr = _some_str(value)
+ if value == 'None' or value is None or not valuestr:
+ line = u("%s\n") % etype
+ else:
+ line = u("%s: %s\n") % (etype, valuestr)
+ return line
+
+def _some_str(value):
+ try:
+ if PY2:
+ # If there is a working __unicode__, great.
+ # Otherwise see if we can get a bytestring...
+ # Otherwise we fallback to unprintable.
+ try:
+ return unicode(value)
+ except:
+ return "b%s" % repr(str(value))
+ else:
+ # For Python3, bytestrings don't implicit decode, so its trivial.
+ return str(value)
+ except:
+ return '<unprintable %s object>' % type(value).__name__
+
+# --
+
+def _some_fs_str(value):
+ """_some_str, but for filesystem paths."""
+ if value is None:
+ return None
+ try:
+ if type(value) is bytes:
+ return value.decode(sys.getfilesystemencoding())
+ except:
+ pass
+ return _some_str(value)
+
+
+def print_exc(limit=None, file=None, chain=True):
+ """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
+ print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
+
+def format_exc(limit=None, chain=True):
+ """Like print_exc() but return a string."""
+ return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
+
+def 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)'."""
+ if not hasattr(sys, "last_type"):
+ raise ValueError("no last exception")
+ print_exception(sys.last_type, sys.last_value, sys.last_traceback,
+ limit, file, chain)
+
+#
+# Printing and Extracting Stacks.
+#
+
+def print_stack(f=None, limit=None, file=None):
+ """Print a stack trace from its invocation point.
+
+ The optional 'f' argument can be used to specify an alternate
+ stack frame at which to start. The optional 'limit' and 'file'
+ arguments have the same meaning as for print_exception().
+ """
+ print_list(extract_stack(f, limit=limit), file=file)
+
+
+def format_stack(f=None, limit=None):
+ """Shorthand for 'format_list(extract_stack(f, limit))'."""
+ return format_list(extract_stack(f, limit=limit))
+
+
+def extract_stack(f=None, limit=None):
+ """Extract the raw traceback from the current stack frame.
+
+ The return value has the same format as for extract_tb(). The
+ optional 'f' and 'limit' arguments have the same meaning as for
+ print_stack(). Each item in the list is a quadruple (filename,
+ line number, function name, text), and the entries are in order
+ from oldest to newest stack frame.
+ """
+ stack = StackSummary.extract(walk_stack(f), limit=limit)
+ stack.reverse()
+ return stack
+
+
+_identity = lambda:None
+def clear_frames(tb):
+ "Clear all references to local variables in the frames of a traceback."
+ while tb is not None:
+ try:
+ getattr(tb.tb_frame, 'clear', _identity)()
+ except RuntimeError:
+ # Ignore the exception raised if the frame is still executing.
+ pass
+ tb = tb.tb_next
+
+
+class FrameSummary:
+ """A single frame from a traceback.
+
+ - :attr:`filename` The filename for the frame.
+ - :attr:`lineno` The line within filename for the frame that was
+ active when the frame was captured.
+ - :attr:`name` The name of the function or method that was executing
+ when the frame was captured.
+ - :attr:`line` The text from the linecache module for the
+ of code that was running when the frame was captured.
+ - :attr:`locals` Either None if locals were not supplied, or a dict
+ mapping the name to the repr() of the variable.
+ """
+
+ __slots__ = ('filename', 'lineno', 'name', '_line', 'locals')
+
+ def __init__(self, filename, lineno, name, lookup_line=True,
+ locals=None, line=None):
+ """Construct a FrameSummary.
+
+ :param lookup_line: If True, `linecache` is consulted for the source
+ code line. Otherwise, the line will be looked up when first needed.
+ :param locals: If supplied the frame locals, which will be captured as
+ object representations.
+ :param line: If provided, use this instead of looking up the line in
+ the linecache.
+ """
+ self.filename = filename
+ self.lineno = lineno
+ self.name = name
+ self._line = line
+ if lookup_line:
+ self.line
+ self.locals = \
+ dict((k, repr(v)) for k, v in locals.items()) if locals else None
+
+ def __eq__(self, other):
+ return (self.filename == other.filename and
+ self.lineno == other.lineno and
+ self.name == other.name and
+ self.locals == other.locals)
+
+ def __getitem__(self, pos):
+ return (self.filename, self.lineno, self.name, self.line)[pos]
+
+ def __iter__(self):
+ return iter([self.filename, self.lineno, self.name, self.line])
+
+ def __repr__(self):
+ return "<FrameSummary file {filename}, line {lineno} in {name}>".format(
+ filename=self.filename, lineno=self.lineno, name=self.name)
+
+ @property
+ def line(self):
+ if self._line is None:
+ self._line = linecache.getline(self.filename, self.lineno).strip()
+ return self._line
+
+
+def walk_stack(f):
+ """Walk a stack yielding the frame and line number for each frame.
+
+ This will follow f.f_back from the given frame. If no frame is given, the
+ current stack is used. Usually used with StackSummary.extract.
+ """
+ if f is None:
+ f = sys._getframe().f_back.f_back
+ while f is not None:
+ yield f, f.f_lineno
+ f = f.f_back
+
+
+def walk_tb(tb):
+ """Walk a traceback yielding the frame and line number for each frame.
+
+ This will follow tb.tb_next (and thus is in the opposite order to
+ walk_stack). Usually used with StackSummary.extract.
+ """
+ while tb is not None:
+ yield tb.tb_frame, tb.tb_lineno
+ tb = tb.tb_next
+
+
+class StackSummary(list):
+ """A stack of frames."""
+
+ @classmethod
+ def extract(klass, frame_gen, limit=None, lookup_lines=True,
+ capture_locals=False):
+ """Create a StackSummary from a traceback or stack object.
+
+ :param frame_gen: A generator that yields (frame, lineno) tuples to
+ include in the stack.
+ :param limit: None to include all frames or the number of frames to
+ include.
+ :param lookup_lines: If True, lookup lines for each frame immediately,
+ otherwise lookup is deferred until the frame is rendered.
+ :param capture_locals: If True, the local variables from each frame will
+ be captured as object representations into the FrameSummary.
+ """
+ if limit is None:
+ limit = getattr(sys, 'tracebacklimit', None)
+
+ result = klass()
+ fnames = set()
+ for pos, (f, lineno) in enumerate(frame_gen):
+ if limit is not None and pos >= limit:
+ break
+ co = f.f_code
+ filename = co.co_filename
+ name = co.co_name
+
+ fnames.add(filename)
+ linecache.lazycache(filename, f.f_globals)
+ # Must defer line lookups until we have called checkcache.
+ if capture_locals:
+ f_locals = f.f_locals
+ else:
+ f_locals = None
+ result.append(FrameSummary(
+ filename, lineno, name, lookup_line=False, locals=f_locals))
+ for filename in fnames:
+ linecache.checkcache(filename)
+ # If immediate lookup was desired, trigger lookups now.
+ if lookup_lines:
+ for f in result:
+ f.line
+ return result
+
+ @classmethod
+ def from_list(klass, a_list):
+ """Create a StackSummary from a simple list of tuples.
+
+ This method supports the older Python API. Each tuple should be a
+ 4-tuple with (filename, lineno, name, line) elements.
+ """
+ if isinstance(a_list, StackSummary):
+ return StackSummary(a_list)
+ result = StackSummary()
+ for filename, lineno, name, line in a_list:
+ result.append(FrameSummary(filename, lineno, name, line=line))
+ return result
+
+ def format(self):
+ """Format the stack ready for printing.
+
+ Returns a list of strings ready for printing. Each string in the
+ resulting list corresponds to a single frame from the stack.
+ Each string ends in a newline; the strings may contain internal
+ newlines as well, for those items with source text lines.
+ """
+ result = []
+ for frame in self:
+ row = []
+ row.append(u(' File "{0}", line {1}, in {2}\n').format(
+ _some_fs_str(frame.filename), frame.lineno, frame.name))
+ if frame.line:
+ row.append(u(' {0}\n').format(frame.line.strip()))
+ if frame.locals:
+ for name, value in sorted(frame.locals.items()):
+ row.append(u(' {name} = {value}\n').format(name=name, value=value))
+ result.append(u('').join(row))
+ return result
+
+
+class TracebackException:
+ """An exception ready for rendering.
+
+ The traceback module captures enough attributes from the original exception
+ to this intermediary form to ensure that no references are held, while
+ still being able to fully print or format it.
+
+ Use `from_exception` to create TracebackException instances from exception
+ objects, or the constructor to create TracebackException instances from
+ individual components.
+
+ - :attr:`__cause__` A TracebackException of the original *__cause__*.
+ - :attr:`__context__` A TracebackException of the original *__context__*.
+ - :attr:`__suppress_context__` The *__suppress_context__* value from the
+ original exception.
+ - :attr:`stack` A `StackSummary` representing the traceback.
+ - :attr:`exc_type` The class of the original traceback.
+ - :attr:`filename` For syntax errors - the filename where the error
+ occured.
+ - :attr:`lineno` For syntax errors - the linenumber where the error
+ occured.
+ - :attr:`text` For syntax errors - the text where the error
+ occured.
+ - :attr:`offset` For syntax errors - the offset into the text where the
+ error occured.
+ - :attr:`msg` For syntax errors - the compiler error message.
+ """
+
+ def __init__(self, exc_type, exc_value, exc_traceback, limit=None,
+ lookup_lines=True, capture_locals=False, _seen=None):
+ # NB: we need to accept exc_traceback, exc_value, exc_traceback to
+ # permit backwards compat with the existing API, otherwise we
+ # need stub thunk objects just to glue it together.
+ # Handle loops in __cause__ or __context__.
+ if _seen is None:
+ _seen = set()
+ _seen.add(exc_value)
+ # Gracefully handle (the way Python 2.4 and earlier did) the case of
+ # being called with no type or value (None, None, None).
+ if (exc_value and getattr(exc_value, '__cause__', None) is not None
+ and exc_value.__cause__ not in _seen):
+ cause = TracebackException(
+ type(exc_value.__cause__),
+ exc_value.__cause__,
+ exc_value.__cause__.__traceback__,
+ limit=limit,
+ lookup_lines=False,
+ capture_locals=capture_locals,
+ _seen=_seen)
+ else:
+ cause = None
+ if (exc_value and getattr(exc_value, '__context__', None) is not None
+ and exc_value.__context__ not in _seen):
+ context = TracebackException(
+ type(exc_value.__context__),
+ exc_value.__context__,
+ exc_value.__context__.__traceback__,
+ limit=limit,
+ lookup_lines=False,
+ capture_locals=capture_locals,
+ _seen=_seen)
+ else:
+ context = None
+ self.__cause__ = cause
+ self.__context__ = context
+ self.__suppress_context__ = \
+ getattr(exc_value, '__suppress_context__', False) if exc_value else False
+ # TODO: locals.
+ self.stack = StackSummary.extract(
+ walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines,
+ capture_locals=capture_locals)
+ self.exc_type = exc_type
+ # Capture now to permit freeing resources: only complication is in the
+ # unofficial API _format_final_exc_line
+ self._str = _some_str(exc_value)
+ if exc_type and issubclass(exc_type, SyntaxError):
+ # Handle SyntaxError's specially
+ self.filename = exc_value.filename
+ self.lineno = str(exc_value.lineno)
+ self.text = exc_value.text
+ self.offset = exc_value.offset
+ self.msg = exc_value.msg
+ if lookup_lines:
+ self._load_lines()
+
+ @classmethod
+ def from_exception(self, exc, *args, **kwargs):
+ """Create a TracebackException from an exception.
+
+ Only useful in Python 3 specific code.
+ """
+ return TracebackException(
+ type(exc), exc, exc.__traceback__, *args, **kwargs)
+
+ def _load_lines(self):
+ """Private API. force all lines in the stack to be loaded."""
+ for frame in self.stack:
+ frame.line
+ if self.__context__:
+ self.__context__._load_lines()
+ if self.__cause__:
+ self.__cause__._load_lines()
+
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
+
+ def __str__(self):
+ return self._str
+
+ def format_exception_only(self):
+ """Format the exception part of the traceback.
+
+ The return value is a generator of strings, each ending in a newline.
+
+ Normally, the generator emits a single string; however, for
+ SyntaxError exceptions, it emites several lines that (when
+ printed) display detailed information about where the syntax
+ error occurred.
+
+ The message indicating which exception occurred is always the last
+ string in the output.
+ """
+ if self.exc_type is None:
+ yield _format_final_exc_line(None, self._str)
+ return
+
+ stype = getattr(self.exc_type, '__qualname__', self.exc_type.__name__)
+ smod = u(self.exc_type.__module__)
+ if smod not in ("__main__", "builtins", "exceptions"):
+ stype = smod + u('.') + stype
+
+ if not issubclass(self.exc_type, SyntaxError):
+ yield _format_final_exc_line(stype, self._str)
+ return
+
+ # It was a syntax error; show exactly where the problem was found.
+ filename = _some_fs_str(self.filename) or u("<string>")
+ lineno = str(self.lineno) or u('?')
+ yield u(' File "{0}", line {1}\n').format(filename, lineno)
+
+ badline = None
+ if self.text is not None:
+ if type(self.text) is bytes:
+ # Not decoded - get the line via linecache which will decode
+ # for us.
+ if self.lineno:
+ badline = linecache.getline(filename, int(lineno))
+ if not badline:
+ # But we can't for some reason, so fallback to attempting a
+ # u cast.
+ badline = u(self.text)
+ else:
+ badline = self.text
+ offset = self.offset
+ if badline is not None:
+ yield u(' {0}\n').format(badline.strip())
+ if offset is not None:
+ caretspace = badline.rstrip('\n')
+ offset = min(len(caretspace), offset) - 1
+ caretspace = caretspace[:offset].lstrip()
+ # non-space whitespace (likes tabs) must be kept for alignment
+ caretspace = ((c.isspace() and c or ' ') for c in caretspace)
+ yield u(' {0}^\n').format(''.join(caretspace))
+ msg = self.msg or u("<no detail available>")
+ yield u("{0}: {1}\n").format(stype, msg)
+
+ def format(self, chain=True):
+ """Format the exception.
+
+ If chain is not *True*, *__cause__* and *__context__* will not be formatted.
+
+ The return value is a generator of strings, each ending in a newline and
+ some containing internal newlines. `print_exception` is a wrapper around
+ this method which just prints the lines to a file.
+
+ The message indicating which exception occurred is always the last
+ string in the output.
+ """
+ if chain:
+ if self.__cause__ is not None:
+ for line in self.__cause__.format(chain=chain):
+ yield line
+ yield _cause_message
+ elif (self.__context__ is not None and
+ not self.__suppress_context__):
+ for line in self.__context__.format(chain=chain):
+ yield line
+ yield _context_message
+ yield u('Traceback (most recent call last):\n')
+ for line in self.stack.format():
+ yield line
+ for line in self.format_exception_only():
+ yield line
--- /dev/null
+"""Test cases for traceback module"""
+
+from collections import namedtuple
+import doctest
+import io
+from io import StringIO
+import platform
+import sys
+import re
+
+import contextlib2 as contextlib
+import fixtures
+import linecache2 as linecache
+import six
+from six import b, text_type, u
+try:
+ from six import raise_from
+except ImportError:
+# support raise_from on 3.x:
+# submitted to six: https://bitbucket.org/gutworth/six/issue/102/raise-foo-from-bar-is-a-syntax-error-on-27
+ if sys.version_info[:2] > (3, 1):
+ six.exec_("""def raise_from(value, from_value):
+ raise value from from_value
+ """)
+ else:
+ def raise_from(value, from_value):
+ raise value
+import unittest2 as unittest
+import testtools
+from testtools.matchers import DocTestMatches, Equals, MatchesAny
+
+import traceback2 as traceback
+
+
+@contextlib.contextmanager
+def captured_output(streamname):
+ stream = StringIO()
+ patch = fixtures.MonkeyPatch('sys.%s' % streamname, stream)
+ with patch:
+ yield stream
+
+
+FNAME = __file__
+if FNAME.endswith('.pyc'):
+ FNAME = FNAME[:-1]
+class FakeLoader:
+ def __init__(self, lines):
+ self._lines = lines
+ def get_source(self, name):
+ return self._lines
+fake_module = dict(
+ __name__='fred',
+ __loader__=FakeLoader(''.join(linecache.getlines(FNAME)))
+ )
+
+
+test_code = namedtuple('code', ['co_filename', 'co_name'])
+test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
+test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next'])
+
+
+class SyntaxTracebackCases(testtools.TestCase):
+ # For now, a very minimal set of tests. I want to be sure that
+ # formatting of SyntaxErrors works based on changes for 2.1.
+
+ def get_exception_format(self, func, exc):
+ try:
+ func()
+ except exc as value:
+ return traceback.format_exception_only(exc, value)
+ else:
+ raise ValueError("call did not raise exception")
+
+ def syntax_error_with_caret(self):
+ compile("def fact(x):\n\treturn x!\n", "?", "exec")
+
+ def syntax_error_with_caret_2(self):
+ compile("1 +\n", "?", "exec")
+
+ def syntax_error_bad_indentation(self):
+ compile("def spam():\n print(1)\n print(2)", "?", "exec")
+
+ def syntax_error_with_caret_non_ascii(self):
+ compile('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', "?", "exec")
+
+ def syntax_error_bad_indentation2(self):
+ compile(" print(2)", "?", "exec")
+
+ def test_caret(self):
+ err = self.get_exception_format(self.syntax_error_with_caret,
+ SyntaxError)
+ self.assertEqual(len(err), 4)
+ self.assertTrue(err[1].strip() == "return x!")
+ self.assertIn("^", err[2]) # third line has caret
+ self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
+
+ err = self.get_exception_format(self.syntax_error_with_caret_2,
+ SyntaxError)
+ self.assertIn("^", err[2]) # third line has caret
+ self.assertEqual(err[2].count('\n'), 1) # and no additional newline
+ self.assertEqual(err[1].find("+"), err[2].find("^")) # in the right place
+
+ err = self.get_exception_format(self.syntax_error_with_caret_non_ascii,
+ SyntaxError)
+ self.assertIn("^", err[2]) # third line has caret
+ self.assertEqual(err[2].count('\n'), 1) # and no additional newline
+ self.assertEqual(err[1].find("+"), err[2].find("^")) # in the right place
+
+ def test_nocaret(self):
+ exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
+ err = traceback.format_exception_only(SyntaxError, exc)
+ self.assertEqual(len(err), 3)
+ self.assertEqual(err[1].strip(), "bad syntax")
+
+ def test_bad_indentation(self):
+ err = self.get_exception_format(self.syntax_error_bad_indentation,
+ IndentationError)
+ self.assertEqual(len(err), 4)
+ self.assertEqual(err[1].strip(), "print(2)")
+ self.assertIn("^", err[2])
+ self.assertEqual(err[1].find(")"), err[2].find("^"))
+
+ err = self.get_exception_format(self.syntax_error_bad_indentation2,
+ IndentationError)
+ self.assertEqual(len(err), 4)
+ self.assertEqual(err[1].strip(), "print(2)")
+ self.assertIn("^", err[2])
+ # pypy has a different offset for its errors.
+ pos_cpython = err[1].find("p")
+ pos_pypy = err[1].find(")")
+ self.assertThat(
+ err[2].find("^"),
+ MatchesAny(Equals(pos_cpython), Equals(pos_pypy)))
+
+ def test_base_exception(self):
+ # Test that exceptions derived from BaseException are formatted right
+ e = KeyboardInterrupt()
+ lst = traceback.format_exception_only(e.__class__, e)
+ self.assertThat(lst,
+ MatchesAny(Equals(['KeyboardInterrupt\n']),
+ Equals(['exceptions.KeyboardInterrupt\n'])))
+
+ def test_format_exception_only_bad__str__(self):
+ def qualname(X):
+ return getattr(X, '__qualname__', X.__name__)
+ class X(Exception):
+ def __str__(self):
+ 1/0
+ err = traceback.format_exception_only(X, X())
+ self.assertEqual(len(err), 1)
+ str_value = '<unprintable %s object>' % X.__name__
+ if X.__module__ in ('__main__', 'builtins'):
+ str_name = qualname(X)
+ else:
+ str_name = '.'.join([X.__module__, qualname(X)])
+ self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
+
+ def test_format_exception_only_undecodable__str__(self):
+ # This won't decode via the ascii codec.
+ X = Exception(u('\u5341').encode('shift-jis'))
+ err = traceback.format_exception_only(type(X), X)
+ self.assertEqual(len(err), 1)
+ str_value = "b'\\x8f\\\\'"
+ self.assertEqual(err[0], "Exception: %s\n" % str_value)
+
+ def test_without_exception(self):
+ err = traceback.format_exception_only(None, None)
+ self.assertEqual(err, ['None\n'])
+
+ def test_encoded_file(self):
+ # Test that tracebacks are correctly printed for encoded source files:
+ # - correct line number (Issue2384)
+ # - respect file encoding (Issue3975)
+ import tempfile, sys, subprocess, os
+
+ # The spawned subprocess has its stdout redirected to a PIPE, and its
+ # encoding may be different from the current interpreter, on Windows
+ # at least.
+ process = subprocess.Popen([sys.executable, "-c",
+ "import sys; print(sys.stdout.encoding)"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ stdout, stderr = process.communicate()
+ output_encoding = text_type(stdout, 'ascii').splitlines()[0]
+
+ def do_test(firstlines, message, charset, lineno, output_encoding):
+ # Raise the message in a subprocess, and catch the output
+ with fixtures.TempDir() as d:
+ TESTFN = d.path + '/fname'
+ output = io.open(TESTFN, "w", encoding=charset)
+ output.write(u("""{0}if 1:
+ import traceback;
+ raise RuntimeError('{1}')
+ """).format(firstlines, message))
+ output.close()
+ process = subprocess.Popen([sys.executable, TESTFN],
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ stdout, stderr = process.communicate()
+ if output_encoding == 'None':
+ output_encoding = charset
+ stdout = stdout.decode(output_encoding).splitlines()
+
+ # The source lines are encoded with the 'backslashreplace' handler
+ encoded_message = message.encode(output_encoding,
+ 'backslashreplace')
+ # and we just decoded them with the output_encoding.
+ message_ascii = encoded_message.decode(output_encoding)
+
+ err_line = u("raise RuntimeError('{0}')").format(message_ascii)
+ err_msg = u("RuntimeError: {0}").format(message_ascii)
+
+ if platform.python_implementation() == 'PyPy':
+ # PyPy includes its own top level app_main.py in the traceback.
+ del stdout[1]
+ self.assertIn(("line %s" % lineno), stdout[1],
+ "Invalid line number: {0!r} instead of {1}".format(
+ stdout[1], lineno))
+ self.assertTrue(stdout[2].endswith(err_line),
+ "Invalid traceback line: {0!r} instead of {1!r}".format(
+ stdout[2], err_line))
+ self.assertTrue(stdout[3] == err_msg,
+ "Invalid error message: {0!r} instead of {1!r}".format(
+ stdout[3], err_msg))
+
+ do_test("", "foo", "ascii", 3, output_encoding)
+ for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
+ if charset == "ascii":
+ text = u("foo")
+ elif charset == "GBK":
+ text = u("\u4E02\u5100")
+ else:
+ text = u("h\xe9 ho")
+ do_test("# coding: {0}\n".format(charset),
+ text, charset, 4, output_encoding)
+ do_test("#!shebang\n# coding: {0}\n".format(charset),
+ text, charset, 5, output_encoding)
+ do_test(" \t\f\n# coding: {0}\n".format(charset),
+ text, charset, 5, output_encoding)
+ # Issue #18960: coding spec should has no effect
+ # (Fixed in 3.4)
+ if sys.version_info[:2] > (3, 3):
+ do_test(
+ "0\n# coding: GBK\n", u("h\xe9 ho"), 'utf-8', 5,
+ output_encoding)
+
+
+class TracebackFormatTests(unittest.TestCase):
+
+ def some_exception(self):
+ raise KeyError('blah')
+
+ def check_traceback_format(self, cleanup_func=None):
+ try:
+ if issubclass(six.binary_type, six.string_types):
+ # Python 2.6 or other platform where the interpreter
+ # is likely going to be spitting out bytes, which will
+ # then fail with io.StringIO(), so we skip the cross-
+ # checks with the C API there. Note that _testcapi
+ # is included in (at least) Ubuntu CPython packages, which
+ # makes the import check less effective than desired.
+ raise ImportError
+ from _testcapi import traceback_print
+ except ImportError:
+ traceback_print = None
+ try:
+ self.some_exception()
+ except KeyError:
+ type_, value, tb = sys.exc_info()
+ if cleanup_func is not None:
+ # Clear the inner frames, not this one
+ cleanup_func(tb.tb_next)
+ traceback_fmt = u('Traceback (most recent call last):\n') + \
+ u('').join(traceback.format_tb(tb))
+ if traceback_print is not None:
+ file_ = StringIO()
+ traceback_print(tb, file_)
+ python_fmt = file_.getvalue()
+ # Call all _tb and _exc functions
+ with captured_output("stderr") as tbstderr:
+ traceback.print_tb(tb)
+ tbfile = StringIO()
+ traceback.print_tb(tb, file=tbfile)
+ with captured_output("stderr") as excstderr:
+ traceback.print_exc()
+ excfmt = traceback.format_exc()
+ excfile = StringIO()
+ traceback.print_exc(file=excfile)
+ else:
+ self.fail("unable to create test traceback string")
+
+ # Make sure that Python and the traceback module format the same thing
+ if traceback_print is not None:
+ self.assertEqual(traceback_fmt, python_fmt)
+ # Now verify the _tb func output
+ self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
+ # Now verify the _exc func output
+ self.assertEqual(excstderr.getvalue(), excfile.getvalue())
+ self.assertEqual(excfmt, excfile.getvalue())
+
+ # Make sure that the traceback is properly indented.
+ tb_lines = traceback_fmt.splitlines()
+ self.assertEqual(len(tb_lines), 5)
+ banner = tb_lines[0]
+ location, source_line = tb_lines[-2:]
+ self.assertTrue(banner.startswith('Traceback'))
+ self.assertTrue(location.startswith(' File'))
+ self.assertTrue(source_line.startswith(' raise'))
+
+ def test_traceback_format(self):
+ self.check_traceback_format()
+
+ def test_traceback_format_with_cleared_frames(self):
+ # Check that traceback formatting also works with a clear()ed frame
+ def cleanup_tb(tb):
+ if getattr(tb.tb_frame, 'clear_frames', None):
+ tb.tb_frame.clear()
+ self.check_traceback_format(cleanup_tb)
+
+ def test_stack_format(self):
+ # Verify _stack functions. Note we have to use _getframe(1) to
+ # compare them without this frame appearing in the output
+ with captured_output("stderr") as ststderr:
+ traceback.print_stack(sys._getframe(1))
+ stfile = StringIO()
+ traceback.print_stack(sys._getframe(1), file=stfile)
+ self.assertEqual(ststderr.getvalue(), stfile.getvalue())
+
+ stfmt = traceback.format_stack(sys._getframe(1))
+
+ self.assertEqual(ststderr.getvalue(), "".join(stfmt))
+
+
+cause_message = (
+ "\nThe above exception was the direct cause "
+ "of the following exception:\n\n")
+
+context_message = (
+ "\nDuring handling of the above exception, "
+ "another exception occurred:\n\n")
+
+boundaries = re.compile(
+ '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
+
+
+class BaseExceptionReportingTests:
+
+ def get_exception(self, exception_or_callable, tb=None):
+ if isinstance(exception_or_callable, Exception):
+ return exception_or_callable, tb
+ try:
+ exception_or_callable()
+ except Exception as e:
+ return e, sys.exc_info()[2]
+
+ def zero_div(self):
+ 1/0 # In zero_div
+
+ def check_zero_div(self, msg):
+ lines = msg.splitlines()
+ self.assertTrue(lines[-3].startswith(' File'))
+ self.assertIn('1/0 # In zero_div', lines[-2])
+ self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
+
+ def test_simple(self):
+ try:
+ 1/0 # Marker
+ except ZeroDivisionError as _:
+ e = _
+ tb = sys.exc_info()[2]
+ lines = self.get_report(e, tb=tb).splitlines()
+ self.assertEqual(len(lines), 4)
+ self.assertTrue(lines[0].startswith('Traceback'))
+ self.assertTrue(lines[1].startswith(' File'))
+ self.assertIn('1/0 # Marker', lines[2])
+ # < 3 show as exceptions.ZeroDivisionError.
+ self.assertIn('ZeroDivisionError', lines[3])
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_cause(self):
+ def inner_raise():
+ try:
+ self.zero_div()
+ except ZeroDivisionError as e:
+ raise_from(KeyError, e)
+ def outer_raise():
+ inner_raise() # Marker
+ blocks = boundaries.split(self.get_report(outer_raise))
+ self.assertEqual(len(blocks), 3)
+ self.assertEqual(blocks[1], cause_message)
+ self.check_zero_div(blocks[0])
+ self.assertIn('inner_raise() # Marker', blocks[2])
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_context(self):
+ def inner_raise():
+ try:
+ self.zero_div()
+ except ZeroDivisionError:
+ raise KeyError
+ def outer_raise():
+ inner_raise() # Marker
+ blocks = boundaries.split(self.get_report(outer_raise))
+ self.assertEqual(len(blocks), 3)
+ self.assertEqual(blocks[1], context_message)
+ self.check_zero_div(blocks[0])
+ self.assertIn('inner_raise() # Marker', blocks[2])
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 3), "Only applies to 3.3+")
+ def test_context_suppression(self):
+ try:
+ try:
+ raise Exception
+ except:
+ raise_from(ZeroDivisionError, None)
+ except ZeroDivisionError as _:
+ e = _
+ tb = sys.exc_info()[2]
+ lines = self.get_report(e, tb)
+ self.assertThat(lines, DocTestMatches("""\
+Traceback (most recent call last):
+ File "...traceback2/tests/test_traceback.py", line ..., in test_context_suppression
+ raise_from(ZeroDivisionError, None)
+ File "<string>", line 2, in raise_from
+ZeroDivisionError
+""", doctest.ELLIPSIS))
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_cause_and_context(self):
+ # When both a cause and a context are set, only the cause should be
+ # displayed and the context should be muted.
+ def inner_raise():
+ try:
+ self.zero_div()
+ except ZeroDivisionError as _e:
+ e = _e
+ try:
+ xyzzy
+ except NameError:
+ raise_from(KeyError, e)
+ def outer_raise():
+ inner_raise() # Marker
+ blocks = boundaries.split(self.get_report(outer_raise))
+ self.assertEqual(len(blocks), 3)
+ self.assertEqual(blocks[1], cause_message)
+ self.check_zero_div(blocks[0])
+ self.assertIn('inner_raise() # Marker', blocks[2])
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_cause_recursive(self):
+ def inner_raise():
+ try:
+ try:
+ self.zero_div()
+ except ZeroDivisionError as e:
+ z = e
+ raise_from(KeyError, e)
+ except KeyError as e:
+ raise_from(z, e)
+ def outer_raise():
+ inner_raise() # Marker
+ blocks = boundaries.split(self.get_report(outer_raise))
+ self.assertEqual(len(blocks), 3)
+ self.assertEqual(blocks[1], cause_message)
+ # The first block is the KeyError raised from the ZeroDivisionError
+ self.assertIn('raise_from(KeyError, e)', blocks[0])
+ self.assertNotIn('1/0', blocks[0])
+ # The second block (apart from the boundary) is the ZeroDivisionError
+ # re-raised from the KeyError
+ self.assertIn('inner_raise() # Marker', blocks[2])
+ self.check_zero_div(blocks[2])
+
+ def test_syntax_error_offset_at_eol(self):
+ # See #10186.
+ def e():
+ raise SyntaxError('', ('', 0, 5, u('hello')))
+ msg = self.get_report(e).splitlines()
+ self.assertEqual(msg[-2], " ^")
+ def e():
+ exec("x = 5 | 4 |")
+ msg = self.get_report(e).splitlines()
+ self.assertEqual(msg[-2], ' ^')
+
+
+class PyExcReportingTests(BaseExceptionReportingTests, testtools.TestCase):
+ #
+ # This checks reporting through the 'traceback' module, with both
+ # format_exception() and print_exception().
+ #
+
+ def get_report(self, e, tb=None):
+ e, tb = self.get_exception(e, tb)
+ s = ''.join(
+ traceback.format_exception(type(e), e, tb))
+ with captured_output("stderr") as sio:
+ traceback.print_exception(type(e), e, tb)
+ self.assertEqual(sio.getvalue(), s)
+ return s
+
+
+class MiscTracebackCases(unittest.TestCase):
+ #
+ # Check non-printing functions in traceback module
+ #
+
+ def test_clear(self):
+ def outer():
+ middle()
+ def middle():
+ inner()
+ def inner():
+ i = 1
+ 1/0
+
+ try:
+ outer()
+ except:
+ type_, value, tb = sys.exc_info()
+
+ # Initial assertion: there's one local in the inner frame.
+ inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
+ self.assertEqual(len(inner_frame.f_locals), 1)
+
+ # Clear traceback frames
+ traceback.clear_frames(tb)
+
+ # Local variable dict should now be empty (on Python 3.4+)
+ if sys.version_info[:2] > (3, 3):
+ self.assertEqual({}, inner_frame.f_locals)
+
+
+class TestFrame(unittest.TestCase):
+
+ def test_basics(self):
+ linecache.clearcache()
+ linecache.lazycache("f", fake_module)
+ f = traceback.FrameSummary("f", 1, "dummy")
+ self.assertEqual(
+ ("f", 1, "dummy", '"""Test cases for traceback module"""'),
+ tuple(f))
+ self.assertEqual(None, f.locals)
+
+ def test_lazy_lines(self):
+ linecache.clearcache()
+ f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
+ self.assertEqual(None, f._line)
+ linecache.lazycache("f", fake_module)
+ self.assertEqual(
+ '"""Test cases for traceback module"""',
+ f.line)
+
+ def test_explicit_line(self):
+ f = traceback.FrameSummary("f", 1, "dummy", line="line")
+ self.assertEqual("line", f.line)
+
+
+class TestStack(unittest.TestCase):
+
+ def test_walk_stack(self):
+ s = list(traceback.walk_stack(None))
+ self.assertGreater(len(s), 10)
+
+ def test_walk_tb(self):
+ try:
+ 1/0
+ except Exception:
+ _, _, tb = sys.exc_info()
+ s = list(traceback.walk_tb(tb))
+ self.assertEqual(len(s), 1)
+
+ def test_extract_stack(self):
+ s = traceback.StackSummary.extract(traceback.walk_stack(None))
+ self.assertIsInstance(s, traceback.StackSummary)
+
+ def test_extract_stack_limit(self):
+ s = traceback.StackSummary.extract(traceback.walk_stack(None), limit=5)
+ self.assertEqual(len(s), 5)
+
+ def test_extract_stack_lookup_lines(self):
+ linecache.clearcache()
+ linecache.updatecache('/foo.py', fake_module)
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, None, None)
+ s = traceback.StackSummary.extract(iter([(f, 8)]), lookup_lines=True)
+ linecache.clearcache()
+ self.assertEqual(s[0].line, "import sys")
+
+ def test_extract_stackup_deferred_lookup_lines(self):
+ linecache.clearcache()
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, None, None)
+ s = traceback.StackSummary.extract(iter([(f, 8)]), lookup_lines=False)
+ self.assertEqual({}, linecache.cache)
+ linecache.updatecache('/foo.py', fake_module)
+ self.assertEqual(s[0].line, "import sys")
+
+ def test_from_list(self):
+ s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
+ self.assertEqual(
+ [' File "foo.py", line 1, in fred\n line\n'],
+ s.format())
+
+ def test_format_smoke(self):
+ # For detailed tests see the format_list tests, which consume the same
+ # code.
+ s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
+ self.assertEqual(
+ [' File "foo.py", line 1, in fred\n line\n'],
+ s.format())
+
+ @unittest.skipIf(sys.getfilesystemencoding()=='ANSI_X3.4-1968',
+ 'Requires non-ascii fs encoding')
+ def test_format_unicode_filename(self):
+ # Filenames in Python2 may be bytestrings that will fail to implicit
+ # decode.
+ fname = u('\u5341').encode(sys.getfilesystemencoding())
+ s = traceback.StackSummary.from_list([(fname, 1, 'fred', 'line')])
+ self.assertEqual(
+ [u(' File "\u5341", line 1, in fred\n line\n')],
+ s.format())
+
+ def test_format_bad_filename(self):
+ # Filenames in Python2 may be bytestrings that will fail to implicit
+ # decode.
+ # This won't decode via the implicit(ascii) codec or the default
+ # fs encoding (unless the encoding is a wildcard encoding).
+ fname = b('\x8b')
+ s = traceback.StackSummary.from_list([(fname, 1, 'fred', 'line')])
+ self.assertEqual(
+ [' File "b\'\\x8b\'", line 1, in fred\n line\n'],
+ s.format())
+
+ def test_locals(self):
+ linecache.updatecache('/foo.py', globals())
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, globals(), {'something': 1})
+ s = traceback.StackSummary.extract(iter([(f, 6)]), capture_locals=True)
+ self.assertEqual(s[0].locals, {'something': '1'})
+
+ def test_no_locals(self):
+ linecache.updatecache('/foo.py', globals())
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, globals(), {'something': 1})
+ s = traceback.StackSummary.extract(iter([(f, 6)]))
+ self.assertEqual(s[0].locals, None)
+
+ def test_format_locals(self):
+ def some_inner(k, v):
+ a = 1
+ b = 2
+ return traceback.StackSummary.extract(
+ traceback.walk_stack(None), capture_locals=True, limit=1)
+ s = some_inner(3, 4)
+ self.assertEqual(
+ [' File "' + FNAME + '", line 651, '
+ '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'
+ ], s.format())
+
+
+
+class TestTracebackException(unittest.TestCase):
+
+ def test_smoke(self):
+ try:
+ 1/0
+ except Exception:
+ exc_info = sys.exc_info()
+ exc = traceback.TracebackException(*exc_info)
+ expected_stack = traceback.StackSummary.extract(
+ traceback.walk_tb(exc_info[2]))
+ self.assertEqual(None, exc.__cause__)
+ self.assertEqual(None, exc.__context__)
+ self.assertEqual(False, exc.__suppress_context__)
+ self.assertEqual(expected_stack, exc.stack)
+ self.assertEqual(exc_info[0], exc.exc_type)
+ self.assertEqual(str(exc_info[1]), str(exc))
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 0), "Only applies to 3+")
+ def test_from_exception(self):
+ # Check all the parameters are accepted.
+ def foo():
+ 1/0
+ try:
+ foo()
+ except Exception as e:
+ exc_info = sys.exc_info()
+ self.expected_stack = traceback.StackSummary.extract(
+ traceback.walk_tb(exc_info[2]), limit=1, lookup_lines=False,
+ capture_locals=True)
+ self.exc = traceback.TracebackException.from_exception(
+ e, limit=1, lookup_lines=False, capture_locals=True)
+ expected_stack = self.expected_stack
+ exc = self.exc
+ self.assertEqual(None, exc.__cause__)
+ self.assertEqual(None, exc.__context__)
+ self.assertEqual(False, exc.__suppress_context__)
+ self.assertEqual(expected_stack, exc.stack)
+ self.assertEqual(exc_info[0], exc.exc_type)
+ self.assertEqual(str(exc_info[1]), str(exc))
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_cause(self):
+ try:
+ try:
+ 1/0
+ finally:
+ exc_info_context = sys.exc_info()
+ exc_context = traceback.TracebackException(*exc_info_context)
+ cause = Exception("cause")
+ raise_from(Exception("uh ok"), cause)
+ except Exception:
+ exc_info = sys.exc_info()
+ exc = traceback.TracebackException(*exc_info)
+ expected_stack = traceback.StackSummary.extract(
+ traceback.walk_tb(exc_info[2]))
+ exc_cause = traceback.TracebackException(Exception, cause, None)
+ self.assertEqual(exc_cause, exc.__cause__)
+ self.assertEqual(exc_context, exc.__context__)
+ if hasattr(exc_info[1], '__suppress_context__'):
+ self.assertEqual(True, exc.__suppress_context__)
+ self.assertEqual(expected_stack, exc.stack)
+ self.assertEqual(exc_info[0], exc.exc_type)
+ self.assertEqual(str(exc_info[1]), str(exc))
+
+ @unittest.skipIf(sys.version_info[:2] < (3, 2), "Only applies to 3.2+")
+ def test_context(self):
+ try:
+ try:
+ 1/0
+ finally:
+ exc_info_context = sys.exc_info()
+ exc_context = traceback.TracebackException(*exc_info_context)
+ raise Exception("uh oh")
+ except Exception:
+ exc_info = sys.exc_info()
+ exc = traceback.TracebackException(*exc_info)
+ expected_stack = traceback.StackSummary.extract(
+ traceback.walk_tb(exc_info[2]))
+ self.assertEqual(None, exc.__cause__)
+ self.assertEqual(exc_context, exc.__context__)
+ self.assertEqual(False, exc.__suppress_context__)
+ self.assertEqual(expected_stack, exc.stack)
+ self.assertEqual(exc_info[0], exc.exc_type)
+ self.assertEqual(str(exc_info[1]), str(exc))
+
+ def test_limit(self):
+ def recurse(n):
+ if n:
+ recurse(n-1)
+ else:
+ 1/0
+ try:
+ recurse(10)
+ except Exception:
+ exc_info = sys.exc_info()
+ exc = traceback.TracebackException(*exc_info, limit=5)
+ expected_stack = traceback.StackSummary.extract(
+ traceback.walk_tb(exc_info[2]), limit=5)
+ self.assertEqual(expected_stack, exc.stack)
+
+ def test_lookup_lines(self):
+ linecache.clearcache()
+ e = Exception("uh oh")
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, None, None)
+ tb = test_tb(f, 8, None)
+ exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False)
+ self.assertEqual({}, linecache.cache)
+ linecache.updatecache('/foo.py', fake_module)
+ self.assertEqual(exc.stack[0].line, "import sys")
+
+ def test_locals(self):
+ linecache.updatecache('/foo.py', fake_module)
+ e = Exception("uh oh")
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, globals(), {'something': 1, 'other': 'string'})
+ tb = test_tb(f, 6, None)
+ exc = traceback.TracebackException(
+ Exception, e, tb, capture_locals=True)
+ self.assertEqual(
+ exc.stack[0].locals, {'something': '1', 'other': "'string'"})
+
+ def test_no_locals(self):
+ linecache.updatecache('/foo.py', fake_module)
+ e = Exception("uh oh")
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, fake_module, {'something': 1})
+ tb = test_tb(f, 6, None)
+ exc = traceback.TracebackException(Exception, e, tb)
+ self.assertEqual(exc.stack[0].locals, None)
+
+ def test_syntax_no_extras(self):
+ linecache.updatecache('/foo.py', fake_module)
+ e = SyntaxError("uh oh")
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, fake_module, {'something': 1})
+ tb = test_tb(f, 6, None)
+ exc = traceback.TracebackException(SyntaxError, e, tb)
+ self.assertEqual([
+ u('Traceback (most recent call last):\n'),
+ u(' File "/foo.py", line 6, in method\n from io import StringIO\n'),
+ u(' File "<string>", line None\n'),
+ u('SyntaxError: uh oh\n')],
+ list(exc.format()))
+
+ def test_syntax_undecoded_lines(self):
+ # If the interpreter returns bytestrings, we have to decode ourselves.
+ lines = u("1\n\u5341\n3\n")
+ fake_module = dict(
+ __name__='fred',
+ __loader__=FakeLoader(lines)
+ )
+ linecache.updatecache('/foo.py', fake_module)
+ e = SyntaxError("uh oh")
+ e.filename = '/foo.py'
+ e.lineno = 2
+ e.text = b('something wrong')
+ e.offset = 1
+ c = test_code('/foo.py', 'method')
+ f = test_frame(c, fake_module, {'something': 1})
+ tb = test_tb(f, 2, None)
+ exc = traceback.TracebackException(SyntaxError, e, tb)
+ list(exc.format_exception_only())
+ self.assertEqual([
+ u('Traceback (most recent call last):\n'),
+ u(' File "/foo.py", line 2, in method\n \u5341\n'),
+ u(' File "/foo.py", line 2\n'),
+ u(' \u5341\n'),
+ u(' ^\n'),
+ u('SyntaxError: uh oh\n')],
+ list(exc.format()))
+
+ @unittest.skipUnless(sys.version_info[0] < 3, "Applies to 2.x only.")
+ @unittest.skipIf(sys.getfilesystemencoding()=='ANSI_X3.4-1968',
+ 'Requires non-ascii fs encoding')
+ def test_format_unicode_filename(self):
+ # Filenames in Python2 may be bytestrings that will fail to implicit
+ # decode.
+ fname = u('\u5341').encode(sys.getfilesystemencoding())
+ lines = u("1\n2\n3\n")
+ fake_module = dict(
+ __name__='fred',
+ __loader__=FakeLoader(lines)
+ )
+ linecache.updatecache(fname, fake_module)
+ e = SyntaxError("uh oh")
+ e.filename = fname
+ e.lineno = 2
+ e.text = b('something wrong')
+ e.offset = 1
+ c = test_code(fname, 'method')
+ f = test_frame(c, fake_module, {'something': 1})
+ tb = test_tb(f, 2, None)
+ exc = traceback.TracebackException(SyntaxError, e, tb)
+ list(exc.format_exception_only())
+ self.assertEqual([
+ u('Traceback (most recent call last):\n'),
+ u(' File "\u5341", line 2, in method\n 2\n'),
+ u(' File "\u5341", line 2\n'),
+ u(' something wrong\n'),
+ u(' ^\n'),
+ u('SyntaxError: uh oh\n')],
+ list(exc.format()))
+
+ @unittest.skipUnless(sys.version_info[0] < 3, "Applies to 2.x only.")
+ def test_format_bad_filename(self):
+ # Filenames in Python2 may be bytestrings that will fail to implicit
+ # decode.
+ # This won't decode via the implicit(ascii) codec or the default
+ # fs encoding (unless the encoding is a wildcard encoding).
+ fname = b('\x8b')
+ lines = u("1\n2\n3\n")
+ fake_module = dict(
+ __name__='fred',
+ __loader__=FakeLoader(lines)
+ )
+ linecache.updatecache(fname, fake_module)
+ e = SyntaxError("uh oh")
+ e.filename = fname
+ e.lineno = 2
+ e.text = b('something wrong')
+ e.offset = 1
+ c = test_code(fname, 'method')
+ f = test_frame(c, fake_module, {'something': 1})
+ tb = test_tb(f, 2, None)
+ exc = traceback.TracebackException(SyntaxError, e, tb)
+ list(exc.format_exception_only())
+ self.assertEqual([
+ u('Traceback (most recent call last):\n'),
+ b(' File "b\'\\x8b\'", line 2, in method\n 2\n').decode(),
+ b(' File "b\'\\x8b\'", line 2\n').decode(),
+ u(' something wrong\n'),
+ u(' ^\n'),
+ u('SyntaxError: uh oh\n')],
+ list(exc.format()))