##// END OF EJS Templates
Fix test_ultratb (particularly recursion-related tests)
Alex Hall -
Show More
@@ -1,466 +1,439
1 1 # encoding: utf-8
2 2 """Tests for IPython.core.ultratb
3 3 """
4 4 import io
5 5 import logging
6 6 import sys
7 7 import os.path
8 8 from textwrap import dedent
9 9 import traceback
10 10 import unittest
11 from unittest import mock
12 11
13 import IPython.core.ultratb as ultratb
14 from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion
12 from IPython.core.ultratb import ColorTB, VerboseTB
15 13
16 14
17 15 from IPython.testing import tools as tt
18 16 from IPython.testing.decorators import onlyif_unicode_paths
19 17 from IPython.utils.syspathcontext import prepended_to_syspath
20 18 from IPython.utils.tempdir import TemporaryDirectory
21 19
22 20 file_1 = """1
23 21 2
24 22 3
25 23 def f():
26 24 1/0
27 25 """
28 26
29 27 file_2 = """def f():
30 28 1/0
31 29 """
32 30
33 31
34 32 def recursionlimit(frames):
35 33 """
36 34 decorator to set the recursion limit temporarily
37 35 """
38 36
39 37 def inner(test_function):
40 38 def wrapper(*args, **kwargs):
41 _orig_rec_limit = ultratb._FRAME_RECURSION_LIMIT
42 ultratb._FRAME_RECURSION_LIMIT = 50
43
44 39 rl = sys.getrecursionlimit()
45 40 sys.setrecursionlimit(frames)
46 41 try:
47 42 return test_function(*args, **kwargs)
48 43 finally:
49 44 sys.setrecursionlimit(rl)
50 ultratb._FRAME_RECURSION_LIMIT = _orig_rec_limit
51 45
52 46 return wrapper
53 47
54 48 return inner
55 49
56 50
57 51 class ChangedPyFileTest(unittest.TestCase):
58 52 def test_changing_py_file(self):
59 53 """Traceback produced if the line where the error occurred is missing?
60 54
61 55 https://github.com/ipython/ipython/issues/1456
62 56 """
63 57 with TemporaryDirectory() as td:
64 58 fname = os.path.join(td, "foo.py")
65 59 with open(fname, "w") as f:
66 60 f.write(file_1)
67 61
68 62 with prepended_to_syspath(td):
69 63 ip.run_cell("import foo")
70 64
71 65 with tt.AssertPrints("ZeroDivisionError"):
72 66 ip.run_cell("foo.f()")
73 67
74 68 # Make the file shorter, so the line of the error is missing.
75 69 with open(fname, "w") as f:
76 70 f.write(file_2)
77 71
78 72 # For some reason, this was failing on the *second* call after
79 73 # changing the file, so we call f() twice.
80 74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
81 75 with tt.AssertPrints("ZeroDivisionError"):
82 76 ip.run_cell("foo.f()")
83 77 with tt.AssertPrints("ZeroDivisionError"):
84 78 ip.run_cell("foo.f()")
85 79
86 80 iso_8859_5_file = u'''# coding: iso-8859-5
87 81
88 82 def fail():
89 83 """Π΄Π±Π˜Π–"""
90 84 1/0 # Π΄Π±Π˜Π–
91 85 '''
92 86
93 87 class NonAsciiTest(unittest.TestCase):
94 88 @onlyif_unicode_paths
95 89 def test_nonascii_path(self):
96 90 # Non-ascii directory name as well.
97 91 with TemporaryDirectory(suffix=u'Γ©') as td:
98 92 fname = os.path.join(td, u"fooΓ©.py")
99 93 with open(fname, "w") as f:
100 94 f.write(file_1)
101 95
102 96 with prepended_to_syspath(td):
103 97 ip.run_cell("import foo")
104 98
105 99 with tt.AssertPrints("ZeroDivisionError"):
106 100 ip.run_cell("foo.f()")
107 101
108 102 def test_iso8859_5(self):
109 103 with TemporaryDirectory() as td:
110 104 fname = os.path.join(td, 'dfghjkl.py')
111 105
112 106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
113 107 f.write(iso_8859_5_file)
114 108
115 109 with prepended_to_syspath(td):
116 110 ip.run_cell("from dfghjkl import fail")
117 111
118 112 with tt.AssertPrints("ZeroDivisionError"):
119 113 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
120 114 ip.run_cell('fail()')
121 115
122 116 def test_nonascii_msg(self):
123 117 cell = u"raise Exception('Γ©')"
124 118 expected = u"Exception('Γ©')"
125 119 ip.run_cell("%xmode plain")
126 120 with tt.AssertPrints(expected):
127 121 ip.run_cell(cell)
128 122
129 123 ip.run_cell("%xmode verbose")
130 124 with tt.AssertPrints(expected):
131 125 ip.run_cell(cell)
132 126
133 127 ip.run_cell("%xmode context")
134 128 with tt.AssertPrints(expected):
135 129 ip.run_cell(cell)
136 130
137 131 ip.run_cell("%xmode minimal")
138 132 with tt.AssertPrints(u"Exception: Γ©"):
139 133 ip.run_cell(cell)
140 134
141 135 # Put this back into Context mode for later tests.
142 136 ip.run_cell("%xmode context")
143 137
144 138 class NestedGenExprTestCase(unittest.TestCase):
145 139 """
146 140 Regression test for the following issues:
147 141 https://github.com/ipython/ipython/issues/8293
148 142 https://github.com/ipython/ipython/issues/8205
149 143 """
150 144 def test_nested_genexpr(self):
151 145 code = dedent(
152 146 """\
153 147 class SpecificException(Exception):
154 148 pass
155 149
156 150 def foo(x):
157 151 raise SpecificException("Success!")
158 152
159 153 sum(sum(foo(x) for _ in [0]) for x in [0])
160 154 """
161 155 )
162 156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
163 157 ip.run_cell(code)
164 158
165 159
166 160 indentationerror_file = """if True:
167 161 zoon()
168 162 """
169 163
170 164 class IndentationErrorTest(unittest.TestCase):
171 165 def test_indentationerror_shows_line(self):
172 166 # See issue gh-2398
173 167 with tt.AssertPrints("IndentationError"):
174 168 with tt.AssertPrints("zoon()", suppress=False):
175 169 ip.run_cell(indentationerror_file)
176 170
177 171 with TemporaryDirectory() as td:
178 172 fname = os.path.join(td, "foo.py")
179 173 with open(fname, "w") as f:
180 174 f.write(indentationerror_file)
181 175
182 176 with tt.AssertPrints("IndentationError"):
183 177 with tt.AssertPrints("zoon()", suppress=False):
184 178 ip.magic('run %s' % fname)
185 179
186 180 se_file_1 = """1
187 181 2
188 182 7/
189 183 """
190 184
191 185 se_file_2 = """7/
192 186 """
193 187
194 188 class SyntaxErrorTest(unittest.TestCase):
195 189 def test_syntaxerror_without_lineno(self):
196 190 with tt.AssertNotPrints("TypeError"):
197 191 with tt.AssertPrints("line unknown"):
198 192 ip.run_cell("raise SyntaxError()")
199 193
200 194 def test_syntaxerror_no_stacktrace_at_compile_time(self):
201 195 syntax_error_at_compile_time = """
202 196 def foo():
203 197 ..
204 198 """
205 199 with tt.AssertPrints("SyntaxError"):
206 200 ip.run_cell(syntax_error_at_compile_time)
207 201
208 202 with tt.AssertNotPrints("foo()"):
209 203 ip.run_cell(syntax_error_at_compile_time)
210 204
211 205 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
212 206 syntax_error_at_runtime = """
213 207 def foo():
214 208 eval("..")
215 209
216 210 def bar():
217 211 foo()
218 212
219 213 bar()
220 214 """
221 215 with tt.AssertPrints("SyntaxError"):
222 216 ip.run_cell(syntax_error_at_runtime)
223 217 # Assert syntax error during runtime generate stacktrace
224 218 with tt.AssertPrints(["foo()", "bar()"]):
225 219 ip.run_cell(syntax_error_at_runtime)
226 220 del ip.user_ns['bar']
227 221 del ip.user_ns['foo']
228 222
229 223 def test_changing_py_file(self):
230 224 with TemporaryDirectory() as td:
231 225 fname = os.path.join(td, "foo.py")
232 226 with open(fname, 'w') as f:
233 227 f.write(se_file_1)
234 228
235 229 with tt.AssertPrints(["7/", "SyntaxError"]):
236 230 ip.magic("run " + fname)
237 231
238 232 # Modify the file
239 233 with open(fname, 'w') as f:
240 234 f.write(se_file_2)
241 235
242 236 # The SyntaxError should point to the correct line
243 237 with tt.AssertPrints(["7/", "SyntaxError"]):
244 238 ip.magic("run " + fname)
245 239
246 240 def test_non_syntaxerror(self):
247 241 # SyntaxTB may be called with an error other than a SyntaxError
248 242 # See e.g. gh-4361
249 243 try:
250 244 raise ValueError('QWERTY')
251 245 except ValueError:
252 246 with tt.AssertPrints('QWERTY'):
253 247 ip.showsyntaxerror()
254 248
255 249
256 250 class MemoryErrorTest(unittest.TestCase):
257 251 def test_memoryerror(self):
258 252 memoryerror_code = "(" * 200 + ")" * 200
259 253 with tt.AssertPrints("MemoryError"):
260 254 ip.run_cell(memoryerror_code)
261 255
262 256
263 257 class Python3ChainedExceptionsTest(unittest.TestCase):
264 258 DIRECT_CAUSE_ERROR_CODE = """
265 259 try:
266 260 x = 1 + 2
267 261 print(not_defined_here)
268 262 except Exception as e:
269 263 x += 55
270 264 x - 1
271 265 y = {}
272 266 raise KeyError('uh') from e
273 267 """
274 268
275 269 EXCEPTION_DURING_HANDLING_CODE = """
276 270 try:
277 271 x = 1 + 2
278 272 print(not_defined_here)
279 273 except Exception as e:
280 274 x += 55
281 275 x - 1
282 276 y = {}
283 277 raise KeyError('uh')
284 278 """
285 279
286 280 SUPPRESS_CHAINING_CODE = """
287 281 try:
288 282 1/0
289 283 except Exception:
290 284 raise ValueError("Yikes") from None
291 285 """
292 286
293 287 def test_direct_cause_error(self):
294 288 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
295 289 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
296 290
297 291 def test_exception_during_handling_error(self):
298 292 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
299 293 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
300 294
301 295 def test_suppress_exception_chaining(self):
302 296 with tt.AssertNotPrints("ZeroDivisionError"), \
303 297 tt.AssertPrints("ValueError", suppress=False):
304 298 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
305 299
306 300 def test_plain_direct_cause_error(self):
307 301 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
308 302 ip.run_cell("%xmode Plain")
309 303 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
310 304 ip.run_cell("%xmode Verbose")
311 305
312 306 def test_plain_exception_during_handling_error(self):
313 307 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
314 308 ip.run_cell("%xmode Plain")
315 309 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
316 310 ip.run_cell("%xmode Verbose")
317 311
318 312 def test_plain_suppress_exception_chaining(self):
319 313 with tt.AssertNotPrints("ZeroDivisionError"), \
320 314 tt.AssertPrints("ValueError", suppress=False):
321 315 ip.run_cell("%xmode Plain")
322 316 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
323 317 ip.run_cell("%xmode Verbose")
324 318
325 319
326 320 class RecursionTest(unittest.TestCase):
327 321 DEFINITIONS = """
328 322 def non_recurs():
329 323 1/0
330 324
331 325 def r1():
332 326 r1()
333 327
334 328 def r3a():
335 329 r3b()
336 330
337 331 def r3b():
338 332 r3c()
339 333
340 334 def r3c():
341 335 r3a()
342 336
343 337 def r3o1():
344 338 r3a()
345 339
346 340 def r3o2():
347 341 r3o1()
348 342 """
349 343 def setUp(self):
350 344 ip.run_cell(self.DEFINITIONS)
351 345
352 346 def test_no_recursion(self):
353 with tt.AssertNotPrints("frames repeated"):
347 with tt.AssertNotPrints("skipping similar frames"):
354 348 ip.run_cell("non_recurs()")
355 349
356 350 @recursionlimit(150)
357 351 def test_recursion_one_frame(self):
358 with tt.AssertPrints("1 frames repeated"):
352 with tt.AssertPrints("[... skipping similar frames: r1 at line 5 (95 times)]"):
359 353 ip.run_cell("r1()")
360 354
361 355 @recursionlimit(150)
362 356 def test_recursion_three_frames(self):
363 with tt.AssertPrints("3 frames repeated"):
357 with tt.AssertPrints(
358 "[... skipping similar frames: "
359 "r3a at line 8 (29 times), "
360 "r3b at line 11 (29 times), "
361 "r3c at line 14 (29 times)]"
362 ):
364 363 ip.run_cell("r3o2()")
365 364
366 @recursionlimit(150)
367 def test_find_recursion(self):
368 captured = []
369 def capture_exc(*args, **kwargs):
370 captured.append(sys.exc_info())
371 with mock.patch.object(ip, 'showtraceback', capture_exc):
372 ip.run_cell("r3o2()")
373
374 self.assertEqual(len(captured), 1)
375 etype, evalue, tb = captured[0]
376 self.assertIn("recursion", str(evalue))
377
378 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
379 for r in records[:10]:
380 print(r[1:4])
381
382 # The outermost frames should be:
383 # 0: the 'cell' that was running when the exception came up
384 # 1: r3o2()
385 # 2: r3o1()
386 # 3: r3a()
387 # Then repeating r3b, r3c, r3a
388 last_unique, repeat_length = find_recursion(etype, evalue, records)
389 self.assertEqual(last_unique, 2)
390 self.assertEqual(repeat_length, 3)
391
392 365
393 366 #----------------------------------------------------------------------------
394 367
395 368 # module testing (minimal)
396 369 def test_handlers():
397 370 def spam(c, d_e):
398 371 (d, e) = d_e
399 372 x = c + d
400 373 y = c * d
401 374 foo(x, y)
402 375
403 376 def foo(a, b, bar=1):
404 377 eggs(a, b + bar)
405 378
406 379 def eggs(f, g, z=globals()):
407 380 h = f + g
408 381 i = f - g
409 382 return h / i
410 383
411 384 buff = io.StringIO()
412 385
413 386 buff.write('')
414 387 buff.write('*** Before ***')
415 388 try:
416 389 buff.write(spam(1, (2, 3)))
417 390 except:
418 391 traceback.print_exc(file=buff)
419 392
420 393 handler = ColorTB(ostream=buff)
421 394 buff.write('*** ColorTB ***')
422 395 try:
423 396 buff.write(spam(1, (2, 3)))
424 397 except:
425 398 handler(*sys.exc_info())
426 399 buff.write('')
427 400
428 401 handler = VerboseTB(ostream=buff)
429 402 buff.write('*** VerboseTB ***')
430 403 try:
431 404 buff.write(spam(1, (2, 3)))
432 405 except:
433 406 handler(*sys.exc_info())
434 407 buff.write('')
435 408
436 409 from IPython.testing.decorators import skipif
437 410
438 411 class TokenizeFailureTest(unittest.TestCase):
439 412 """Tests related to https://github.com/ipython/ipython/issues/6864."""
440 413
441 414 # that appear to test that we are handling an exception that can be thrown
442 415 # by the tokenizer due to a bug that seem to have been fixed in 3.8, though
443 416 # I'm unsure if other sequences can make it raise this error. Let's just
444 417 # skip in 3.8 for now
445 418 @skipif(sys.version_info > (3,8))
446 419 def testLogging(self):
447 420 message = "An unexpected error occurred while tokenizing input"
448 421 cell = 'raise ValueError("""a\nb""")'
449 422
450 423 stream = io.StringIO()
451 424 handler = logging.StreamHandler(stream)
452 425 logger = logging.getLogger()
453 426 loglevel = logger.level
454 427 logger.addHandler(handler)
455 428 self.addCleanup(lambda: logger.removeHandler(handler))
456 429 self.addCleanup(lambda: logger.setLevel(loglevel))
457 430
458 431 logger.setLevel(logging.INFO)
459 432 with tt.AssertNotPrints(message):
460 433 ip.run_cell(cell)
461 434 self.assertNotIn(message, stream.getvalue())
462 435
463 436 logger.setLevel(logging.DEBUG)
464 437 with tt.AssertNotPrints(message):
465 438 ip.run_cell(cell)
466 439 self.assertIn(message, stream.getvalue())
General Comments 0
You need to be logged in to leave comments. Login now