test_ultratb.py
440 lines
| 12.1 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r8326 | # encoding: utf-8 | ||
Thomas Kluyver
|
r8099 | """Tests for IPython.core.ultratb | ||
""" | ||||
Thomas Kluyver
|
r8326 | import io | ||
Craig Citro
|
r23973 | import logging | ||
Matthias Bussonnier
|
r21789 | import sys | ||
Thomas Kluyver
|
r8099 | import os.path | ||
Scott Sanderson
|
r21719 | from textwrap import dedent | ||
Matthias Bussonnier
|
r21789 | import traceback | ||
Thomas Kluyver
|
r8099 | import unittest | ||
Srinivas Reddy Thatiparthy
|
r23064 | from unittest import mock | ||
Thomas Kluyver
|
r21983 | |||
Matthias Bussonnier
|
r25073 | import IPython.core.ultratb as ultratb | ||
from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion | ||||
Matthias Bussonnier
|
r21789 | |||
Scott Sanderson
|
r21719 | |||
Thomas Kluyver
|
r8099 | from IPython.testing import tools as tt | ||
Thomas Kluyver
|
r12167 | from IPython.testing.decorators import onlyif_unicode_paths | ||
Thomas Kluyver
|
r8099 | 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 | ||||
""" | ||||
Matthias Bussonnier
|
r25073 | |||
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 | ||||
Matthias Bussonnier
|
r25115 | ultratb._FRAME_RECURSION_LIMIT = 50 | ||
Matthias Bussonnier
|
r25073 | |||
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 | ||||
Thomas Kluyver
|
r8099 | 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()") | ||||
Thomas Kluyver
|
r8326 | |||
iso_8859_5_file = u'''# coding: iso-8859-5 | ||||
def fail(): | ||||
"""дбИЖ""" | ||||
1/0 # дбИЖ | ||||
''' | ||||
class NonAsciiTest(unittest.TestCase): | ||||
Thomas Kluyver
|
r12167 | @onlyif_unicode_paths | ||
def test_nonascii_path(self): | ||||
Thomas Kluyver
|
r8326 | # Non-ascii directory name as well. | ||
with TemporaryDirectory(suffix=u'é') as td: | ||||
Thomas Kluyver
|
r12167 | 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: | ||||
Thomas Kluyver
|
r8326 | 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()') | ||||
Min RK
|
r22268 | |||
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) | ||||
Thomas Kluyver
|
r8416 | |||
Dan Allan
|
r24849 | ip.run_cell("%xmode minimal") | ||
with tt.AssertPrints(u"Exception: é"): | ||||
ip.run_cell(cell) | ||||
Scott Sanderson
|
r21719 | |||
Dan Allan
|
r24852 | # Put this back into Context mode for later tests. | ||
ip.run_cell("%xmode context") | ||||
Scott Sanderson
|
r21719 | 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) | ||||
Thomas Kluyver
|
r8416 | 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) | ||||
Jez Ng
|
r8589 | |||
Thomas Kluyver
|
r12543 | se_file_1 = """1 | ||
2 | ||||
7/ | ||||
""" | ||||
se_file_2 = """7/ | ||||
""" | ||||
Jez Ng
|
r8589 | class SyntaxErrorTest(unittest.TestCase): | ||
def test_syntaxerror_without_lineno(self): | ||||
with tt.AssertNotPrints("TypeError"): | ||||
with tt.AssertPrints("line unknown"): | ||||
ip.run_cell("raise SyntaxError()") | ||||
Thomas Kluyver
|
r12543 | |||
Piotr Zielinski
|
r23611 | 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) | ||||
Matthias Bussonnier
|
r25073 | del ip.user_ns['bar'] | ||
del ip.user_ns['foo'] | ||||
Piotr Zielinski
|
r23611 | |||
Thomas Kluyver
|
r12543 | 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) | ||||
Thomas Kluyver
|
r12950 | |||
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() | ||||
Justyna Ilczuk
|
r17161 | |||
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') | ||||
""" | ||||
Thomas Kluyver
|
r21519 | SUPPRESS_CHAINING_CODE = """ | ||
try: | ||||
1/0 | ||||
except Exception: | ||||
raise ValueError("Yikes") from None | ||||
""" | ||||
Justyna Ilczuk
|
r17161 | def test_direct_cause_error(self): | ||
Srinivas Reddy Thatiparthy
|
r23081 | with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): | ||
ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) | ||||
Justyna Ilczuk
|
r17161 | |||
def test_exception_during_handling_error(self): | ||||
Srinivas Reddy Thatiparthy
|
r23081 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | ||
ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) | ||||
Thomas Kluyver
|
r21519 | |||
def test_suppress_exception_chaining(self): | ||||
Srinivas Reddy Thatiparthy
|
r23081 | with tt.AssertNotPrints("ZeroDivisionError"), \ | ||
tt.AssertPrints("ValueError", suppress=False): | ||||
ip.run_cell(self.SUPPRESS_CHAINING_CODE) | ||||
Matthias Bussonnier
|
r21789 | |||
Thomas Kluyver
|
r21983 | 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()") | ||||
Matthias Bussonnier
|
r25115 | @recursionlimit(150) | ||
Thomas Kluyver
|
r21983 | def test_recursion_one_frame(self): | ||
with tt.AssertPrints("1 frames repeated"): | ||||
ip.run_cell("r1()") | ||||
Matthias Bussonnier
|
r25115 | @recursionlimit(150) | ||
Thomas Kluyver
|
r21983 | def test_recursion_three_frames(self): | ||
with tt.AssertPrints("3 frames repeated"): | ||||
ip.run_cell("r3o2()") | ||||
Matthias Bussonnier
|
r25115 | @recursionlimit(150) | ||
Thomas Kluyver
|
r21983 | 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) | ||||
Matthias Bussonnier
|
r21789 | #---------------------------------------------------------------------------- | ||
# module testing (minimal) | ||||
Paul Ivanov
|
r22959 | 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('') | ||||
Matthias Bussonnier
|
r21789 | |||
Matthias Bussonnier
|
r24926 | from IPython.testing.decorators import skipif | ||
Craig Citro
|
r23973 | |||
class TokenizeFailureTest(unittest.TestCase): | ||||
"""Tests related to https://github.com/ipython/ipython/issues/6864.""" | ||||
Matthias Bussonnier
|
r24932 | # 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 | ||||
Matthias Bussonnier
|
r24926 | @skipif(sys.version_info > (3,8)) | ||
Craig Citro
|
r23973 | 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()) | ||||