Python2.xify the code base.

Skip a few tests that test the core interpreter and don't work on <3.
Unicodify the core.
Fix tests to deal with Python 2's behaviour (stdout not encoded at
all) when stdout is a pipe.
This commit is contained in:
Robert Collins 2014-11-24 21:49:37 +13:00
parent 43118d83bc
commit 9a9a29e95f
3 changed files with 57 additions and 30 deletions

View File

@ -3,3 +3,10 @@ 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.
Exception frame clearing silently does nothing if the interpreter in use does
not support it.

View File

@ -4,6 +4,8 @@ import linecache
import sys
import operator
from six import u
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
'format_tb', 'print_exc', 'format_exc', 'print_exception',
@ -16,9 +18,9 @@ __all__ = ['extract_stack', 'extract_tb', 'format_exception',
def _format_list_iter(extracted_list):
for filename, lineno, name, line in extracted_list:
item = ' File "{0}", line {1}, in {2}\n'.format(filename, lineno, name)
item = u(' File "{0}", line {1}, in {2}\n').format(filename, lineno, name)
if line:
item = item + ' {0}\n'.format(line.strip())
item = item + u(' {0}\n').format(line.strip())
yield item
def print_list(extracted_list, file=None):
@ -147,10 +149,10 @@ def _format_exception_iter(etype, value, tb, limit, chain):
for value, tb in values:
if isinstance(value, str):
# This is a cause/context message line
yield value + '\n'
yield value + u('\n')
continue
if tb:
yield 'Traceback (most recent call last):\n'
yield u('Traceback (most recent call last):\n')
for it in _format_list_iter(_extract_tb_iter(tb, limit=limit)):
yield it
for it in _format_exception_only_iter(type(value), value):
@ -209,32 +211,32 @@ def _format_exception_only_iter(etype, value):
return
stype = getattr(etype, '__qualname__', etype.__name__)
smod = etype.__module__
smod = u(etype.__module__)
if smod not in ("__main__", "builtins"):
stype = smod + '.' + stype
stype = smod + u('.') + stype
if not issubclass(etype, SyntaxError):
yield _format_final_exc_line(stype, value)
return
# It was a syntax error; show exactly where the problem was found.
filename = value.filename or "<string>"
lineno = str(value.lineno) or '?'
yield ' File "{0}", line {1}\n'.format(filename, lineno)
filename = value.filename or u("<string>")
lineno = str(value.lineno) or u('?')
yield u(' File "{0}", line {1}\n').format(filename, lineno)
badline = value.text
offset = value.offset
if badline is not None:
yield ' {0}\n'.format(badline.strip())
yield u(' {0}\n').format(u(badline).strip())
if offset is not None:
caretspace = badline.rstrip('\n')
caretspace = u(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 ' {0}^\n'.format(''.join(caretspace))
yield u(' {0}^\n'.format(''.join(caretspace)))
msg = value.msg or "<no detail available>"
yield "{0}: {1}\n".format(stype, msg)
yield u("{0}: {1}\n").format(stype, msg)
def _format_final_exc_line(etype, value):
valuestr = _some_str(value)

View File

@ -1,6 +1,7 @@
"""Test cases for traceback module"""
import doctest
import io
from io import StringIO
import sys
import re
@ -8,6 +9,7 @@ import re
import contextlib2 as contextlib
import fixtures
import six
from six import text_type, u
try:
from six import raise_from
except ImportError:
@ -144,21 +146,23 @@ class SyntaxTracebackCases(testtools.TestCase):
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
output_encoding = str(stdout, 'ascii').splitlines()[0]
output_encoding = text_type(stdout, 'ascii').splitlines()[0]
def do_test(firstlines, message, charset, lineno):
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 = open(TESTFN, "w", encoding=charset)
output.write("""{0}if 1:
output = io.open(TESTFN, "w", encoding=charset)
output.write(u("""{0}if 1:
import traceback;
raise RuntimeError('{1}')
""".format(firstlines, message))
""").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
@ -167,8 +171,8 @@ class SyntaxTracebackCases(testtools.TestCase):
# and we just decoded them with the output_encoding.
message_ascii = encoded_message.decode(output_encoding)
err_line = "raise RuntimeError('{0}')".format(message_ascii)
err_msg = "RuntimeError: {0}".format(message_ascii)
err_line = u("raise RuntimeError('{0}')").format(message_ascii)
err_msg = u("RuntimeError: {0}").format(message_ascii)
self.assertIn(("line %s" % lineno), stdout[1],
"Invalid line number: {0!r} instead of {1}".format(
@ -180,24 +184,26 @@ class SyntaxTracebackCases(testtools.TestCase):
"Invalid error message: {0!r} instead of {1!r}".format(
stdout[3], err_msg))
do_test("", "foo", "ascii", 3)
do_test("", "foo", "ascii", 3, output_encoding)
for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
if charset == "ascii":
text = "foo"
text = u("foo")
elif charset == "GBK":
text = "\u4E02\u5100"
text = u("\u4E02\u5100")
else:
text = "h\xe9 ho"
text = u("h\xe9 ho")
do_test("# coding: {0}\n".format(charset),
text, charset, 4)
text, charset, 4, output_encoding)
do_test("#!shebang\n# coding: {0}\n".format(charset),
text, charset, 5)
text, charset, 5, output_encoding)
do_test(" \t\f\n# coding: {0}\n".format(charset),
text, charset, 5)
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", "h\xe9 ho", 'utf-8', 5)
do_test(
"0\n# coding: GBK\n", u("h\xe9 ho"), 'utf-8', 5,
output_encoding)
class TracebackFormatTests(unittest.TestCase):
@ -207,6 +213,14 @@ class TracebackFormatTests(unittest.TestCase):
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
@ -217,8 +231,8 @@ class TracebackFormatTests(unittest.TestCase):
if cleanup_func is not None:
# Clear the inner frames, not this one
cleanup_func(tb.tb_next)
traceback_fmt = 'Traceback (most recent call last):\n' + \
''.join(traceback.format_tb(tb))
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_)
@ -323,6 +337,7 @@ class BaseExceptionReportingTests:
# < 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:
@ -337,6 +352,7 @@ class BaseExceptionReportingTests:
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:
@ -369,6 +385,7 @@ Traceback (most recent call last):
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.
@ -389,6 +406,7 @@ ZeroDivisionError
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: