File: test_ultratb.py

package info (click to toggle)
ipython 7.20.0-1%2Bdeb11u1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 11,100 kB
  • sloc: python: 36,813; sh: 379; makefile: 243
file content (470 lines) | stat: -rw-r--r-- 13,550 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# 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()

import sys
if sys.version_info < (3,9):
    """
    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
            with tt.AssertPrints("MemoryError"):
                ip.run_cell(memoryerror_code)


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())