'),
'<span>XXX & YYY<span>')
def test_getPrefix(self):
fmt = self._makeOne()
self.assertEqual(fmt.getPrefix(),
'Traceback (most recent call last):
\r\n')
def test_formatSupplementLine(self):
fmt = self._makeOne()
self.assertEqual(fmt.formatSupplementLine('XXX'), 'XXX')
def test_formatSupplementLine_w_markup(self):
fmt = self._makeOne()
self.assertEqual(fmt.formatSupplementLine('XXX & YYY'),
'XXX & YYY')
def test_formatSupplementInfo_simple(self):
INFO = 'Some days\nI wonder.'
fmt = self._makeOne()
self.assertEqual(fmt.formatSupplementInfo(INFO),
'Some days
\r\nI wonder.')
def test_formatSupplementInfo_w_markup(self):
INFO = 'Some days\nI wonder, Why?.'
fmt = self._makeOne()
self.assertEqual(fmt.formatSupplementInfo(INFO),
'Some days
\r\nI wonder, '
'<b>Why?</b>.')
def test_formatTracebackInfo(self):
fmt = self._makeOne()
self.assertEqual(fmt.formatTracebackInfo('XXX & YYY\nZZZ'),
'__traceback_info__: XXX & YYY
\r\nZZZ')
def test_formatLine_simple(self):
fmt = self._makeOne(with_filenames=True)
tb = DummyTB()
tb.tb_frame = f = DummyFrame()
result = fmt.formatLine(tb)
self.assertEqual(
result,
'- File "%s", line %d, in %s
'
% (f.f_code.co_filename,
tb.tb_lineno,
f.f_code.co_name,)
)
def test_formatLastLine(self):
fmt = self._makeOne()
self.assertEqual(fmt.formatLastLine('XXX'), '
XXX
')
class Test_format_exception(unittest.TestCase):
def _callFUT(self, as_html=False):
from zope.exceptions.exceptionformatter import format_exception
t, v, b = sys.exc_info()
try:
return ''.join(format_exception(t, v, b, as_html=as_html))
finally:
del b
def test_basic_names_text(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
# The traceback should include the name of this function.
self.assertTrue(s.find('test_basic_names_text') >= 0)
# The traceback should include the name of the exception.
self.assertTrue(s.find('ExceptionForTesting') >= 0)
def test_basic_names_html(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# The traceback should include the name of this function.
self.assertTrue(s.find('test_basic_names_html') >= 0)
# The traceback should include the name of the exception.
self.assertTrue(s.find('ExceptionForTesting') >= 0)
def test_traceback_info_text(self):
try:
__traceback_info__ = "Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
self.assertTrue(s.find('Adam & Eve') >= 0, s)
def test_traceback_info_html(self):
try:
__traceback_info__ = "Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# Be sure quoting is happening.
self.assertTrue(s.find('Adam & Eve') >= 0, s)
def test_traceback_info_is_tuple(self):
try:
__traceback_info__ = ("Adam", "Eve")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
self.assertTrue(s.find('Adam') >= 0, s)
self.assertTrue(s.find('Eve') >= 0, s)
def test_supplement_text(self, as_html=0):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(as_html)
# The source URL
self.assertTrue(s.find('/somepath') >= 0, s)
# The line number
self.assertTrue(s.find('634') >= 0, s)
# The column number
self.assertTrue(s.find('57') >= 0, s)
# The expression
self.assertTrue(s.find("You're one in a million") >= 0, s)
# The warning
self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
def test_supplement_html(self):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# The source URL
self.assertTrue(s.find('/somepath') >= 0, s)
# The line number
self.assertTrue(s.find('634') >= 0, s)
# The column number
self.assertTrue(s.find('57') >= 0, s)
# The expression
self.assertTrue(s.find("You're one in a million") >= 0, s)
# The warning
self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
def test_multiple_levels(self):
# Ensure many levels are shown in a traceback.
HOW_MANY = 10
def f(n):
"""Produces a (n + 1)-level traceback."""
__traceback_info__ = 'level%d' % n
if n > 0:
f(n - 1)
else:
raise ExceptionForTesting
try:
f(HOW_MANY)
except ExceptionForTesting:
s = self._callFUT(False)
for n in range(HOW_MANY+1):
self.assertTrue(s.find('level%d' % n) >= 0, s)
def test_quote_last_line(self):
class C(object):
pass
try:
raise TypeError(C())
except TypeError:
s = self._callFUT(True)
self.assertIn('<', s)
self.assertIn('>', s)
def test_multiline_exception(self):
try:
exec('syntax error\n')
except SyntaxError:
s = self._callFUT(False)
lines = s.splitlines()[-3:]
self.assertEqual(lines[0], ' syntax error')
self.assertIn(' ^', lines[1])
self.assertTrue(lines[2].startswith('SyntaxError: invalid syntax'),
lines[2])
def test_traceback_info_non_ascii(self):
__traceback_info__ = u"Have a Snowman: \u2603"
try:
raise TypeError()
except TypeError:
s = self._callFUT(True)
self.assertIsInstance(s, str)
self.assertIn('Have a Snowman', s)
def test_recursion_failure(self):
from zope.exceptions.exceptionformatter import TextExceptionFormatter
class FormatterException(Exception):
pass
class FailingFormatter(TextExceptionFormatter):
def formatLine(self, tb=None, f=None):
raise FormatterException("Formatter failed")
fmt = FailingFormatter()
try:
raise ExceptionForTesting
except ExceptionForTesting:
try:
fmt.formatException(*sys.exc_info())
except FormatterException:
s = self._callFUT(False)
# Recursion was detected
self.assertTrue('(Recursive formatException() stopped, '
'trying traceback.format_tb)' in s, s)
# and we fellback to the stdlib rather than hid the real error
self.assertEqual(s.splitlines()[-2],
' raise FormatterException("Formatter failed")')
self.assertTrue('FormatterException: Formatter failed'
in s.splitlines()[-1])
def test_format_exception_as_html(self):
# Test for format_exception (as_html=True)
import re
from textwrap import dedent
from zope.exceptions.exceptionformatter import format_exception
try:
exec('import')
except SyntaxError:
result = ''.join(format_exception(*sys.exc_info(), as_html=True))
expected = dedent("""\
Traceback (most recent call last):
- Module {module}, line ABC, in {fn}
exec('import')
File "<string>", line 1
import
^
SyntaxError: invalid syntax
""").format(
module='zope.exceptions.tests.test_exceptionformatter',
fn='test_format_exception_as_html',
)
if IS_PY39_OR_GREATER: # pragma: no cover
# Python 3.9 moves the pointer after the statement instead to the
# last character of it:
expected = expected.replace('^
', ' ^
')
# HTML formatter uses Windows line endings for some reason.
result = result.replace('\r\n', '\n')
result = re.sub(r'line \d\d\d,', 'line ABC,', result)
self.maxDiff = None
self.assertEqual(expected, result)
class Test_print_exception(unittest.TestCase):
def _callFUT(self, as_html=False):
import io
buf = io.StringIO() if bytes is not str else io.BytesIO()
from zope.exceptions.exceptionformatter import print_exception
t, v, b = sys.exc_info()
try:
print_exception(t, v, b, file=buf, as_html=as_html)
return buf.getvalue()
finally:
del b
def test_basic_names_text(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
# The traceback should include the name of this function.
self.assertTrue(s.find('test_basic_names_text') >= 0)
# The traceback should include the name of the exception.
self.assertTrue(s.find('ExceptionForTesting') >= 0)
def test_basic_names_html(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# The traceback should include the name of this function.
self.assertTrue(s.find('test_basic_names_html') >= 0)
# The traceback should include the name of the exception.
self.assertTrue(s.find('ExceptionForTesting') >= 0)
class Test_extract_stack(unittest.TestCase):
def _callFUT(self, as_html=False):
from zope.exceptions.exceptionformatter import extract_stack
f = sys.exc_info()[2].tb_frame
try:
return ''.join(extract_stack(f, as_html=as_html))
finally:
del f
def test_basic_names_as_text(self, as_html=0):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
# The stack trace should include the name of this function.
self.assertTrue(s.find('test_basic_names_as_text') >= 0)
def test_basic_names_as_html(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# The stack trace should include the name of this function.
self.assertTrue(s.find('test_basic_names_as_html') >= 0)
def test_traceback_info_text(self):
try:
__traceback_info__ = "Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
self.assertTrue(s.find('Adam & Eve') >= 0, s)
def test_traceback_info_html(self):
try:
__traceback_info__ = u"Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
self.assertTrue(s.find('Adam & Eve') >= 0, s)
def test_traceback_supplement_text(self):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
u"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
# The source URL
self.assertTrue(s.find('/somepath') >= 0, s)
# The line number
self.assertTrue(s.find('634') >= 0, s)
# The column number
self.assertTrue(s.find('57') >= 0, s)
# The expression
self.assertTrue(s.find("You're one in a million") >= 0, s)
# The warning
self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
def test_traceback_supplement_html(self):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
# The source URL
self.assertTrue(s.find('/somepath') >= 0, s)
# The line number
self.assertTrue(s.find('634') >= 0, s)
# The column number
self.assertTrue(s.find('57') >= 0, s)
# The expression
self.assertTrue(s.find("You're one in a million") >= 0, s)
# The warning
self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
def test_noinput(self):
try:
raise ExceptionForTesting
except ExceptionForTesting:
from zope.exceptions.exceptionformatter import extract_stack
s = ''.join(extract_stack())
self.assertTrue(s.find('test_noinput') >= 0)
class ExceptionForTesting(Exception):
pass
class TestingTracebackSupplement(object):
source_url = '/somepath'
line = 634
column = 57
warnings = ['Repent, for the end is nigh']
def __init__(self, expression):
self.expression = expression
class DummySupplement(object):
def __init__(self, info=''):
self._info = info
def getInfo(self):
return self._info
class DummyTB(object):
# https://docs.python.org/3/reference/datamodel.html#traceback-objects
tb_frame = None
tb_lineno = 14
tb_next = None
tb_lasti = 1
class DummyFrame(object):
f_lineno = 137
f_back = None
def __init__(self):
self.f_locals = {}
self.f_globals = {}
self.f_code = DummyCode()
class DummyCode(object):
co_filename = 'dummy/filename.py'
co_name = 'dummy_function'
def co_positions(self):
# New in Python 3.11.
# https://docs.python.org/3/reference/datamodel.html#codeobject.co_positions
# Note that this is not called for DummyTB if you have tb_lasti=-1.
# The 27 in the return value is chosen to match tb_recurse.tb_lineno=27
# in test_formatException_recursion_in_tb_stack in this file.
# The rest is random.
# Note that this code is only called on Python 3.11+, so we mark it for
# the coverage tool.
return [(27, 2, 3, 4)] # pragma: no cover
class _Monkey(object):
# context-manager for replacing module names in the scope of a test.
def __init__(self, module, **kw):
self.module = module
self.to_restore = {key: getattr(module, key, self)
for key in kw}
for key, value in kw.items():
setattr(module, key, value)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
for key, value in self.to_restore.items():
if value is not self: # pragma: no cover
setattr(self.module, key, value)
else:
delattr(self.module, key)
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)