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