|
|
# encoding: utf-8
|
|
|
"""Tests for IPython.core.ultratb
|
|
|
"""
|
|
|
import io
|
|
|
import logging
|
|
|
import sys
|
|
|
import os.path
|
|
|
from textwrap import dedent
|
|
|
import traceback
|
|
|
import unittest
|
|
|
from unittest import mock
|
|
|
|
|
|
import IPython.core.ultratb as ultratb
|
|
|
from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion
|
|
|
|
|
|
|
|
|
from IPython.testing import tools as tt
|
|
|
from IPython.testing.decorators import onlyif_unicode_paths
|
|
|
from IPython.utils.syspathcontext import prepended_to_syspath
|
|
|
from IPython.utils.tempdir import TemporaryDirectory
|
|
|
|
|
|
file_1 = """1
|
|
|
2
|
|
|
3
|
|
|
def f():
|
|
|
1/0
|
|
|
"""
|
|
|
|
|
|
file_2 = """def f():
|
|
|
1/0
|
|
|
"""
|
|
|
|
|
|
|
|
|
def recursionlimit(frames):
|
|
|
"""
|
|
|
decorator to set the recursion limit temporarily
|
|
|
"""
|
|
|
|
|
|
def inner(test_function):
|
|
|
def wrapper(*args, **kwargs):
|
|
|
_orig_rec_limit = ultratb._FRAME_RECURSION_LIMIT
|
|
|
ultratb._FRAME_RECURSION_LIMIT = 50
|
|
|
|
|
|
rl = sys.getrecursionlimit()
|
|
|
sys.setrecursionlimit(frames)
|
|
|
try:
|
|
|
return test_function(*args, **kwargs)
|
|
|
finally:
|
|
|
sys.setrecursionlimit(rl)
|
|
|
ultratb._FRAME_RECURSION_LIMIT = _orig_rec_limit
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
class ChangedPyFileTest(unittest.TestCase):
|
|
|
def test_changing_py_file(self):
|
|
|
"""Traceback produced if the line where the error occurred is missing?
|
|
|
|
|
|
https://github.com/ipython/ipython/issues/1456
|
|
|
"""
|
|
|
with TemporaryDirectory() as td:
|
|
|
fname = os.path.join(td, "foo.py")
|
|
|
with open(fname, "w") as f:
|
|
|
f.write(file_1)
|
|
|
|
|
|
with prepended_to_syspath(td):
|
|
|
ip.run_cell("import foo")
|
|
|
|
|
|
with tt.AssertPrints("ZeroDivisionError"):
|
|
|
ip.run_cell("foo.f()")
|
|
|
|
|
|
# Make the file shorter, so the line of the error is missing.
|
|
|
with open(fname, "w") as f:
|
|
|
f.write(file_2)
|
|
|
|
|
|
# For some reason, this was failing on the *second* call after
|
|
|
# changing the file, so we call f() twice.
|
|
|
with tt.AssertNotPrints("Internal Python error", channel='stderr'):
|
|
|
with tt.AssertPrints("ZeroDivisionError"):
|
|
|
ip.run_cell("foo.f()")
|
|
|
with tt.AssertPrints("ZeroDivisionError"):
|
|
|
ip.run_cell("foo.f()")
|
|
|
|
|
|
iso_8859_5_file = u'''# coding: iso-8859-5
|
|
|
|
|
|
def fail():
|
|
|
"""дбИЖ"""
|
|
|
1/0 # дбИЖ
|
|
|
'''
|
|
|
|
|
|
class NonAsciiTest(unittest.TestCase):
|
|
|
@onlyif_unicode_paths
|
|
|
def test_nonascii_path(self):
|
|
|
# Non-ascii directory name as well.
|
|
|
with TemporaryDirectory(suffix=u'é') as td:
|
|
|
fname = os.path.join(td, u"fooé.py")
|
|
|
with open(fname, "w") as f:
|
|
|
f.write(file_1)
|
|
|
|
|
|
with prepended_to_syspath(td):
|
|
|
ip.run_cell("import foo")
|
|
|
|
|
|
with tt.AssertPrints("ZeroDivisionError"):
|
|
|
ip.run_cell("foo.f()")
|
|
|
|
|
|
def test_iso8859_5(self):
|
|
|
with TemporaryDirectory() as td:
|
|
|
fname = os.path.join(td, 'dfghjkl.py')
|
|
|
|
|
|
with io.open(fname, 'w', encoding='iso-8859-5') as f:
|
|
|
f.write(iso_8859_5_file)
|
|
|
|
|
|
with prepended_to_syspath(td):
|
|
|
ip.run_cell("from dfghjkl import fail")
|
|
|
|
|
|
with tt.AssertPrints("ZeroDivisionError"):
|
|
|
with tt.AssertPrints(u'дбИЖ', suppress=False):
|
|
|
ip.run_cell('fail()')
|
|
|
|
|
|
def test_nonascii_msg(self):
|
|
|
cell = u"raise Exception('é')"
|
|
|
expected = u"Exception('é')"
|
|
|
ip.run_cell("%xmode plain")
|
|
|
with tt.AssertPrints(expected):
|
|
|
ip.run_cell(cell)
|
|
|
|
|
|
ip.run_cell("%xmode verbose")
|
|
|
with tt.AssertPrints(expected):
|
|
|
ip.run_cell(cell)
|
|
|
|
|
|
ip.run_cell("%xmode context")
|
|
|
with tt.AssertPrints(expected):
|
|
|
ip.run_cell(cell)
|
|
|
|
|
|
ip.run_cell("%xmode minimal")
|
|
|
with tt.AssertPrints(u"Exception: é"):
|
|
|
ip.run_cell(cell)
|
|
|
|
|
|
# Put this back into Context mode for later tests.
|
|
|
ip.run_cell("%xmode context")
|
|
|
|
|
|
class NestedGenExprTestCase(unittest.TestCase):
|
|
|
"""
|
|
|
Regression test for the following issues:
|
|
|
https://github.com/ipython/ipython/issues/8293
|
|
|
https://github.com/ipython/ipython/issues/8205
|
|
|
"""
|
|
|
def test_nested_genexpr(self):
|
|
|
code = dedent(
|
|
|
"""\
|
|
|
class SpecificException(Exception):
|
|
|
pass
|
|
|
|
|
|
def foo(x):
|
|
|
raise SpecificException("Success!")
|
|
|
|
|
|
sum(sum(foo(x) for _ in [0]) for x in [0])
|
|
|
"""
|
|
|
)
|
|
|
with tt.AssertPrints('SpecificException: Success!', suppress=False):
|
|
|
ip.run_cell(code)
|
|
|
|
|
|
|
|
|
indentationerror_file = """if True:
|
|
|
zoon()
|
|
|
"""
|
|
|
|
|
|
class IndentationErrorTest(unittest.TestCase):
|
|
|
def test_indentationerror_shows_line(self):
|
|
|
# See issue gh-2398
|
|
|
with tt.AssertPrints("IndentationError"):
|
|
|
with tt.AssertPrints("zoon()", suppress=False):
|
|
|
ip.run_cell(indentationerror_file)
|
|
|
|
|
|
with TemporaryDirectory() as td:
|
|
|
fname = os.path.join(td, "foo.py")
|
|
|
with open(fname, "w") as f:
|
|
|
f.write(indentationerror_file)
|
|
|
|
|
|
with tt.AssertPrints("IndentationError"):
|
|
|
with tt.AssertPrints("zoon()", suppress=False):
|
|
|
ip.magic('run %s' % fname)
|
|
|
|
|
|
se_file_1 = """1
|
|
|
2
|
|
|
7/
|
|
|
"""
|
|
|
|
|
|
se_file_2 = """7/
|
|
|
"""
|
|
|
|
|
|
class SyntaxErrorTest(unittest.TestCase):
|
|
|
def test_syntaxerror_without_lineno(self):
|
|
|
with tt.AssertNotPrints("TypeError"):
|
|
|
with tt.AssertPrints("line unknown"):
|
|
|
ip.run_cell("raise SyntaxError()")
|
|
|
|
|
|
def test_syntaxerror_no_stacktrace_at_compile_time(self):
|
|
|
syntax_error_at_compile_time = """
|
|
|
def foo():
|
|
|
..
|
|
|
"""
|
|
|
with tt.AssertPrints("SyntaxError"):
|
|
|
ip.run_cell(syntax_error_at_compile_time)
|
|
|
|
|
|
with tt.AssertNotPrints("foo()"):
|
|
|
ip.run_cell(syntax_error_at_compile_time)
|
|
|
|
|
|
def test_syntaxerror_stacktrace_when_running_compiled_code(self):
|
|
|
syntax_error_at_runtime = """
|
|
|
def foo():
|
|
|
eval("..")
|
|
|
|
|
|
def bar():
|
|
|
foo()
|
|
|
|
|
|
bar()
|
|
|
"""
|
|
|
with tt.AssertPrints("SyntaxError"):
|
|
|
ip.run_cell(syntax_error_at_runtime)
|
|
|
# Assert syntax error during runtime generate stacktrace
|
|
|
with tt.AssertPrints(["foo()", "bar()"]):
|
|
|
ip.run_cell(syntax_error_at_runtime)
|
|
|
del ip.user_ns['bar']
|
|
|
del ip.user_ns['foo']
|
|
|
|
|
|
def test_changing_py_file(self):
|
|
|
with TemporaryDirectory() as td:
|
|
|
fname = os.path.join(td, "foo.py")
|
|
|
with open(fname, 'w') as f:
|
|
|
f.write(se_file_1)
|
|
|
|
|
|
with tt.AssertPrints(["7/", "SyntaxError"]):
|
|
|
ip.magic("run " + fname)
|
|
|
|
|
|
# Modify the file
|
|
|
with open(fname, 'w') as f:
|
|
|
f.write(se_file_2)
|
|
|
|
|
|
# The SyntaxError should point to the correct line
|
|
|
with tt.AssertPrints(["7/", "SyntaxError"]):
|
|
|
ip.magic("run " + fname)
|
|
|
|
|
|
def test_non_syntaxerror(self):
|
|
|
# SyntaxTB may be called with an error other than a SyntaxError
|
|
|
# See e.g. gh-4361
|
|
|
try:
|
|
|
raise ValueError('QWERTY')
|
|
|
except ValueError:
|
|
|
with tt.AssertPrints('QWERTY'):
|
|
|
ip.showsyntaxerror()
|
|
|
|
|
|
|
|
|
class Python3ChainedExceptionsTest(unittest.TestCase):
|
|
|
DIRECT_CAUSE_ERROR_CODE = """
|
|
|
try:
|
|
|
x = 1 + 2
|
|
|
print(not_defined_here)
|
|
|
except Exception as e:
|
|
|
x += 55
|
|
|
x - 1
|
|
|
y = {}
|
|
|
raise KeyError('uh') from e
|
|
|
"""
|
|
|
|
|
|
EXCEPTION_DURING_HANDLING_CODE = """
|
|
|
try:
|
|
|
x = 1 + 2
|
|
|
print(not_defined_here)
|
|
|
except Exception as e:
|
|
|
x += 55
|
|
|
x - 1
|
|
|
y = {}
|
|
|
raise KeyError('uh')
|
|
|
"""
|
|
|
|
|
|
SUPPRESS_CHAINING_CODE = """
|
|
|
try:
|
|
|
1/0
|
|
|
except Exception:
|
|
|
raise ValueError("Yikes") from None
|
|
|
"""
|
|
|
|
|
|
def test_direct_cause_error(self):
|
|
|
with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
|
|
|
ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
|
|
|
|
|
|
def test_exception_during_handling_error(self):
|
|
|
with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
|
|
|
ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
|
|
|
|
|
|
def test_suppress_exception_chaining(self):
|
|
|
with tt.AssertNotPrints("ZeroDivisionError"), \
|
|
|
tt.AssertPrints("ValueError", suppress=False):
|
|
|
ip.run_cell(self.SUPPRESS_CHAINING_CODE)
|
|
|
|
|
|
def test_plain_direct_cause_error(self):
|
|
|
with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
|
|
|
ip.run_cell("%xmode Plain")
|
|
|
ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
|
|
|
ip.run_cell("%xmode Verbose")
|
|
|
|
|
|
def test_plain_exception_during_handling_error(self):
|
|
|
with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
|
|
|
ip.run_cell("%xmode Plain")
|
|
|
ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
|
|
|
ip.run_cell("%xmode Verbose")
|
|
|
|
|
|
def test_plain_suppress_exception_chaining(self):
|
|
|
with tt.AssertNotPrints("ZeroDivisionError"), \
|
|
|
tt.AssertPrints("ValueError", suppress=False):
|
|
|
ip.run_cell("%xmode Plain")
|
|
|
ip.run_cell(self.SUPPRESS_CHAINING_CODE)
|
|
|
ip.run_cell("%xmode Verbose")
|
|
|
|
|
|
|
|
|
class RecursionTest(unittest.TestCase):
|
|
|
DEFINITIONS = """
|
|
|
def non_recurs():
|
|
|
1/0
|
|
|
|
|
|
def r1():
|
|
|
r1()
|
|
|
|
|
|
def r3a():
|
|
|
r3b()
|
|
|
|
|
|
def r3b():
|
|
|
r3c()
|
|
|
|
|
|
def r3c():
|
|
|
r3a()
|
|
|
|
|
|
def r3o1():
|
|
|
r3a()
|
|
|
|
|
|
def r3o2():
|
|
|
r3o1()
|
|
|
"""
|
|
|
def setUp(self):
|
|
|
ip.run_cell(self.DEFINITIONS)
|
|
|
|
|
|
def test_no_recursion(self):
|
|
|
with tt.AssertNotPrints("frames repeated"):
|
|
|
ip.run_cell("non_recurs()")
|
|
|
|
|
|
@recursionlimit(150)
|
|
|
def test_recursion_one_frame(self):
|
|
|
with tt.AssertPrints("1 frames repeated"):
|
|
|
ip.run_cell("r1()")
|
|
|
|
|
|
@recursionlimit(150)
|
|
|
def test_recursion_three_frames(self):
|
|
|
with tt.AssertPrints("3 frames repeated"):
|
|
|
ip.run_cell("r3o2()")
|
|
|
|
|
|
@recursionlimit(150)
|
|
|
def test_find_recursion(self):
|
|
|
captured = []
|
|
|
def capture_exc(*args, **kwargs):
|
|
|
captured.append(sys.exc_info())
|
|
|
with mock.patch.object(ip, 'showtraceback', capture_exc):
|
|
|
ip.run_cell("r3o2()")
|
|
|
|
|
|
self.assertEqual(len(captured), 1)
|
|
|
etype, evalue, tb = captured[0]
|
|
|
self.assertIn("recursion", str(evalue))
|
|
|
|
|
|
records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
|
|
|
for r in records[:10]:
|
|
|
print(r[1:4])
|
|
|
|
|
|
# The outermost frames should be:
|
|
|
# 0: the 'cell' that was running when the exception came up
|
|
|
# 1: r3o2()
|
|
|
# 2: r3o1()
|
|
|
# 3: r3a()
|
|
|
# Then repeating r3b, r3c, r3a
|
|
|
last_unique, repeat_length = find_recursion(etype, evalue, records)
|
|
|
self.assertEqual(last_unique, 2)
|
|
|
self.assertEqual(repeat_length, 3)
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
# module testing (minimal)
|
|
|
def test_handlers():
|
|
|
def spam(c, d_e):
|
|
|
(d, e) = d_e
|
|
|
x = c + d
|
|
|
y = c * d
|
|
|
foo(x, y)
|
|
|
|
|
|
def foo(a, b, bar=1):
|
|
|
eggs(a, b + bar)
|
|
|
|
|
|
def eggs(f, g, z=globals()):
|
|
|
h = f + g
|
|
|
i = f - g
|
|
|
return h / i
|
|
|
|
|
|
buff = io.StringIO()
|
|
|
|
|
|
buff.write('')
|
|
|
buff.write('*** Before ***')
|
|
|
try:
|
|
|
buff.write(spam(1, (2, 3)))
|
|
|
except:
|
|
|
traceback.print_exc(file=buff)
|
|
|
|
|
|
handler = ColorTB(ostream=buff)
|
|
|
buff.write('*** ColorTB ***')
|
|
|
try:
|
|
|
buff.write(spam(1, (2, 3)))
|
|
|
except:
|
|
|
handler(*sys.exc_info())
|
|
|
buff.write('')
|
|
|
|
|
|
handler = VerboseTB(ostream=buff)
|
|
|
buff.write('*** VerboseTB ***')
|
|
|
try:
|
|
|
buff.write(spam(1, (2, 3)))
|
|
|
except:
|
|
|
handler(*sys.exc_info())
|
|
|
buff.write('')
|
|
|
|
|
|
from IPython.testing.decorators import skipif
|
|
|
|
|
|
class TokenizeFailureTest(unittest.TestCase):
|
|
|
"""Tests related to https://github.com/ipython/ipython/issues/6864."""
|
|
|
|
|
|
# that appear to test that we are handling an exception that can be thrown
|
|
|
# by the tokenizer due to a bug that seem to have been fixed in 3.8, though
|
|
|
# I'm unsure if other sequences can make it raise this error. Let's just
|
|
|
# skip in 3.8 for now
|
|
|
@skipif(sys.version_info > (3,8))
|
|
|
def testLogging(self):
|
|
|
message = "An unexpected error occurred while tokenizing input"
|
|
|
cell = 'raise ValueError("""a\nb""")'
|
|
|
|
|
|
stream = io.StringIO()
|
|
|
handler = logging.StreamHandler(stream)
|
|
|
logger = logging.getLogger()
|
|
|
loglevel = logger.level
|
|
|
logger.addHandler(handler)
|
|
|
self.addCleanup(lambda: logger.removeHandler(handler))
|
|
|
self.addCleanup(lambda: logger.setLevel(loglevel))
|
|
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
with tt.AssertNotPrints(message):
|
|
|
ip.run_cell(cell)
|
|
|
self.assertNotIn(message, stream.getvalue())
|
|
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
with tt.AssertNotPrints(message):
|
|
|
ip.run_cell(cell)
|
|
|
self.assertIn(message, stream.getvalue())
|
|
|
|