test_ultratb.py
457 lines
| 12.4 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r8326 | # encoding: utf-8 | ||
Thomas Kluyver
|
r8099 | """Tests for IPython.core.ultratb | ||
""" | ||||
Thomas Kluyver
|
r8326 | import io | ||
Matthias Bussonnier
|
r27509 | import os.path | ||
Nikita Kniazev
|
r27235 | import platform | ||
Alex Hall
|
r25474 | import re | ||
Matthias Bussonnier
|
r21789 | import sys | ||
import traceback | ||||
Thomas Kluyver
|
r8099 | import unittest | ||
Matthias Bussonnier
|
r27509 | from textwrap import dedent | ||
Thomas Kluyver
|
r21983 | |||
Matthias Bussonnier
|
r27509 | from tempfile import TemporaryDirectory | ||
Scott Sanderson
|
r21719 | |||
Matthias Bussonnier
|
r27509 | from IPython.core.ultratb import ColorTB, VerboseTB | ||
Thomas Kluyver
|
r8099 | from IPython.testing import tools as tt | ||
Matthias Bussonnier
|
r28653 | from IPython.testing.decorators import onlyif_unicode_paths, skip_without | ||
Thomas Kluyver
|
r8099 | from IPython.utils.syspathcontext import prepended_to_syspath | ||
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): | ||||
rl = sys.getrecursionlimit() | ||||
sys.setrecursionlimit(frames) | ||||
try: | ||||
return test_function(*args, **kwargs) | ||||
finally: | ||||
sys.setrecursionlimit(rl) | ||||
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? | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8099 | https://github.com/ipython/ipython/issues/1456 | ||
""" | ||||
with TemporaryDirectory() as td: | ||||
fname = os.path.join(td, "foo.py") | ||||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r8099 | f.write(file_1) | ||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8099 | with prepended_to_syspath(td): | ||
ip.run_cell("import foo") | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8099 | with tt.AssertPrints("ZeroDivisionError"): | ||
ip.run_cell("foo.f()") | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8099 | # Make the file shorter, so the line of the error is missing. | ||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r8099 | f.write(file_2) | ||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8099 | # 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") | ||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r12167 | f.write(file_1) | ||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r12167 | with prepended_to_syspath(td): | ||
ip.run_cell("import foo") | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r12167 | with tt.AssertPrints("ZeroDivisionError"): | ||
ip.run_cell("foo.f()") | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r12167 | 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) | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8326 | with prepended_to_syspath(td): | ||
ip.run_cell("from dfghjkl import fail") | ||||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8326 | with tt.AssertPrints("ZeroDivisionError"): | ||
with tt.AssertPrints(u'дбИЖ', suppress=False): | ||||
ip.run_cell('fail()') | ||||
EtiennePelletier
|
r28278 | |||
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: | ||
Andrew Kreimer
|
r28888 | zoom() | ||
Thomas Kluyver
|
r8416 | """ | ||
class IndentationErrorTest(unittest.TestCase): | ||||
def test_indentationerror_shows_line(self): | ||||
# See issue gh-2398 | ||||
with tt.AssertPrints("IndentationError"): | ||||
Andrew Kreimer
|
r28888 | with tt.AssertPrints("zoom()", suppress=False): | ||
Thomas Kluyver
|
r8416 | ip.run_cell(indentationerror_file) | ||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8416 | with TemporaryDirectory() as td: | ||
fname = os.path.join(td, "foo.py") | ||||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r8416 | f.write(indentationerror_file) | ||
EtiennePelletier
|
r28278 | |||
Thomas Kluyver
|
r8416 | with tt.AssertPrints("IndentationError"): | ||
Andrew Kreimer
|
r28888 | with tt.AssertPrints("zoom()", suppress=False): | ||
M Bussonnier
|
r28944 | ip.run_line_magic("run", fname) | ||
Jez Ng
|
r8589 | |||
Matthias Bussonnier
|
r28653 | @skip_without("pandas") | ||
def test_dynamic_code(): | ||||
code = """ | ||||
import pandas | ||||
df = pandas.DataFrame([]) | ||||
# Important: only fails inside of an "exec" call: | ||||
exec("df.foobarbaz()") | ||||
""" | ||||
with tt.AssertPrints("Could not get source"): | ||||
ip.run_cell(code) | ||||
Thomas Kluyver
|
r12543 | se_file_1 = """1 | ||
2 | ||||
7/ | ||||
""" | ||||
se_file_2 = """7/ | ||||
""" | ||||
Jez Ng
|
r8589 | class SyntaxErrorTest(unittest.TestCase): | ||
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") | ||||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r12543 | f.write(se_file_1) | ||
with tt.AssertPrints(["7/", "SyntaxError"]): | ||||
M Bussonnier
|
r28944 | ip.run_line_magic("run", fname) | ||
Thomas Kluyver
|
r12543 | |||
# Modify the file | ||||
gousaiyang
|
r27495 | with open(fname, "w", encoding="utf-8") as f: | ||
Thomas Kluyver
|
r12543 | f.write(se_file_2) | ||
# The SyntaxError should point to the correct line | ||||
with tt.AssertPrints(["7/", "SyntaxError"]): | ||||
M Bussonnier
|
r28944 | ip.run_line_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 | |||
Matthias Bussonnier
|
r25719 | import sys | ||
Nikita Kniazev
|
r27235 | |||
Matthias Bussonnier
|
r28219 | if platform.python_implementation() != "PyPy": | ||
Matthias Bussonnier
|
r25719 | """ | ||
New 3.9 Pgen Parser does not raise Memory error, except on failed malloc. | ||||
""" | ||||
class MemoryErrorTest(unittest.TestCase): | ||||
def test_memoryerror(self): | ||||
memoryerror_code = "(" * 200 + ")" * 200 | ||||
Matthias Bussonnier
|
r28219 | ip.run_cell(memoryerror_code) | ||
yangyang
|
r25430 | |||
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 | ||||
""" | ||||
Thomas Kluyver
|
r28824 | SYS_EXIT_WITH_CONTEXT_CODE = """ | ||
try: | ||||
1/0 | ||||
except Exception as e: | ||||
raise SystemExit(1) | ||||
""" | ||||
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 | |||
Thomas Kluyver
|
r28824 | def test_sysexit_while_handling_error(self): | ||
with tt.AssertPrints(["SystemExit", "to see the full traceback"]): | ||||
with tt.AssertNotPrints(["another exception"], suppress=False): | ||||
ip.run_cell(self.SYS_EXIT_WITH_CONTEXT_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 | |||
Quentin Peter
|
r25305 | 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): | ||||
Quentin Peter
|
r25302 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | ||
ip.run_cell("%xmode Plain") | ||||
ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) | ||||
Quentin Peter
|
r25304 | ip.run_cell("%xmode Verbose") | ||
Quentin Peter
|
r25302 | |||
Quentin Peter
|
r25305 | 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") | ||||
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): | ||||
Alex Hall
|
r25469 | with tt.AssertNotPrints("skipping similar frames"): | ||
Thomas Kluyver
|
r21983 | ip.run_cell("non_recurs()") | ||
Alex Hall
|
r25493 | @recursionlimit(200) | ||
Thomas Kluyver
|
r21983 | def test_recursion_one_frame(self): | ||
Alex Hall
|
r25474 | with tt.AssertPrints(re.compile( | ||
Alex Hall
|
r25493 | r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]") | ||
Alex Hall
|
r25474 | ): | ||
Thomas Kluyver
|
r21983 | ip.run_cell("r1()") | ||
Nikita Kniazev
|
r27235 | @recursionlimit(160) | ||
Thomas Kluyver
|
r21983 | def test_recursion_three_frames(self): | ||
Alex Hall
|
r25475 | with tt.AssertPrints("[... skipping similar frames: "), \ | ||
tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ | ||||
tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \ | ||||
tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False): | ||||
Thomas Kluyver
|
r21983 | ip.run_cell("r3o2()") | ||
Zac Hatfield-Dodds
|
r28277 | class PEP678NotesReportingTest(unittest.TestCase): | ||
ERROR_WITH_NOTE = """ | ||||
try: | ||||
raise AssertionError("Message") | ||||
except Exception as e: | ||||
try: | ||||
e.add_note("This is a PEP-678 note.") | ||||
except AttributeError: # Python <= 3.10 | ||||
e.__notes__ = ("This is a PEP-678 note.",) | ||||
raise | ||||
""" | ||||
def test_verbose_reports_notes(self): | ||||
with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): | ||||
ip.run_cell(self.ERROR_WITH_NOTE) | ||||
def test_plain_reports_notes(self): | ||||
with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): | ||||
ip.run_cell("%xmode Plain") | ||||
ip.run_cell(self.ERROR_WITH_NOTE) | ||||
ip.run_cell("%xmode Verbose") | ||||
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('') | ||||