Imported Upstream version 1.4.0 87/124087/1 upstream upstream/1.4.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 10 Apr 2017 07:21:25 +0000 (16:21 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 10 Apr 2017 07:21:41 +0000 (16:21 +0900)
Change-Id: I1dd9b757190fd564951cfbcfd27b7a2dc78138d7
Signed-off-by: DongHun Kwak <dh0128.kwak@samsung.com>
20 files changed:
.testr.conf [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
Makefile [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.rst [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
test-requirements.txt [new file with mode: 0644]
traceback2.egg-info/PKG-INFO [new file with mode: 0644]
traceback2.egg-info/SOURCES.txt [new file with mode: 0644]
traceback2.egg-info/dependency_links.txt [new file with mode: 0644]
traceback2.egg-info/not-zip-safe [new file with mode: 0644]
traceback2.egg-info/pbr.json [new file with mode: 0644]
traceback2.egg-info/requires.txt [new file with mode: 0644]
traceback2.egg-info/top_level.txt [new file with mode: 0644]
traceback2/__init__.py [new file with mode: 0644]
traceback2/tests/__init__.py [new file with mode: 0644]
traceback2/tests/test_traceback.py [new file with mode: 0644]

diff --git a/.testr.conf b/.testr.conf
new file mode 100644 (file)
index 0000000..a7167a7
--- /dev/null
@@ -0,0 +1,4 @@
+[DEFAULT]
+test_command=${PYTHON:-python} -m subunit.run discover $LISTOPT $IDOPTION
+test_id_option=--load-list $IDFILE
+test_list_option=--list
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..ad86f89
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Robert Collins <rbtcollins@hp.com>
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..2a3ef46
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,52 @@
+CHANGES
+=======
+
+1.4.0
+-----
+
+* Handle unicode paths more thoroughly
+
+1.3.0
+-----
+
+* Try harder to show unicode lines in SyntaxErrors
+
+1.2.1
+-----
+
+* Unbreak handling of syntax errors with no line
+
+1.2.0
+-----
+
+* Handle filenames that can't be decoded to unicode
+
+1.1.0
+-----
+
+* Handle objects with broken __unicode__
+
+1.0.0
+-----
+
+* Time for 1.0.0
+* Fixe test when running with .pyc files
+* Issue #22936: Make it possible to show local variables in tracebacks
+* Remaining fallout from 17911
+* Fix brownbag in issue 17911 commit
+* Issue #17911: traceback module overhaul
+* Supporting boilerplate
+
+0.0.1
+-----
+
+* Fixes for the test suite on PyPy
+* Python2.xify the code base
+* Handle missing __traceback__ in tests for < 3.x
+* Handle absence of __context__ and __cause__ on < 3.x
+* Handle different type repr in < 3.2
+* Fixes for 3.2 - qualname and exception suppressing
+* Fixes for 3.3
+* Port trunk traceback module to standalone w/3.5
+* Ignore .eggs as well
+* Basic boilerplate
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..eb41afa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,3 @@
+# tag it first.
+release:
+       python setup.py sdist bdist_wheel upload -s
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..4815752
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,41 @@
+Metadata-Version: 1.1
+Name: traceback2
+Version: 1.4.0
+Summary: Backports of the traceback module
+Home-page: https://github.com/testing-cabal/traceback2
+Author: Testing-cabal
+Author-email: testing-cabal@lists.launchpad.net
+License: UNKNOWN
+Description: A backport of traceback to older supported Pythons.
+        
+         >>> import traceback2 as traceback
+        
+        Profit.
+        
+        Things to be aware of!
+        
+        In Python 2.x, unlike traceback, traceback2 creates unicode output (because it
+        depends on the linecache2 module).
+        
+        Exception frame clearing silently does nothing if the interpreter in use does
+        not support it.
+        
+        traceback2._some_str, which while not an official API is so old its likely in
+        use behaves similarly to the Python3 version - objects where unicode(obj) fails
+        but str(object) works will be shown as b'thestrvaluerepr'.
+        
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..3d279dd
--- /dev/null
@@ -0,0 +1,17 @@
+A backport of traceback to older supported Pythons.
+
+ >>> import traceback2 as traceback
+
+Profit.
+
+Things to be aware of!
+
+In Python 2.x, unlike traceback, traceback2 creates unicode output (because it
+depends on the linecache2 module).
+
+Exception frame clearing silently does nothing if the interpreter in use does
+not support it.
+
+traceback2._some_str, which while not an official API is so old its likely in
+use behaves similarly to the Python3 version - objects where unicode(obj) fails
+but str(object) works will be shown as b'thestrvaluerepr'.
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..2861de5
--- /dev/null
@@ -0,0 +1 @@
+linecache2
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..9246706
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,40 @@
+[metadata]
+name = traceback2
+version = 1.0.0
+summary = Backports of the traceback module
+description-file = 
+       README.rst
+author = Testing-cabal
+author-email = testing-cabal@lists.launchpad.net
+home-page = https://github.com/testing-cabal/traceback2
+classifier = 
+       Development Status :: 6 - Mature
+       Intended Audience :: Developers
+       License :: OSI Approved :: Python Software Foundation License
+       Operating System :: OS Independent
+       Programming Language :: Python
+       Programming Language :: Python :: 2
+       Programming Language :: Python :: 2.6
+       Programming Language :: Python :: 2.7
+       Programming Language :: Python :: 3
+       Programming Language :: Python :: 3.2
+       Programming Language :: Python :: 3.3
+       Programming Language :: Python :: 3.4
+       Topic :: Software Development
+
+[files]
+packages = 
+       traceback2
+
+[wheel]
+universal = 1
+
+[pbr]
+autodoc_index_modules = 0
+warnerrors = true
+
+[egg_info]
+tag_date = 0
+tag_svn_revision = 0
+tag_build = 
+
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..31b64ee
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+    setup_requires=['pbr'],
+    pbr=True)
+
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644 (file)
index 0000000..14e4a45
--- /dev/null
@@ -0,0 +1,4 @@
+contextlib2
+fixtures
+testtools
+unittest2
diff --git a/traceback2.egg-info/PKG-INFO b/traceback2.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..4815752
--- /dev/null
@@ -0,0 +1,41 @@
+Metadata-Version: 1.1
+Name: traceback2
+Version: 1.4.0
+Summary: Backports of the traceback module
+Home-page: https://github.com/testing-cabal/traceback2
+Author: Testing-cabal
+Author-email: testing-cabal@lists.launchpad.net
+License: UNKNOWN
+Description: A backport of traceback to older supported Pythons.
+        
+         >>> import traceback2 as traceback
+        
+        Profit.
+        
+        Things to be aware of!
+        
+        In Python 2.x, unlike traceback, traceback2 creates unicode output (because it
+        depends on the linecache2 module).
+        
+        Exception frame clearing silently does nothing if the interpreter in use does
+        not support it.
+        
+        traceback2._some_str, which while not an official API is so old its likely in
+        use behaves similarly to the Python3 version - objects where unicode(obj) fails
+        but str(object) works will be shown as b'thestrvaluerepr'.
+        
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development
diff --git a/traceback2.egg-info/SOURCES.txt b/traceback2.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..7b1f471
--- /dev/null
@@ -0,0 +1,19 @@
+.testr.conf
+AUTHORS
+ChangeLog
+Makefile
+README.rst
+requirements.txt
+setup.cfg
+setup.py
+test-requirements.txt
+traceback2/__init__.py
+traceback2.egg-info/PKG-INFO
+traceback2.egg-info/SOURCES.txt
+traceback2.egg-info/dependency_links.txt
+traceback2.egg-info/not-zip-safe
+traceback2.egg-info/pbr.json
+traceback2.egg-info/requires.txt
+traceback2.egg-info/top_level.txt
+traceback2/tests/__init__.py
+traceback2/tests/test_traceback.py
\ No newline at end of file
diff --git a/traceback2.egg-info/dependency_links.txt b/traceback2.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/traceback2.egg-info/not-zip-safe b/traceback2.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/traceback2.egg-info/pbr.json b/traceback2.egg-info/pbr.json
new file mode 100644 (file)
index 0000000..e71fd07
--- /dev/null
@@ -0,0 +1 @@
+{"git_version": "37c5f15", "is_release": true}
\ No newline at end of file
diff --git a/traceback2.egg-info/requires.txt b/traceback2.egg-info/requires.txt
new file mode 100644 (file)
index 0000000..816ba28
--- /dev/null
@@ -0,0 +1 @@
+linecache2
\ No newline at end of file
diff --git a/traceback2.egg-info/top_level.txt b/traceback2.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..ac5f6de
--- /dev/null
@@ -0,0 +1 @@
+traceback2
diff --git a/traceback2/__init__.py b/traceback2/__init__.py
new file mode 100644 (file)
index 0000000..88fcaaf
--- /dev/null
@@ -0,0 +1,597 @@
+"""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
diff --git a/traceback2/tests/__init__.py b/traceback2/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/traceback2/tests/test_traceback.py b/traceback2/tests/test_traceback.py
new file mode 100644 (file)
index 0000000..2a933b5
--- /dev/null
@@ -0,0 +1,899 @@
+"""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()))