##// END OF EJS Templates
Fix state leakage and speedup in test-suite...
Matthias Bussonnier -
Show More
@@ -1,558 +1,559 b''
1 1 # encoding: utf-8
2 2 """Tests for code execution (%run and related), which is particularly tricky.
3 3
4 4 Because of how %run manages namespaces, and the fact that we are trying here to
5 5 verify subtle object deletion and reference counting issues, the %run tests
6 6 will be kept in this separate file. This makes it easier to aggregate in one
7 7 place the tricks needed to handle it; most other magics are much easier to test
8 8 and we do so in a common test_magic file.
9 9
10 10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 11 as otherwise it may influence later tests.
12 12 """
13 13
14 14 # Copyright (c) IPython Development Team.
15 15 # Distributed under the terms of the Modified BSD License.
16 16
17 17
18 18
19 19 import functools
20 20 import os
21 21 from os.path import join as pjoin
22 22 import random
23 23 import string
24 24 import sys
25 25 import textwrap
26 26 import unittest
27 27 from unittest.mock import patch
28 28
29 29 import nose.tools as nt
30 30 from nose import SkipTest
31 31
32 32 from IPython.testing import decorators as dec
33 33 from IPython.testing import tools as tt
34 34 from IPython.utils.io import capture_output
35 35 from IPython.utils.tempdir import TemporaryDirectory
36 36 from IPython.core import debugger
37 37
38
39 38 def doctest_refbug():
40 39 """Very nasty problem with references held by multiple runs of a script.
41 40 See: https://github.com/ipython/ipython/issues/141
42 41
43 42 In [1]: _ip.clear_main_mod_cache()
44 43 # random
45 44
46 45 In [2]: %run refbug
47 46
48 47 In [3]: call_f()
49 48 lowercased: hello
50 49
51 50 In [4]: %run refbug
52 51
53 52 In [5]: call_f()
54 53 lowercased: hello
55 54 lowercased: hello
56 55 """
57 56
58 57
59 58 def doctest_run_builtins():
60 59 r"""Check that %run doesn't damage __builtins__.
61 60
62 61 In [1]: import tempfile
63 62
64 63 In [2]: bid1 = id(__builtins__)
65 64
66 65 In [3]: fname = tempfile.mkstemp('.py')[1]
67 66
68 67 In [3]: f = open(fname,'w')
69 68
70 69 In [4]: dummy= f.write('pass\n')
71 70
72 71 In [5]: f.flush()
73 72
74 73 In [6]: t1 = type(__builtins__)
75 74
76 75 In [7]: %run $fname
77 76
78 77 In [7]: f.close()
79 78
80 79 In [8]: bid2 = id(__builtins__)
81 80
82 81 In [9]: t2 = type(__builtins__)
83 82
84 83 In [10]: t1 == t2
85 84 Out[10]: True
86 85
87 86 In [10]: bid1 == bid2
88 87 Out[10]: True
89 88
90 89 In [12]: try:
91 90 ....: os.unlink(fname)
92 91 ....: except:
93 92 ....: pass
94 93 ....:
95 94 """
96 95
97 96
98 97 def doctest_run_option_parser():
99 98 r"""Test option parser in %run.
100 99
101 100 In [1]: %run print_argv.py
102 101 []
103 102
104 103 In [2]: %run print_argv.py print*.py
105 104 ['print_argv.py']
106 105
107 106 In [3]: %run -G print_argv.py print*.py
108 107 ['print*.py']
109 108
110 109 """
111 110
112 111
113 112 @dec.skip_win32
114 113 def doctest_run_option_parser_for_posix():
115 114 r"""Test option parser in %run (Linux/OSX specific).
116 115
117 116 You need double quote to escape glob in POSIX systems:
118 117
119 118 In [1]: %run print_argv.py print\\*.py
120 119 ['print*.py']
121 120
122 121 You can't use quote to escape glob in POSIX systems:
123 122
124 123 In [2]: %run print_argv.py 'print*.py'
125 124 ['print_argv.py']
126 125
127 126 """
128 127
129 128
130 129 @dec.skip_if_not_win32
131 130 def doctest_run_option_parser_for_windows():
132 131 r"""Test option parser in %run (Windows specific).
133 132
134 133 In Windows, you can't escape ``*` `by backslash:
135 134
136 135 In [1]: %run print_argv.py print\\*.py
137 136 ['print\\*.py']
138 137
139 138 You can use quote to escape glob:
140 139
141 140 In [2]: %run print_argv.py 'print*.py'
142 141 ['print*.py']
143 142
144 143 """
145 144
146 145
147 146 def doctest_reset_del():
148 147 """Test that resetting doesn't cause errors in __del__ methods.
149 148
150 149 In [2]: class A(object):
151 150 ...: def __del__(self):
152 151 ...: print(str("Hi"))
153 152 ...:
154 153
155 154 In [3]: a = A()
156 155
157 156 In [4]: get_ipython().reset()
158 157 Hi
159 158
160 159 In [5]: 1+1
161 160 Out[5]: 2
162 161 """
163 162
164 163 # For some tests, it will be handy to organize them in a class with a common
165 164 # setup that makes a temp file
166 165
167 166 class TestMagicRunPass(tt.TempFileMixin):
168 167
169 168 def setup(self):
170 169 content = "a = [1,2,3]\nb = 1"
171 170 self.mktmp(content)
172 171
173 172 def run_tmpfile(self):
174 173 _ip = get_ipython()
175 174 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
176 175 # See below and ticket https://bugs.launchpad.net/bugs/366353
177 176 _ip.magic('run %s' % self.fname)
178 177
179 178 def run_tmpfile_p(self):
180 179 _ip = get_ipython()
181 180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
182 181 # See below and ticket https://bugs.launchpad.net/bugs/366353
183 182 _ip.magic('run -p %s' % self.fname)
184 183
185 184 def test_builtins_id(self):
186 185 """Check that %run doesn't damage __builtins__ """
187 186 _ip = get_ipython()
188 187 # Test that the id of __builtins__ is not modified by %run
189 188 bid1 = id(_ip.user_ns['__builtins__'])
190 189 self.run_tmpfile()
191 190 bid2 = id(_ip.user_ns['__builtins__'])
192 191 nt.assert_equal(bid1, bid2)
193 192
194 193 def test_builtins_type(self):
195 194 """Check that the type of __builtins__ doesn't change with %run.
196 195
197 196 However, the above could pass if __builtins__ was already modified to
198 197 be a dict (it should be a module) by a previous use of %run. So we
199 198 also check explicitly that it really is a module:
200 199 """
201 200 _ip = get_ipython()
202 201 self.run_tmpfile()
203 202 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
204 203
205 204 def test_run_profile( self ):
206 205 """Test that the option -p, which invokes the profiler, do not
207 206 crash by invoking execfile"""
208 207 self.run_tmpfile_p()
209 208
210 209 def test_run_debug_twice(self):
211 210 # https://github.com/ipython/ipython/issues/10028
212 211 _ip = get_ipython()
213 212 with tt.fake_input(['c']):
214 213 _ip.magic('run -d %s' % self.fname)
215 214 with tt.fake_input(['c']):
216 215 _ip.magic('run -d %s' % self.fname)
217 216
218 217 def test_run_debug_twice_with_breakpoint(self):
219 218 """Make a valid python temp file."""
220 219 _ip = get_ipython()
221 220 with tt.fake_input(['b 2', 'c', 'c']):
222 221 _ip.magic('run -d %s' % self.fname)
223 222
224 223 with tt.fake_input(['c']):
225 224 with tt.AssertNotPrints('KeyError'):
226 225 _ip.magic('run -d %s' % self.fname)
227 226
228 227
229 228 class TestMagicRunSimple(tt.TempFileMixin):
230 229
231 230 def test_simpledef(self):
232 231 """Test that simple class definitions work."""
233 232 src = ("class foo: pass\n"
234 233 "def f(): return foo()")
235 234 self.mktmp(src)
236 235 _ip.magic('run %s' % self.fname)
237 236 _ip.run_cell('t = isinstance(f(), foo)')
238 237 nt.assert_true(_ip.user_ns['t'])
239 238
240 239 def test_obj_del(self):
241 240 """Test that object's __del__ methods are called on exit."""
242 241 if sys.platform == 'win32':
243 242 try:
244 243 import win32api
245 244 except ImportError:
246 245 raise SkipTest("Test requires pywin32")
247 246 src = ("class A(object):\n"
248 247 " def __del__(self):\n"
249 248 " print('object A deleted')\n"
250 249 "a = A()\n")
251 250 self.mktmp(src)
252 251 if dec.module_not_available('sqlite3'):
253 252 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
254 253 else:
255 254 err = None
256 255 tt.ipexec_validate(self.fname, 'object A deleted', err)
257 256
258 257 def test_aggressive_namespace_cleanup(self):
259 258 """Test that namespace cleanup is not too aggressive GH-238
260 259
261 260 Returning from another run magic deletes the namespace"""
262 261 # see ticket https://github.com/ipython/ipython/issues/238
263 262
264 263 with tt.TempFileMixin() as empty:
265 264 empty.mktmp('')
266 265 # On Windows, the filename will have \users in it, so we need to use the
267 266 # repr so that the \u becomes \\u.
268 267 src = ("ip = get_ipython()\n"
269 268 "for i in range(5):\n"
270 269 " try:\n"
271 270 " ip.magic(%r)\n"
272 271 " except NameError as e:\n"
273 272 " print(i)\n"
274 273 " break\n" % ('run ' + empty.fname))
275 274 self.mktmp(src)
276 275 _ip.magic('run %s' % self.fname)
277 276 _ip.run_cell('ip == get_ipython()')
278 277 nt.assert_equal(_ip.user_ns['i'], 4)
279 278
280 279 def test_run_second(self):
281 280 """Test that running a second file doesn't clobber the first, gh-3547
282 281 """
283 282 self.mktmp("avar = 1\n"
284 283 "def afunc():\n"
285 284 " return avar\n")
286 285
287 286 with tt.TempFileMixin() as empty:
288 287 empty.mktmp("")
289 288
290 289 _ip.magic('run %s' % self.fname)
291 290 _ip.magic('run %s' % empty.fname)
292 291 nt.assert_equal(_ip.user_ns['afunc'](), 1)
293 292
294 293 @dec.skip_win32
295 294 def test_tclass(self):
296 295 mydir = os.path.dirname(__file__)
297 296 tc = os.path.join(mydir, 'tclass')
298 297 src = ("%%run '%s' C-first\n"
299 298 "%%run '%s' C-second\n"
300 299 "%%run '%s' C-third\n") % (tc, tc, tc)
301 300 self.mktmp(src, '.ipy')
302 301 out = """\
303 302 ARGV 1-: ['C-first']
304 303 ARGV 1-: ['C-second']
305 304 tclass.py: deleting object: C-first
306 305 ARGV 1-: ['C-third']
307 306 tclass.py: deleting object: C-second
308 307 tclass.py: deleting object: C-third
309 308 """
310 309 if dec.module_not_available('sqlite3'):
311 310 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
312 311 else:
313 312 err = None
314 313 tt.ipexec_validate(self.fname, out, err)
315 314
316 315 def test_run_i_after_reset(self):
317 316 """Check that %run -i still works after %reset (gh-693)"""
318 317 src = "yy = zz\n"
319 318 self.mktmp(src)
320 319 _ip.run_cell("zz = 23")
321 320 try:
322 321 _ip.magic('run -i %s' % self.fname)
323 322 nt.assert_equal(_ip.user_ns['yy'], 23)
324 323 finally:
325 324 _ip.magic('reset -f')
326 325
327 326 _ip.run_cell("zz = 23")
328 327 try:
329 328 _ip.magic('run -i %s' % self.fname)
330 329 nt.assert_equal(_ip.user_ns['yy'], 23)
331 330 finally:
332 331 _ip.magic('reset -f')
333 332
334 333 def test_unicode(self):
335 334 """Check that files in odd encodings are accepted."""
336 335 mydir = os.path.dirname(__file__)
337 336 na = os.path.join(mydir, 'nonascii.py')
338 337 _ip.magic('run "%s"' % na)
339 338 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
340 339
341 340 def test_run_py_file_attribute(self):
342 341 """Test handling of `__file__` attribute in `%run <file>.py`."""
343 342 src = "t = __file__\n"
344 343 self.mktmp(src)
345 344 _missing = object()
346 345 file1 = _ip.user_ns.get('__file__', _missing)
347 346 _ip.magic('run %s' % self.fname)
348 347 file2 = _ip.user_ns.get('__file__', _missing)
349 348
350 349 # Check that __file__ was equal to the filename in the script's
351 350 # namespace.
352 351 nt.assert_equal(_ip.user_ns['t'], self.fname)
353 352
354 353 # Check that __file__ was not leaked back into user_ns.
355 354 nt.assert_equal(file1, file2)
356 355
357 356 def test_run_ipy_file_attribute(self):
358 357 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
359 358 src = "t = __file__\n"
360 359 self.mktmp(src, ext='.ipy')
361 360 _missing = object()
362 361 file1 = _ip.user_ns.get('__file__', _missing)
363 362 _ip.magic('run %s' % self.fname)
364 363 file2 = _ip.user_ns.get('__file__', _missing)
365 364
366 365 # Check that __file__ was equal to the filename in the script's
367 366 # namespace.
368 367 nt.assert_equal(_ip.user_ns['t'], self.fname)
369 368
370 369 # Check that __file__ was not leaked back into user_ns.
371 370 nt.assert_equal(file1, file2)
372 371
373 372 def test_run_formatting(self):
374 373 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
375 374 src = "pass"
376 375 self.mktmp(src)
377 376 _ip.magic('run -t -N 1 %s' % self.fname)
378 377 _ip.magic('run -t -N 10 %s' % self.fname)
379 378
380 379 def test_ignore_sys_exit(self):
381 380 """Test the -e option to ignore sys.exit()"""
382 381 src = "import sys; sys.exit(1)"
383 382 self.mktmp(src)
384 383 with tt.AssertPrints('SystemExit'):
385 384 _ip.magic('run %s' % self.fname)
386 385
387 386 with tt.AssertNotPrints('SystemExit'):
388 387 _ip.magic('run -e %s' % self.fname)
389 388
390 389 def test_run_nb(self):
391 390 """Test %run notebook.ipynb"""
392 391 from nbformat import v4, writes
393 392 nb = v4.new_notebook(
394 393 cells=[
395 394 v4.new_markdown_cell("The Ultimate Question of Everything"),
396 395 v4.new_code_cell("answer=42")
397 396 ]
398 397 )
399 398 src = writes(nb, version=4)
400 399 self.mktmp(src, ext='.ipynb')
401 400
402 401 _ip.magic("run %s" % self.fname)
403 402
404 403 nt.assert_equal(_ip.user_ns['answer'], 42)
405 404
406 405 def test_file_options(self):
407 406 src = ('import sys\n'
408 407 'a = " ".join(sys.argv[1:])\n')
409 408 self.mktmp(src)
410 409 test_opts = '-x 3 --verbose'
411 410 _ip.run_line_magic("run", '{0} {1}'.format(self.fname, test_opts))
412 411 nt.assert_equal(_ip.user_ns['a'], test_opts)
413 412
414 413
415 414 class TestMagicRunWithPackage(unittest.TestCase):
416 415
417 416 def writefile(self, name, content):
418 417 path = os.path.join(self.tempdir.name, name)
419 418 d = os.path.dirname(path)
420 419 if not os.path.isdir(d):
421 420 os.makedirs(d)
422 421 with open(path, 'w') as f:
423 422 f.write(textwrap.dedent(content))
424 423
425 424 def setUp(self):
426 425 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
427 426 """Temporary (probably) valid python package name."""
428 427
429 428 self.value = int(random.random() * 10000)
430 429
431 430 self.tempdir = TemporaryDirectory()
432 431 self.__orig_cwd = os.getcwd()
433 432 sys.path.insert(0, self.tempdir.name)
434 433
435 434 self.writefile(os.path.join(package, '__init__.py'), '')
436 435 self.writefile(os.path.join(package, 'sub.py'), """
437 436 x = {0!r}
438 437 """.format(self.value))
439 438 self.writefile(os.path.join(package, 'relative.py'), """
440 439 from .sub import x
441 440 """)
442 441 self.writefile(os.path.join(package, 'absolute.py'), """
443 442 from {0}.sub import x
444 443 """.format(package))
445 444 self.writefile(os.path.join(package, 'args.py'), """
446 445 import sys
447 446 a = " ".join(sys.argv[1:])
448 447 """.format(package))
449 448
450 449 def tearDown(self):
451 450 os.chdir(self.__orig_cwd)
452 451 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
453 452 self.tempdir.cleanup()
454 453
455 454 def check_run_submodule(self, submodule, opts=''):
456 455 _ip.user_ns.pop('x', None)
457 456 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
458 457 self.assertEqual(_ip.user_ns['x'], self.value,
459 458 'Variable `x` is not loaded from module `{0}`.'
460 459 .format(submodule))
461 460
462 461 def test_run_submodule_with_absolute_import(self):
463 462 self.check_run_submodule('absolute')
464 463
465 464 def test_run_submodule_with_relative_import(self):
466 465 """Run submodule that has a relative import statement (#2727)."""
467 466 self.check_run_submodule('relative')
468 467
469 468 def test_prun_submodule_with_absolute_import(self):
470 469 self.check_run_submodule('absolute', '-p')
471 470
472 471 def test_prun_submodule_with_relative_import(self):
473 472 self.check_run_submodule('relative', '-p')
474 473
475 474 def with_fake_debugger(func):
476 475 @functools.wraps(func)
477 476 def wrapper(*args, **kwds):
478 477 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
479 478 return func(*args, **kwds)
480 479 return wrapper
481 480
482 481 @with_fake_debugger
483 482 def test_debug_run_submodule_with_absolute_import(self):
484 483 self.check_run_submodule('absolute', '-d')
485 484
486 485 @with_fake_debugger
487 486 def test_debug_run_submodule_with_relative_import(self):
488 487 self.check_run_submodule('relative', '-d')
489 488
490 489 def test_module_options(self):
491 490 _ip.user_ns.pop('a', None)
492 491 test_opts = '-x abc -m test'
493 492 _ip.run_line_magic('run', '-m {0}.args {1}'.format(self.package, test_opts))
494 493 nt.assert_equal(_ip.user_ns['a'], test_opts)
495 494
496 495 def test_module_options_with_separator(self):
497 496 _ip.user_ns.pop('a', None)
498 497 test_opts = '-x abc -m test'
499 498 _ip.run_line_magic('run', '-m {0}.args -- {1}'.format(self.package, test_opts))
500 499 nt.assert_equal(_ip.user_ns['a'], test_opts)
501 500
502 501 def test_run__name__():
503 502 with TemporaryDirectory() as td:
504 503 path = pjoin(td, 'foo.py')
505 504 with open(path, 'w') as f:
506 505 f.write("q = __name__")
507 506
508 507 _ip.user_ns.pop('q', None)
509 508 _ip.magic('run {}'.format(path))
510 509 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
511 510
512 511 _ip.magic('run -n {}'.format(path))
513 512 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
514 513
515 514 try:
516 515 _ip.magic('run -i -n {}'.format(path))
517 516 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
518 517 finally:
519 518 _ip.magic('reset -f')
520 519
521 520
522 521 def test_run_tb():
523 522 """Test traceback offset in %run"""
524 523 with TemporaryDirectory() as td:
525 524 path = pjoin(td, 'foo.py')
526 525 with open(path, 'w') as f:
527 526 f.write('\n'.join([
528 527 "def foo():",
529 528 " return bar()",
530 529 "def bar():",
531 530 " raise RuntimeError('hello!')",
532 531 "foo()",
533 532 ]))
534 533 with capture_output() as io:
535 534 _ip.magic('run {}'.format(path))
536 535 out = io.stdout
537 536 nt.assert_not_in("execfile", out)
538 537 nt.assert_in("RuntimeError", out)
539 538 nt.assert_equal(out.count("---->"), 3)
539 del ip.user_ns['bar']
540 del ip.user_ns['foo']
540 541
541 542 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
542 543 def test_script_tb():
543 544 """Test traceback offset in `ipython script.py`"""
544 545 with TemporaryDirectory() as td:
545 546 path = pjoin(td, 'foo.py')
546 547 with open(path, 'w') as f:
547 548 f.write('\n'.join([
548 549 "def foo():",
549 550 " return bar()",
550 551 "def bar():",
551 552 " raise RuntimeError('hello!')",
552 553 "foo()",
553 554 ]))
554 555 out, err = tt.ipexec(path)
555 556 nt.assert_not_in("execfile", out)
556 557 nt.assert_in("RuntimeError", out)
557 558 nt.assert_equal(out.count("---->"), 3)
558 559
@@ -1,412 +1,440 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 from ..ultratb import ColorTB, VerboseTB, find_recursion
13 import IPython.core.ultratb as ultratb
14 from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion
14 15
15 16
16 17 from IPython.testing import tools as tt
17 18 from IPython.testing.decorators import onlyif_unicode_paths
18 19 from IPython.utils.syspathcontext import prepended_to_syspath
19 20 from IPython.utils.tempdir import TemporaryDirectory
20 21
21 ip = get_ipython()
22
23 22 file_1 = """1
24 23 2
25 24 3
26 25 def f():
27 26 1/0
28 27 """
29 28
30 29 file_2 = """def f():
31 30 1/0
32 31 """
33 32
33
34 def recursionlimit(frames):
35 """
36 decorator to set the recursion limit temporarily
37 """
38
39 def inner(test_function):
40 def wrapper(*args, **kwargs):
41 _orig_rec_limit = ultratb._FRAME_RECURSION_LIMIT
42 ultratb._FRAME_RECURSION_LIMIT = frames - 50
43
44 rl = sys.getrecursionlimit()
45 sys.setrecursionlimit(frames)
46 try:
47 return test_function(*args, **kwargs)
48 finally:
49 sys.setrecursionlimit(rl)
50 ultratb._FRAME_RECURSION_LIMIT = _orig_rec_limit
51
52 return wrapper
53
54 return inner
55
56
34 57 class ChangedPyFileTest(unittest.TestCase):
35 58 def test_changing_py_file(self):
36 59 """Traceback produced if the line where the error occurred is missing?
37 60
38 61 https://github.com/ipython/ipython/issues/1456
39 62 """
40 63 with TemporaryDirectory() as td:
41 64 fname = os.path.join(td, "foo.py")
42 65 with open(fname, "w") as f:
43 66 f.write(file_1)
44 67
45 68 with prepended_to_syspath(td):
46 69 ip.run_cell("import foo")
47 70
48 71 with tt.AssertPrints("ZeroDivisionError"):
49 72 ip.run_cell("foo.f()")
50 73
51 74 # Make the file shorter, so the line of the error is missing.
52 75 with open(fname, "w") as f:
53 76 f.write(file_2)
54 77
55 78 # For some reason, this was failing on the *second* call after
56 79 # changing the file, so we call f() twice.
57 80 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
58 81 with tt.AssertPrints("ZeroDivisionError"):
59 82 ip.run_cell("foo.f()")
60 83 with tt.AssertPrints("ZeroDivisionError"):
61 84 ip.run_cell("foo.f()")
62 85
63 86 iso_8859_5_file = u'''# coding: iso-8859-5
64 87
65 88 def fail():
66 89 """Π΄Π±Π˜Π–"""
67 90 1/0 # Π΄Π±Π˜Π–
68 91 '''
69 92
70 93 class NonAsciiTest(unittest.TestCase):
71 94 @onlyif_unicode_paths
72 95 def test_nonascii_path(self):
73 96 # Non-ascii directory name as well.
74 97 with TemporaryDirectory(suffix=u'Γ©') as td:
75 98 fname = os.path.join(td, u"fooΓ©.py")
76 99 with open(fname, "w") as f:
77 100 f.write(file_1)
78 101
79 102 with prepended_to_syspath(td):
80 103 ip.run_cell("import foo")
81 104
82 105 with tt.AssertPrints("ZeroDivisionError"):
83 106 ip.run_cell("foo.f()")
84 107
85 108 def test_iso8859_5(self):
86 109 with TemporaryDirectory() as td:
87 110 fname = os.path.join(td, 'dfghjkl.py')
88 111
89 112 with io.open(fname, 'w', encoding='iso-8859-5') as f:
90 113 f.write(iso_8859_5_file)
91 114
92 115 with prepended_to_syspath(td):
93 116 ip.run_cell("from dfghjkl import fail")
94 117
95 118 with tt.AssertPrints("ZeroDivisionError"):
96 119 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
97 120 ip.run_cell('fail()')
98 121
99 122 def test_nonascii_msg(self):
100 123 cell = u"raise Exception('Γ©')"
101 124 expected = u"Exception('Γ©')"
102 125 ip.run_cell("%xmode plain")
103 126 with tt.AssertPrints(expected):
104 127 ip.run_cell(cell)
105 128
106 129 ip.run_cell("%xmode verbose")
107 130 with tt.AssertPrints(expected):
108 131 ip.run_cell(cell)
109 132
110 133 ip.run_cell("%xmode context")
111 134 with tt.AssertPrints(expected):
112 135 ip.run_cell(cell)
113 136
114 137 ip.run_cell("%xmode minimal")
115 138 with tt.AssertPrints(u"Exception: Γ©"):
116 139 ip.run_cell(cell)
117 140
118 141 # Put this back into Context mode for later tests.
119 142 ip.run_cell("%xmode context")
120 143
121 144 class NestedGenExprTestCase(unittest.TestCase):
122 145 """
123 146 Regression test for the following issues:
124 147 https://github.com/ipython/ipython/issues/8293
125 148 https://github.com/ipython/ipython/issues/8205
126 149 """
127 150 def test_nested_genexpr(self):
128 151 code = dedent(
129 152 """\
130 153 class SpecificException(Exception):
131 154 pass
132 155
133 156 def foo(x):
134 157 raise SpecificException("Success!")
135 158
136 159 sum(sum(foo(x) for _ in [0]) for x in [0])
137 160 """
138 161 )
139 162 with tt.AssertPrints('SpecificException: Success!', suppress=False):
140 163 ip.run_cell(code)
141 164
142 165
143 166 indentationerror_file = """if True:
144 167 zoon()
145 168 """
146 169
147 170 class IndentationErrorTest(unittest.TestCase):
148 171 def test_indentationerror_shows_line(self):
149 172 # See issue gh-2398
150 173 with tt.AssertPrints("IndentationError"):
151 174 with tt.AssertPrints("zoon()", suppress=False):
152 175 ip.run_cell(indentationerror_file)
153 176
154 177 with TemporaryDirectory() as td:
155 178 fname = os.path.join(td, "foo.py")
156 179 with open(fname, "w") as f:
157 180 f.write(indentationerror_file)
158 181
159 182 with tt.AssertPrints("IndentationError"):
160 183 with tt.AssertPrints("zoon()", suppress=False):
161 184 ip.magic('run %s' % fname)
162 185
163 186 se_file_1 = """1
164 187 2
165 188 7/
166 189 """
167 190
168 191 se_file_2 = """7/
169 192 """
170 193
171 194 class SyntaxErrorTest(unittest.TestCase):
172 195 def test_syntaxerror_without_lineno(self):
173 196 with tt.AssertNotPrints("TypeError"):
174 197 with tt.AssertPrints("line unknown"):
175 198 ip.run_cell("raise SyntaxError()")
176 199
177 200 def test_syntaxerror_no_stacktrace_at_compile_time(self):
178 201 syntax_error_at_compile_time = """
179 202 def foo():
180 203 ..
181 204 """
182 205 with tt.AssertPrints("SyntaxError"):
183 206 ip.run_cell(syntax_error_at_compile_time)
184 207
185 208 with tt.AssertNotPrints("foo()"):
186 209 ip.run_cell(syntax_error_at_compile_time)
187 210
188 211 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
189 212 syntax_error_at_runtime = """
190 213 def foo():
191 214 eval("..")
192 215
193 216 def bar():
194 217 foo()
195 218
196 219 bar()
197 220 """
198 221 with tt.AssertPrints("SyntaxError"):
199 222 ip.run_cell(syntax_error_at_runtime)
200 223 # Assert syntax error during runtime generate stacktrace
201 224 with tt.AssertPrints(["foo()", "bar()"]):
202 225 ip.run_cell(syntax_error_at_runtime)
226 del ip.user_ns['bar']
227 del ip.user_ns['foo']
203 228
204 229 def test_changing_py_file(self):
205 230 with TemporaryDirectory() as td:
206 231 fname = os.path.join(td, "foo.py")
207 232 with open(fname, 'w') as f:
208 233 f.write(se_file_1)
209 234
210 235 with tt.AssertPrints(["7/", "SyntaxError"]):
211 236 ip.magic("run " + fname)
212 237
213 238 # Modify the file
214 239 with open(fname, 'w') as f:
215 240 f.write(se_file_2)
216 241
217 242 # The SyntaxError should point to the correct line
218 243 with tt.AssertPrints(["7/", "SyntaxError"]):
219 244 ip.magic("run " + fname)
220 245
221 246 def test_non_syntaxerror(self):
222 247 # SyntaxTB may be called with an error other than a SyntaxError
223 248 # See e.g. gh-4361
224 249 try:
225 250 raise ValueError('QWERTY')
226 251 except ValueError:
227 252 with tt.AssertPrints('QWERTY'):
228 253 ip.showsyntaxerror()
229 254
230 255
231 256 class Python3ChainedExceptionsTest(unittest.TestCase):
232 257 DIRECT_CAUSE_ERROR_CODE = """
233 258 try:
234 259 x = 1 + 2
235 260 print(not_defined_here)
236 261 except Exception as e:
237 262 x += 55
238 263 x - 1
239 264 y = {}
240 265 raise KeyError('uh') from e
241 266 """
242 267
243 268 EXCEPTION_DURING_HANDLING_CODE = """
244 269 try:
245 270 x = 1 + 2
246 271 print(not_defined_here)
247 272 except Exception as e:
248 273 x += 55
249 274 x - 1
250 275 y = {}
251 276 raise KeyError('uh')
252 277 """
253 278
254 279 SUPPRESS_CHAINING_CODE = """
255 280 try:
256 281 1/0
257 282 except Exception:
258 283 raise ValueError("Yikes") from None
259 284 """
260 285
261 286 def test_direct_cause_error(self):
262 287 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
263 288 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
264 289
265 290 def test_exception_during_handling_error(self):
266 291 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
267 292 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
268 293
269 294 def test_suppress_exception_chaining(self):
270 295 with tt.AssertNotPrints("ZeroDivisionError"), \
271 296 tt.AssertPrints("ValueError", suppress=False):
272 297 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
273 298
274 299
275 300 class RecursionTest(unittest.TestCase):
276 301 DEFINITIONS = """
277 302 def non_recurs():
278 303 1/0
279 304
280 305 def r1():
281 306 r1()
282 307
283 308 def r3a():
284 309 r3b()
285 310
286 311 def r3b():
287 312 r3c()
288 313
289 314 def r3c():
290 315 r3a()
291 316
292 317 def r3o1():
293 318 r3a()
294 319
295 320 def r3o2():
296 321 r3o1()
297 322 """
298 323 def setUp(self):
299 324 ip.run_cell(self.DEFINITIONS)
300 325
301 326 def test_no_recursion(self):
302 327 with tt.AssertNotPrints("frames repeated"):
303 328 ip.run_cell("non_recurs()")
304 329
330 @recursionlimit(65)
305 331 def test_recursion_one_frame(self):
306 332 with tt.AssertPrints("1 frames repeated"):
307 333 ip.run_cell("r1()")
308 334
335 @recursionlimit(65)
309 336 def test_recursion_three_frames(self):
310 337 with tt.AssertPrints("3 frames repeated"):
311 338 ip.run_cell("r3o2()")
312 339
340 @recursionlimit(65)
313 341 def test_find_recursion(self):
314 342 captured = []
315 343 def capture_exc(*args, **kwargs):
316 344 captured.append(sys.exc_info())
317 345 with mock.patch.object(ip, 'showtraceback', capture_exc):
318 346 ip.run_cell("r3o2()")
319 347
320 348 self.assertEqual(len(captured), 1)
321 349 etype, evalue, tb = captured[0]
322 350 self.assertIn("recursion", str(evalue))
323 351
324 352 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
325 353 for r in records[:10]:
326 354 print(r[1:4])
327 355
328 356 # The outermost frames should be:
329 357 # 0: the 'cell' that was running when the exception came up
330 358 # 1: r3o2()
331 359 # 2: r3o1()
332 360 # 3: r3a()
333 361 # Then repeating r3b, r3c, r3a
334 362 last_unique, repeat_length = find_recursion(etype, evalue, records)
335 363 self.assertEqual(last_unique, 2)
336 364 self.assertEqual(repeat_length, 3)
337 365
338 366
339 367 #----------------------------------------------------------------------------
340 368
341 369 # module testing (minimal)
342 370 def test_handlers():
343 371 def spam(c, d_e):
344 372 (d, e) = d_e
345 373 x = c + d
346 374 y = c * d
347 375 foo(x, y)
348 376
349 377 def foo(a, b, bar=1):
350 378 eggs(a, b + bar)
351 379
352 380 def eggs(f, g, z=globals()):
353 381 h = f + g
354 382 i = f - g
355 383 return h / i
356 384
357 385 buff = io.StringIO()
358 386
359 387 buff.write('')
360 388 buff.write('*** Before ***')
361 389 try:
362 390 buff.write(spam(1, (2, 3)))
363 391 except:
364 392 traceback.print_exc(file=buff)
365 393
366 394 handler = ColorTB(ostream=buff)
367 395 buff.write('*** ColorTB ***')
368 396 try:
369 397 buff.write(spam(1, (2, 3)))
370 398 except:
371 399 handler(*sys.exc_info())
372 400 buff.write('')
373 401
374 402 handler = VerboseTB(ostream=buff)
375 403 buff.write('*** VerboseTB ***')
376 404 try:
377 405 buff.write(spam(1, (2, 3)))
378 406 except:
379 407 handler(*sys.exc_info())
380 408 buff.write('')
381 409
382 410 from IPython.testing.decorators import skipif
383 411
384 412 class TokenizeFailureTest(unittest.TestCase):
385 413 """Tests related to https://github.com/ipython/ipython/issues/6864."""
386 414
387 415 # that appear to test that we are handling an exception that can be thrown
388 416 # by the tokenizer due to a bug that seem to have been fixed in 3.8, though
389 417 # I'm unsure if other sequences can make it raise this error. Let's just
390 418 # skip in 3.8 for now
391 419 @skipif(sys.version_info > (3,8))
392 420 def testLogging(self):
393 421 message = "An unexpected error occurred while tokenizing input"
394 422 cell = 'raise ValueError("""a\nb""")'
395 423
396 424 stream = io.StringIO()
397 425 handler = logging.StreamHandler(stream)
398 426 logger = logging.getLogger()
399 427 loglevel = logger.level
400 428 logger.addHandler(handler)
401 429 self.addCleanup(lambda: logger.removeHandler(handler))
402 430 self.addCleanup(lambda: logger.setLevel(loglevel))
403 431
404 432 logger.setLevel(logging.INFO)
405 433 with tt.AssertNotPrints(message):
406 434 ip.run_cell(cell)
407 435 self.assertNotIn(message, stream.getvalue())
408 436
409 437 logger.setLevel(logging.DEBUG)
410 438 with tt.AssertNotPrints(message):
411 439 ip.run_cell(cell)
412 440 self.assertIn(message, stream.getvalue())
@@ -1,1467 +1,1473 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Verbose and colourful traceback formatting.
4 4
5 5 **ColorTB**
6 6
7 7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 8 ColorTB class is a solution to that problem. It colors the different parts of a
9 9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 10 text editor.
11 11
12 12 Installation instructions for ColorTB::
13 13
14 14 import sys,ultratb
15 15 sys.excepthook = ultratb.ColorTB()
16 16
17 17 **VerboseTB**
18 18
19 19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 21 and intended it for CGI programmers, but why should they have all the fun? I
22 22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 23 but kind of neat, and maybe useful for long-running programs that you believe
24 24 are bug-free. If a crash *does* occur in that type of program you want details.
25 25 Give it a shot--you'll love it or you'll hate it.
26 26
27 27 .. note::
28 28
29 29 The Verbose mode prints the variables currently visible where the exception
30 30 happened (shortening their strings if too long). This can potentially be
31 31 very slow, if you happen to have a huge data structure whose string
32 32 representation is complex to compute. Your computer may appear to freeze for
33 33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 34 with Ctrl-C (maybe hitting it more than once).
35 35
36 36 If you encounter this kind of situation often, you may want to use the
37 37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 38 variables (but otherwise includes the information and context given by
39 39 Verbose).
40 40
41 41 .. note::
42 42
43 43 The verbose mode print all variables in the stack, which means it can
44 44 potentially leak sensitive information like access keys, or unencryted
45 45 password.
46 46
47 47 Installation instructions for VerboseTB::
48 48
49 49 import sys,ultratb
50 50 sys.excepthook = ultratb.VerboseTB()
51 51
52 52 Note: Much of the code in this module was lifted verbatim from the standard
53 53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54 54
55 55 Color schemes
56 56 -------------
57 57
58 58 The colors are defined in the class TBTools through the use of the
59 59 ColorSchemeTable class. Currently the following exist:
60 60
61 61 - NoColor: allows all of this module to be used in any terminal (the color
62 62 escapes are just dummy blank strings).
63 63
64 64 - Linux: is meant to look good in a terminal like the Linux console (black
65 65 or very dark background).
66 66
67 67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 68 in light background terminals.
69 69
70 70 - Neutral: a neutral color scheme that should be readable on both light and
71 71 dark background
72 72
73 73 You can implement other color schemes easily, the syntax is fairly
74 74 self-explanatory. Please send back new schemes you develop to the author for
75 75 possible inclusion in future releases.
76 76
77 77 Inheritance diagram:
78 78
79 79 .. inheritance-diagram:: IPython.core.ultratb
80 80 :parts: 3
81 81 """
82 82
83 83 #*****************************************************************************
84 84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 86 #
87 87 # Distributed under the terms of the BSD License. The full license is in
88 88 # the file COPYING, distributed as part of this software.
89 89 #*****************************************************************************
90 90
91 91
92 92 import dis
93 93 import inspect
94 94 import keyword
95 95 import linecache
96 96 import os
97 97 import pydoc
98 98 import re
99 99 import sys
100 100 import time
101 101 import tokenize
102 102 import traceback
103 103
104 104 try: # Python 2
105 105 generate_tokens = tokenize.generate_tokens
106 106 except AttributeError: # Python 3
107 107 generate_tokens = tokenize.tokenize
108 108
109 109 # For purposes of monkeypatching inspect to fix a bug in it.
110 110 from inspect import getsourcefile, getfile, getmodule, \
111 111 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
112 112
113 113 # IPython's own modules
114 114 from IPython import get_ipython
115 115 from IPython.core import debugger
116 116 from IPython.core.display_trap import DisplayTrap
117 117 from IPython.core.excolors import exception_colors
118 118 from IPython.utils import PyColorize
119 119 from IPython.utils import path as util_path
120 120 from IPython.utils import py3compat
121 121 from IPython.utils.data import uniq_stable
122 122 from IPython.utils.terminal import get_terminal_size
123 123
124 124 from logging import info, error, debug
125 125
126 126 from importlib.util import source_from_cache
127 127
128 128 import IPython.utils.colorable as colorable
129 129
130 130 # Globals
131 131 # amount of space to put line numbers before verbose tracebacks
132 132 INDENT_SIZE = 8
133 133
134 134 # Default color scheme. This is used, for example, by the traceback
135 135 # formatter. When running in an actual IPython instance, the user's rc.colors
136 136 # value is used, but having a module global makes this functionality available
137 137 # to users of ultratb who are NOT running inside ipython.
138 138 DEFAULT_SCHEME = 'NoColor'
139 139
140
141 # Number of frame above which we are likely to have a recursion and will
142 # **attempt** to detect it. Made modifiable mostly to speedup test suite
143 # as detecting recursion is one of our slowest test
144 _FRAME_RECURSION_LIMIT = 500
145
140 146 # ---------------------------------------------------------------------------
141 147 # Code begins
142 148
143 149 # Utility functions
144 150 def inspect_error():
145 151 """Print a message about internal inspect errors.
146 152
147 153 These are unfortunately quite common."""
148 154
149 155 error('Internal Python error in the inspect module.\n'
150 156 'Below is the traceback from this internal error.\n')
151 157
152 158
153 159 # This function is a monkeypatch we apply to the Python inspect module. We have
154 160 # now found when it's needed (see discussion on issue gh-1456), and we have a
155 161 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
156 162 # the monkeypatch is not applied. TK, Aug 2012.
157 163 def findsource(object):
158 164 """Return the entire source file and starting line number for an object.
159 165
160 166 The argument may be a module, class, method, function, traceback, frame,
161 167 or code object. The source code is returned as a list of all the lines
162 168 in the file and the line number indexes a line in that list. An IOError
163 169 is raised if the source code cannot be retrieved.
164 170
165 171 FIXED version with which we monkeypatch the stdlib to work around a bug."""
166 172
167 173 file = getsourcefile(object) or getfile(object)
168 174 # If the object is a frame, then trying to get the globals dict from its
169 175 # module won't work. Instead, the frame object itself has the globals
170 176 # dictionary.
171 177 globals_dict = None
172 178 if inspect.isframe(object):
173 179 # XXX: can this ever be false?
174 180 globals_dict = object.f_globals
175 181 else:
176 182 module = getmodule(object, file)
177 183 if module:
178 184 globals_dict = module.__dict__
179 185 lines = linecache.getlines(file, globals_dict)
180 186 if not lines:
181 187 raise IOError('could not get source code')
182 188
183 189 if ismodule(object):
184 190 return lines, 0
185 191
186 192 if isclass(object):
187 193 name = object.__name__
188 194 pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
189 195 # make some effort to find the best matching class definition:
190 196 # use the one with the least indentation, which is the one
191 197 # that's most probably not inside a function definition.
192 198 candidates = []
193 199 for i, line in enumerate(lines):
194 200 match = pat.match(line)
195 201 if match:
196 202 # if it's at toplevel, it's already the best one
197 203 if line[0] == 'c':
198 204 return lines, i
199 205 # else add whitespace to candidate list
200 206 candidates.append((match.group(1), i))
201 207 if candidates:
202 208 # this will sort by whitespace, and by line number,
203 209 # less whitespace first
204 210 candidates.sort()
205 211 return lines, candidates[0][1]
206 212 else:
207 213 raise IOError('could not find class definition')
208 214
209 215 if ismethod(object):
210 216 object = object.__func__
211 217 if isfunction(object):
212 218 object = object.__code__
213 219 if istraceback(object):
214 220 object = object.tb_frame
215 221 if isframe(object):
216 222 object = object.f_code
217 223 if iscode(object):
218 224 if not hasattr(object, 'co_firstlineno'):
219 225 raise IOError('could not find function definition')
220 226 pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
221 227 pmatch = pat.match
222 228 # fperez - fix: sometimes, co_firstlineno can give a number larger than
223 229 # the length of lines, which causes an error. Safeguard against that.
224 230 lnum = min(object.co_firstlineno, len(lines)) - 1
225 231 while lnum > 0:
226 232 if pmatch(lines[lnum]):
227 233 break
228 234 lnum -= 1
229 235
230 236 return lines, lnum
231 237 raise IOError('could not find code object')
232 238
233 239
234 240 # This is a patched version of inspect.getargs that applies the (unmerged)
235 241 # patch for http://bugs.python.org/issue14611 by Stefano Taschini. This fixes
236 242 # https://github.com/ipython/ipython/issues/8205 and
237 243 # https://github.com/ipython/ipython/issues/8293
238 244 def getargs(co):
239 245 """Get information about the arguments accepted by a code object.
240 246
241 247 Three things are returned: (args, varargs, varkw), where 'args' is
242 248 a list of argument names (possibly containing nested lists), and
243 249 'varargs' and 'varkw' are the names of the * and ** arguments or None."""
244 250 if not iscode(co):
245 251 raise TypeError('{!r} is not a code object'.format(co))
246 252
247 253 nargs = co.co_argcount
248 254 names = co.co_varnames
249 255 args = list(names[:nargs])
250 256 step = 0
251 257
252 258 # The following acrobatics are for anonymous (tuple) arguments.
253 259 for i in range(nargs):
254 260 if args[i][:1] in ('', '.'):
255 261 stack, remain, count = [], [], []
256 262 while step < len(co.co_code):
257 263 op = ord(co.co_code[step])
258 264 step = step + 1
259 265 if op >= dis.HAVE_ARGUMENT:
260 266 opname = dis.opname[op]
261 267 value = ord(co.co_code[step]) + ord(co.co_code[step+1])*256
262 268 step = step + 2
263 269 if opname in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'):
264 270 remain.append(value)
265 271 count.append(value)
266 272 elif opname in ('STORE_FAST', 'STORE_DEREF'):
267 273 if op in dis.haslocal:
268 274 stack.append(co.co_varnames[value])
269 275 elif op in dis.hasfree:
270 276 stack.append((co.co_cellvars + co.co_freevars)[value])
271 277 # Special case for sublists of length 1: def foo((bar))
272 278 # doesn't generate the UNPACK_TUPLE bytecode, so if
273 279 # `remain` is empty here, we have such a sublist.
274 280 if not remain:
275 281 stack[0] = [stack[0]]
276 282 break
277 283 else:
278 284 remain[-1] = remain[-1] - 1
279 285 while remain[-1] == 0:
280 286 remain.pop()
281 287 size = count.pop()
282 288 stack[-size:] = [stack[-size:]]
283 289 if not remain:
284 290 break
285 291 remain[-1] = remain[-1] - 1
286 292 if not remain:
287 293 break
288 294 args[i] = stack[0]
289 295
290 296 varargs = None
291 297 if co.co_flags & inspect.CO_VARARGS:
292 298 varargs = co.co_varnames[nargs]
293 299 nargs = nargs + 1
294 300 varkw = None
295 301 if co.co_flags & inspect.CO_VARKEYWORDS:
296 302 varkw = co.co_varnames[nargs]
297 303 return inspect.Arguments(args, varargs, varkw)
298 304
299 305
300 306 # Monkeypatch inspect to apply our bugfix.
301 307 def with_patch_inspect(f):
302 308 """
303 309 Deprecated since IPython 6.0
304 310 decorator for monkeypatching inspect.findsource
305 311 """
306 312
307 313 def wrapped(*args, **kwargs):
308 314 save_findsource = inspect.findsource
309 315 save_getargs = inspect.getargs
310 316 inspect.findsource = findsource
311 317 inspect.getargs = getargs
312 318 try:
313 319 return f(*args, **kwargs)
314 320 finally:
315 321 inspect.findsource = save_findsource
316 322 inspect.getargs = save_getargs
317 323
318 324 return wrapped
319 325
320 326
321 327 def fix_frame_records_filenames(records):
322 328 """Try to fix the filenames in each record from inspect.getinnerframes().
323 329
324 330 Particularly, modules loaded from within zip files have useless filenames
325 331 attached to their code object, and inspect.getinnerframes() just uses it.
326 332 """
327 333 fixed_records = []
328 334 for frame, filename, line_no, func_name, lines, index in records:
329 335 # Look inside the frame's globals dictionary for __file__,
330 336 # which should be better. However, keep Cython filenames since
331 337 # we prefer the source filenames over the compiled .so file.
332 338 if not filename.endswith(('.pyx', '.pxd', '.pxi')):
333 339 better_fn = frame.f_globals.get('__file__', None)
334 340 if isinstance(better_fn, str):
335 341 # Check the type just in case someone did something weird with
336 342 # __file__. It might also be None if the error occurred during
337 343 # import.
338 344 filename = better_fn
339 345 fixed_records.append((frame, filename, line_no, func_name, lines, index))
340 346 return fixed_records
341 347
342 348
343 349 @with_patch_inspect
344 350 def _fixed_getinnerframes(etb, context=1, tb_offset=0):
345 351 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
346 352
347 353 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
348 354 # If the error is at the console, don't build any context, since it would
349 355 # otherwise produce 5 blank lines printed out (there is no file at the
350 356 # console)
351 357 rec_check = records[tb_offset:]
352 358 try:
353 359 rname = rec_check[0][1]
354 360 if rname == '<ipython console>' or rname.endswith('<string>'):
355 361 return rec_check
356 362 except IndexError:
357 363 pass
358 364
359 365 aux = traceback.extract_tb(etb)
360 366 assert len(records) == len(aux)
361 367 for i, (file, lnum, _, _) in enumerate(aux):
362 368 maybeStart = lnum - 1 - context // 2
363 369 start = max(maybeStart, 0)
364 370 end = start + context
365 371 lines = linecache.getlines(file)[start:end]
366 372 buf = list(records[i])
367 373 buf[LNUM_POS] = lnum
368 374 buf[INDEX_POS] = lnum - 1 - start
369 375 buf[LINES_POS] = lines
370 376 records[i] = tuple(buf)
371 377 return records[tb_offset:]
372 378
373 379 # Helper function -- largely belongs to VerboseTB, but we need the same
374 380 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
375 381 # can be recognized properly by ipython.el's py-traceback-line-re
376 382 # (SyntaxErrors have to be treated specially because they have no traceback)
377 383
378 384
379 385 def _format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
380 386 """
381 387 Format tracebacks lines with pointing arrow, leading numbers...
382 388
383 389 Parameters
384 390 ==========
385 391
386 392 lnum: int
387 393 index: int
388 394 lines: list[string]
389 395 Colors:
390 396 ColorScheme used.
391 397 lvals: bytes
392 398 Values of local variables, already colored, to inject just after the error line.
393 399 _line_format: f (str) -> (str, bool)
394 400 return (colorized version of str, failure to do so)
395 401 """
396 402 numbers_width = INDENT_SIZE - 1
397 403 res = []
398 404
399 405 for i,line in enumerate(lines, lnum-index):
400 406 line = py3compat.cast_unicode(line)
401 407
402 408 new_line, err = _line_format(line, 'str')
403 409 if not err:
404 410 line = new_line
405 411
406 412 if i == lnum:
407 413 # This is the line with the error
408 414 pad = numbers_width - len(str(i))
409 415 num = '%s%s' % (debugger.make_arrow(pad), str(lnum))
410 416 line = '%s%s%s %s%s' % (Colors.linenoEm, num,
411 417 Colors.line, line, Colors.Normal)
412 418 else:
413 419 num = '%*s' % (numbers_width, i)
414 420 line = '%s%s%s %s' % (Colors.lineno, num,
415 421 Colors.Normal, line)
416 422
417 423 res.append(line)
418 424 if lvals and i == lnum:
419 425 res.append(lvals + '\n')
420 426 return res
421 427
422 428 def is_recursion_error(etype, value, records):
423 429 try:
424 430 # RecursionError is new in Python 3.5
425 431 recursion_error_type = RecursionError
426 432 except NameError:
427 433 recursion_error_type = RuntimeError
428 434
429 435 # The default recursion limit is 1000, but some of that will be taken up
430 436 # by stack frames in IPython itself. >500 frames probably indicates
431 437 # a recursion error.
432 438 return (etype is recursion_error_type) \
433 439 and "recursion" in str(value).lower() \
434 and len(records) > 500
440 and len(records) > _FRAME_RECURSION_LIMIT
435 441
436 442 def find_recursion(etype, value, records):
437 443 """Identify the repeating stack frames from a RecursionError traceback
438 444
439 445 'records' is a list as returned by VerboseTB.get_records()
440 446
441 447 Returns (last_unique, repeat_length)
442 448 """
443 449 # This involves a bit of guesswork - we want to show enough of the traceback
444 450 # to indicate where the recursion is occurring. We guess that the innermost
445 451 # quarter of the traceback (250 frames by default) is repeats, and find the
446 452 # first frame (from in to out) that looks different.
447 453 if not is_recursion_error(etype, value, records):
448 454 return len(records), 0
449 455
450 456 # Select filename, lineno, func_name to track frames with
451 457 records = [r[1:4] for r in records]
452 458 inner_frames = records[-(len(records)//4):]
453 459 frames_repeated = set(inner_frames)
454 460
455 461 last_seen_at = {}
456 462 longest_repeat = 0
457 463 i = len(records)
458 464 for frame in reversed(records):
459 465 i -= 1
460 466 if frame not in frames_repeated:
461 467 last_unique = i
462 468 break
463 469
464 470 if frame in last_seen_at:
465 471 distance = last_seen_at[frame] - i
466 472 longest_repeat = max(longest_repeat, distance)
467 473
468 474 last_seen_at[frame] = i
469 475 else:
470 476 last_unique = 0 # The whole traceback was recursion
471 477
472 478 return last_unique, longest_repeat
473 479
474 480 #---------------------------------------------------------------------------
475 481 # Module classes
476 482 class TBTools(colorable.Colorable):
477 483 """Basic tools used by all traceback printer classes."""
478 484
479 485 # Number of frames to skip when reporting tracebacks
480 486 tb_offset = 0
481 487
482 488 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
483 489 # Whether to call the interactive pdb debugger after printing
484 490 # tracebacks or not
485 491 super(TBTools, self).__init__(parent=parent, config=config)
486 492 self.call_pdb = call_pdb
487 493
488 494 # Output stream to write to. Note that we store the original value in
489 495 # a private attribute and then make the public ostream a property, so
490 496 # that we can delay accessing sys.stdout until runtime. The way
491 497 # things are written now, the sys.stdout object is dynamically managed
492 498 # so a reference to it should NEVER be stored statically. This
493 499 # property approach confines this detail to a single location, and all
494 500 # subclasses can simply access self.ostream for writing.
495 501 self._ostream = ostream
496 502
497 503 # Create color table
498 504 self.color_scheme_table = exception_colors()
499 505
500 506 self.set_colors(color_scheme)
501 507 self.old_scheme = color_scheme # save initial value for toggles
502 508
503 509 if call_pdb:
504 510 self.pdb = debugger.Pdb()
505 511 else:
506 512 self.pdb = None
507 513
508 514 def _get_ostream(self):
509 515 """Output stream that exceptions are written to.
510 516
511 517 Valid values are:
512 518
513 519 - None: the default, which means that IPython will dynamically resolve
514 520 to sys.stdout. This ensures compatibility with most tools, including
515 521 Windows (where plain stdout doesn't recognize ANSI escapes).
516 522
517 523 - Any object with 'write' and 'flush' attributes.
518 524 """
519 525 return sys.stdout if self._ostream is None else self._ostream
520 526
521 527 def _set_ostream(self, val):
522 528 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
523 529 self._ostream = val
524 530
525 531 ostream = property(_get_ostream, _set_ostream)
526 532
527 533 def set_colors(self, *args, **kw):
528 534 """Shorthand access to the color table scheme selector method."""
529 535
530 536 # Set own color table
531 537 self.color_scheme_table.set_active_scheme(*args, **kw)
532 538 # for convenience, set Colors to the active scheme
533 539 self.Colors = self.color_scheme_table.active_colors
534 540 # Also set colors of debugger
535 541 if hasattr(self, 'pdb') and self.pdb is not None:
536 542 self.pdb.set_colors(*args, **kw)
537 543
538 544 def color_toggle(self):
539 545 """Toggle between the currently active color scheme and NoColor."""
540 546
541 547 if self.color_scheme_table.active_scheme_name == 'NoColor':
542 548 self.color_scheme_table.set_active_scheme(self.old_scheme)
543 549 self.Colors = self.color_scheme_table.active_colors
544 550 else:
545 551 self.old_scheme = self.color_scheme_table.active_scheme_name
546 552 self.color_scheme_table.set_active_scheme('NoColor')
547 553 self.Colors = self.color_scheme_table.active_colors
548 554
549 555 def stb2text(self, stb):
550 556 """Convert a structured traceback (a list) to a string."""
551 557 return '\n'.join(stb)
552 558
553 559 def text(self, etype, value, tb, tb_offset=None, context=5):
554 560 """Return formatted traceback.
555 561
556 562 Subclasses may override this if they add extra arguments.
557 563 """
558 564 tb_list = self.structured_traceback(etype, value, tb,
559 565 tb_offset, context)
560 566 return self.stb2text(tb_list)
561 567
562 568 def structured_traceback(self, etype, evalue, tb, tb_offset=None,
563 569 context=5, mode=None):
564 570 """Return a list of traceback frames.
565 571
566 572 Must be implemented by each class.
567 573 """
568 574 raise NotImplementedError()
569 575
570 576
571 577 #---------------------------------------------------------------------------
572 578 class ListTB(TBTools):
573 579 """Print traceback information from a traceback list, with optional color.
574 580
575 581 Calling requires 3 arguments: (etype, evalue, elist)
576 582 as would be obtained by::
577 583
578 584 etype, evalue, tb = sys.exc_info()
579 585 if tb:
580 586 elist = traceback.extract_tb(tb)
581 587 else:
582 588 elist = None
583 589
584 590 It can thus be used by programs which need to process the traceback before
585 591 printing (such as console replacements based on the code module from the
586 592 standard library).
587 593
588 594 Because they are meant to be called without a full traceback (only a
589 595 list), instances of this class can't call the interactive pdb debugger."""
590 596
591 597 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
592 598 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
593 599 ostream=ostream, parent=parent,config=config)
594 600
595 601 def __call__(self, etype, value, elist):
596 602 self.ostream.flush()
597 603 self.ostream.write(self.text(etype, value, elist))
598 604 self.ostream.write('\n')
599 605
600 606 def structured_traceback(self, etype, value, elist, tb_offset=None,
601 607 context=5):
602 608 """Return a color formatted string with the traceback info.
603 609
604 610 Parameters
605 611 ----------
606 612 etype : exception type
607 613 Type of the exception raised.
608 614
609 615 value : object
610 616 Data stored in the exception
611 617
612 618 elist : list
613 619 List of frames, see class docstring for details.
614 620
615 621 tb_offset : int, optional
616 622 Number of frames in the traceback to skip. If not given, the
617 623 instance value is used (set in constructor).
618 624
619 625 context : int, optional
620 626 Number of lines of context information to print.
621 627
622 628 Returns
623 629 -------
624 630 String with formatted exception.
625 631 """
626 632 tb_offset = self.tb_offset if tb_offset is None else tb_offset
627 633 Colors = self.Colors
628 634 out_list = []
629 635 if elist:
630 636
631 637 if tb_offset and len(elist) > tb_offset:
632 638 elist = elist[tb_offset:]
633 639
634 640 out_list.append('Traceback %s(most recent call last)%s:' %
635 641 (Colors.normalEm, Colors.Normal) + '\n')
636 642 out_list.extend(self._format_list(elist))
637 643 # The exception info should be a single entry in the list.
638 644 lines = ''.join(self._format_exception_only(etype, value))
639 645 out_list.append(lines)
640 646
641 647 return out_list
642 648
643 649 def _format_list(self, extracted_list):
644 650 """Format a list of traceback entry tuples for printing.
645 651
646 652 Given a list of tuples as returned by extract_tb() or
647 653 extract_stack(), return a list of strings ready for printing.
648 654 Each string in the resulting list corresponds to the item with the
649 655 same index in the argument list. Each string ends in a newline;
650 656 the strings may contain internal newlines as well, for those items
651 657 whose source text line is not None.
652 658
653 659 Lifted almost verbatim from traceback.py
654 660 """
655 661
656 662 Colors = self.Colors
657 663 list = []
658 664 for filename, lineno, name, line in extracted_list[:-1]:
659 665 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
660 666 (Colors.filename, filename, Colors.Normal,
661 667 Colors.lineno, lineno, Colors.Normal,
662 668 Colors.name, name, Colors.Normal)
663 669 if line:
664 670 item += ' %s\n' % line.strip()
665 671 list.append(item)
666 672 # Emphasize the last entry
667 673 filename, lineno, name, line = extracted_list[-1]
668 674 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
669 675 (Colors.normalEm,
670 676 Colors.filenameEm, filename, Colors.normalEm,
671 677 Colors.linenoEm, lineno, Colors.normalEm,
672 678 Colors.nameEm, name, Colors.normalEm,
673 679 Colors.Normal)
674 680 if line:
675 681 item += '%s %s%s\n' % (Colors.line, line.strip(),
676 682 Colors.Normal)
677 683 list.append(item)
678 684 return list
679 685
680 686 def _format_exception_only(self, etype, value):
681 687 """Format the exception part of a traceback.
682 688
683 689 The arguments are the exception type and value such as given by
684 690 sys.exc_info()[:2]. The return value is a list of strings, each ending
685 691 in a newline. Normally, the list contains a single string; however,
686 692 for SyntaxError exceptions, it contains several lines that (when
687 693 printed) display detailed information about where the syntax error
688 694 occurred. The message indicating which exception occurred is the
689 695 always last string in the list.
690 696
691 697 Also lifted nearly verbatim from traceback.py
692 698 """
693 699 have_filedata = False
694 700 Colors = self.Colors
695 701 list = []
696 702 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
697 703 if value is None:
698 704 # Not sure if this can still happen in Python 2.6 and above
699 705 list.append(stype + '\n')
700 706 else:
701 707 if issubclass(etype, SyntaxError):
702 708 have_filedata = True
703 709 if not value.filename: value.filename = "<string>"
704 710 if value.lineno:
705 711 lineno = value.lineno
706 712 textline = linecache.getline(value.filename, value.lineno)
707 713 else:
708 714 lineno = 'unknown'
709 715 textline = ''
710 716 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
711 717 (Colors.normalEm,
712 718 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
713 719 Colors.linenoEm, lineno, Colors.Normal ))
714 720 if textline == '':
715 721 textline = py3compat.cast_unicode(value.text, "utf-8")
716 722
717 723 if textline is not None:
718 724 i = 0
719 725 while i < len(textline) and textline[i].isspace():
720 726 i += 1
721 727 list.append('%s %s%s\n' % (Colors.line,
722 728 textline.strip(),
723 729 Colors.Normal))
724 730 if value.offset is not None:
725 731 s = ' '
726 732 for c in textline[i:value.offset - 1]:
727 733 if c.isspace():
728 734 s += c
729 735 else:
730 736 s += ' '
731 737 list.append('%s%s^%s\n' % (Colors.caret, s,
732 738 Colors.Normal))
733 739
734 740 try:
735 741 s = value.msg
736 742 except Exception:
737 743 s = self._some_str(value)
738 744 if s:
739 745 list.append('%s%s:%s %s\n' % (stype, Colors.excName,
740 746 Colors.Normal, s))
741 747 else:
742 748 list.append('%s\n' % stype)
743 749
744 750 # sync with user hooks
745 751 if have_filedata:
746 752 ipinst = get_ipython()
747 753 if ipinst is not None:
748 754 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
749 755
750 756 return list
751 757
752 758 def get_exception_only(self, etype, value):
753 759 """Only print the exception type and message, without a traceback.
754 760
755 761 Parameters
756 762 ----------
757 763 etype : exception type
758 764 value : exception value
759 765 """
760 766 return ListTB.structured_traceback(self, etype, value, [])
761 767
762 768 def show_exception_only(self, etype, evalue):
763 769 """Only print the exception type and message, without a traceback.
764 770
765 771 Parameters
766 772 ----------
767 773 etype : exception type
768 774 value : exception value
769 775 """
770 776 # This method needs to use __call__ from *this* class, not the one from
771 777 # a subclass whose signature or behavior may be different
772 778 ostream = self.ostream
773 779 ostream.flush()
774 780 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
775 781 ostream.flush()
776 782
777 783 def _some_str(self, value):
778 784 # Lifted from traceback.py
779 785 try:
780 786 return py3compat.cast_unicode(str(value))
781 787 except:
782 788 return u'<unprintable %s object>' % type(value).__name__
783 789
784 790
785 791 #----------------------------------------------------------------------------
786 792 class VerboseTB(TBTools):
787 793 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
788 794 of HTML. Requires inspect and pydoc. Crazy, man.
789 795
790 796 Modified version which optionally strips the topmost entries from the
791 797 traceback, to be used with alternate interpreters (because their own code
792 798 would appear in the traceback)."""
793 799
794 800 def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
795 801 tb_offset=0, long_header=False, include_vars=True,
796 802 check_cache=None, debugger_cls = None,
797 803 parent=None, config=None):
798 804 """Specify traceback offset, headers and color scheme.
799 805
800 806 Define how many frames to drop from the tracebacks. Calling it with
801 807 tb_offset=1 allows use of this handler in interpreters which will have
802 808 their own code at the top of the traceback (VerboseTB will first
803 809 remove that frame before printing the traceback info)."""
804 810 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
805 811 ostream=ostream, parent=parent, config=config)
806 812 self.tb_offset = tb_offset
807 813 self.long_header = long_header
808 814 self.include_vars = include_vars
809 815 # By default we use linecache.checkcache, but the user can provide a
810 816 # different check_cache implementation. This is used by the IPython
811 817 # kernel to provide tracebacks for interactive code that is cached,
812 818 # by a compiler instance that flushes the linecache but preserves its
813 819 # own code cache.
814 820 if check_cache is None:
815 821 check_cache = linecache.checkcache
816 822 self.check_cache = check_cache
817 823
818 824 self.debugger_cls = debugger_cls or debugger.Pdb
819 825
820 826 def format_records(self, records, last_unique, recursion_repeat):
821 827 """Format the stack frames of the traceback"""
822 828 frames = []
823 829 for r in records[:last_unique+recursion_repeat+1]:
824 830 #print '*** record:',file,lnum,func,lines,index # dbg
825 831 frames.append(self.format_record(*r))
826 832
827 833 if recursion_repeat:
828 834 frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat)
829 835 frames.append(self.format_record(*records[last_unique+recursion_repeat+1]))
830 836
831 837 return frames
832 838
833 839 def format_record(self, frame, file, lnum, func, lines, index):
834 840 """Format a single stack frame"""
835 841 Colors = self.Colors # just a shorthand + quicker name lookup
836 842 ColorsNormal = Colors.Normal # used a lot
837 843 col_scheme = self.color_scheme_table.active_scheme_name
838 844 indent = ' ' * INDENT_SIZE
839 845 em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
840 846 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
841 847 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
842 848 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
843 849 ColorsNormal)
844 850 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
845 851 (Colors.vName, Colors.valEm, ColorsNormal)
846 852 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
847 853 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
848 854 Colors.vName, ColorsNormal)
849 855 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
850 856
851 857 if not file:
852 858 file = '?'
853 859 elif file.startswith(str("<")) and file.endswith(str(">")):
854 860 # Not a real filename, no problem...
855 861 pass
856 862 elif not os.path.isabs(file):
857 863 # Try to make the filename absolute by trying all
858 864 # sys.path entries (which is also what linecache does)
859 865 for dirname in sys.path:
860 866 try:
861 867 fullname = os.path.join(dirname, file)
862 868 if os.path.isfile(fullname):
863 869 file = os.path.abspath(fullname)
864 870 break
865 871 except Exception:
866 872 # Just in case that sys.path contains very
867 873 # strange entries...
868 874 pass
869 875
870 876 file = py3compat.cast_unicode(file, util_path.fs_encoding)
871 877 link = tpl_link % util_path.compress_user(file)
872 878 args, varargs, varkw, locals_ = inspect.getargvalues(frame)
873 879
874 880 if func == '?':
875 881 call = ''
876 882 elif func == '<module>':
877 883 call = tpl_call % (func, '')
878 884 else:
879 885 # Decide whether to include variable details or not
880 886 var_repr = eqrepr if self.include_vars else nullrepr
881 887 try:
882 888 call = tpl_call % (func, inspect.formatargvalues(args,
883 889 varargs, varkw,
884 890 locals_, formatvalue=var_repr))
885 891 except KeyError:
886 892 # This happens in situations like errors inside generator
887 893 # expressions, where local variables are listed in the
888 894 # line, but can't be extracted from the frame. I'm not
889 895 # 100% sure this isn't actually a bug in inspect itself,
890 896 # but since there's no info for us to compute with, the
891 897 # best we can do is report the failure and move on. Here
892 898 # we must *not* call any traceback construction again,
893 899 # because that would mess up use of %debug later on. So we
894 900 # simply report the failure and move on. The only
895 901 # limitation will be that this frame won't have locals
896 902 # listed in the call signature. Quite subtle problem...
897 903 # I can't think of a good way to validate this in a unit
898 904 # test, but running a script consisting of:
899 905 # dict( (k,v.strip()) for (k,v) in range(10) )
900 906 # will illustrate the error, if this exception catch is
901 907 # disabled.
902 908 call = tpl_call_fail % func
903 909
904 910 # Don't attempt to tokenize binary files.
905 911 if file.endswith(('.so', '.pyd', '.dll')):
906 912 return '%s %s\n' % (link, call)
907 913
908 914 elif file.endswith(('.pyc', '.pyo')):
909 915 # Look up the corresponding source file.
910 916 try:
911 917 file = source_from_cache(file)
912 918 except ValueError:
913 919 # Failed to get the source file for some reason
914 920 # E.g. https://github.com/ipython/ipython/issues/9486
915 921 return '%s %s\n' % (link, call)
916 922
917 923 def linereader(file=file, lnum=[lnum], getline=linecache.getline):
918 924 line = getline(file, lnum[0])
919 925 lnum[0] += 1
920 926 return line
921 927
922 928 # Build the list of names on this line of code where the exception
923 929 # occurred.
924 930 try:
925 931 names = []
926 932 name_cont = False
927 933
928 934 for token_type, token, start, end, line in generate_tokens(linereader):
929 935 # build composite names
930 936 if token_type == tokenize.NAME and token not in keyword.kwlist:
931 937 if name_cont:
932 938 # Continuation of a dotted name
933 939 try:
934 940 names[-1].append(token)
935 941 except IndexError:
936 942 names.append([token])
937 943 name_cont = False
938 944 else:
939 945 # Regular new names. We append everything, the caller
940 946 # will be responsible for pruning the list later. It's
941 947 # very tricky to try to prune as we go, b/c composite
942 948 # names can fool us. The pruning at the end is easy
943 949 # to do (or the caller can print a list with repeated
944 950 # names if so desired.
945 951 names.append([token])
946 952 elif token == '.':
947 953 name_cont = True
948 954 elif token_type == tokenize.NEWLINE:
949 955 break
950 956
951 957 except (IndexError, UnicodeDecodeError, SyntaxError):
952 958 # signals exit of tokenizer
953 959 # SyntaxError can occur if the file is not actually Python
954 960 # - see gh-6300
955 961 pass
956 962 except tokenize.TokenError as msg:
957 963 # Tokenizing may fail for various reasons, many of which are
958 964 # harmless. (A good example is when the line in question is the
959 965 # close of a triple-quoted string, cf gh-6864). We don't want to
960 966 # show this to users, but want make it available for debugging
961 967 # purposes.
962 968 _m = ("An unexpected error occurred while tokenizing input\n"
963 969 "The following traceback may be corrupted or invalid\n"
964 970 "The error message is: %s\n" % msg)
965 971 debug(_m)
966 972
967 973 # Join composite names (e.g. "dict.fromkeys")
968 974 names = ['.'.join(n) for n in names]
969 975 # prune names list of duplicates, but keep the right order
970 976 unique_names = uniq_stable(names)
971 977
972 978 # Start loop over vars
973 979 lvals = ''
974 980 lvals_list = []
975 981 if self.include_vars:
976 982 for name_full in unique_names:
977 983 name_base = name_full.split('.', 1)[0]
978 984 if name_base in frame.f_code.co_varnames:
979 985 if name_base in locals_:
980 986 try:
981 987 value = repr(eval(name_full, locals_))
982 988 except:
983 989 value = undefined
984 990 else:
985 991 value = undefined
986 992 name = tpl_local_var % name_full
987 993 else:
988 994 if name_base in frame.f_globals:
989 995 try:
990 996 value = repr(eval(name_full, frame.f_globals))
991 997 except:
992 998 value = undefined
993 999 else:
994 1000 value = undefined
995 1001 name = tpl_global_var % name_full
996 1002 lvals_list.append(tpl_name_val % (name, value))
997 1003 if lvals_list:
998 1004 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
999 1005
1000 1006 level = '%s %s\n' % (link, call)
1001 1007
1002 1008 if index is None:
1003 1009 return level
1004 1010 else:
1005 1011 _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2
1006 1012 return '%s%s' % (level, ''.join(
1007 1013 _format_traceback_lines(lnum, index, lines, Colors, lvals,
1008 1014 _line_format)))
1009 1015
1010 1016 def prepare_chained_exception_message(self, cause):
1011 1017 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
1012 1018 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
1013 1019
1014 1020 if cause:
1015 1021 message = [[direct_cause]]
1016 1022 else:
1017 1023 message = [[exception_during_handling]]
1018 1024 return message
1019 1025
1020 1026 def prepare_header(self, etype, long_version=False):
1021 1027 colors = self.Colors # just a shorthand + quicker name lookup
1022 1028 colorsnormal = colors.Normal # used a lot
1023 1029 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
1024 1030 width = min(75, get_terminal_size()[0])
1025 1031 if long_version:
1026 1032 # Header with the exception type, python version, and date
1027 1033 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
1028 1034 date = time.ctime(time.time())
1029 1035
1030 1036 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal,
1031 1037 exc, ' ' * (width - len(str(etype)) - len(pyver)),
1032 1038 pyver, date.rjust(width) )
1033 1039 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
1034 1040 "\ncalls leading up to the error, with the most recent (innermost) call last."
1035 1041 else:
1036 1042 # Simplified header
1037 1043 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
1038 1044 rjust(width - len(str(etype))) )
1039 1045
1040 1046 return head
1041 1047
1042 1048 def format_exception(self, etype, evalue):
1043 1049 colors = self.Colors # just a shorthand + quicker name lookup
1044 1050 colorsnormal = colors.Normal # used a lot
1045 1051 # Get (safely) a string form of the exception info
1046 1052 try:
1047 1053 etype_str, evalue_str = map(str, (etype, evalue))
1048 1054 except:
1049 1055 # User exception is improperly defined.
1050 1056 etype, evalue = str, sys.exc_info()[:2]
1051 1057 etype_str, evalue_str = map(str, (etype, evalue))
1052 1058 # ... and format it
1053 1059 return ['%s%s%s: %s' % (colors.excName, etype_str,
1054 1060 colorsnormal, py3compat.cast_unicode(evalue_str))]
1055 1061
1056 1062 def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset):
1057 1063 """Formats the header, traceback and exception message for a single exception.
1058 1064
1059 1065 This may be called multiple times by Python 3 exception chaining
1060 1066 (PEP 3134).
1061 1067 """
1062 1068 # some locals
1063 1069 orig_etype = etype
1064 1070 try:
1065 1071 etype = etype.__name__
1066 1072 except AttributeError:
1067 1073 pass
1068 1074
1069 1075 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1070 1076 head = self.prepare_header(etype, self.long_header)
1071 1077 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
1072 1078
1073 1079 if records is None:
1074 1080 return ""
1075 1081
1076 1082 last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records)
1077 1083
1078 1084 frames = self.format_records(records, last_unique, recursion_repeat)
1079 1085
1080 1086 formatted_exception = self.format_exception(etype, evalue)
1081 1087 if records:
1082 1088 filepath, lnum = records[-1][1:3]
1083 1089 filepath = os.path.abspath(filepath)
1084 1090 ipinst = get_ipython()
1085 1091 if ipinst is not None:
1086 1092 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
1087 1093
1088 1094 return [[head] + frames + [''.join(formatted_exception[0])]]
1089 1095
1090 1096 def get_records(self, etb, number_of_lines_of_context, tb_offset):
1091 1097 try:
1092 1098 # Try the default getinnerframes and Alex's: Alex's fixes some
1093 1099 # problems, but it generates empty tracebacks for console errors
1094 1100 # (5 blanks lines) where none should be returned.
1095 1101 return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset)
1096 1102 except UnicodeDecodeError:
1097 1103 # This can occur if a file's encoding magic comment is wrong.
1098 1104 # I can't see a way to recover without duplicating a bunch of code
1099 1105 # from the stdlib traceback module. --TK
1100 1106 error('\nUnicodeDecodeError while processing traceback.\n')
1101 1107 return None
1102 1108 except:
1103 1109 # FIXME: I've been getting many crash reports from python 2.3
1104 1110 # users, traceable to inspect.py. If I can find a small test-case
1105 1111 # to reproduce this, I should either write a better workaround or
1106 1112 # file a bug report against inspect (if that's the real problem).
1107 1113 # So far, I haven't been able to find an isolated example to
1108 1114 # reproduce the problem.
1109 1115 inspect_error()
1110 1116 traceback.print_exc(file=self.ostream)
1111 1117 info('\nUnfortunately, your original traceback can not be constructed.\n')
1112 1118 return None
1113 1119
1114 1120 def get_parts_of_chained_exception(self, evalue):
1115 1121 def get_chained_exception(exception_value):
1116 1122 cause = getattr(exception_value, '__cause__', None)
1117 1123 if cause:
1118 1124 return cause
1119 1125 if getattr(exception_value, '__suppress_context__', False):
1120 1126 return None
1121 1127 return getattr(exception_value, '__context__', None)
1122 1128
1123 1129 chained_evalue = get_chained_exception(evalue)
1124 1130
1125 1131 if chained_evalue:
1126 1132 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
1127 1133
1128 1134 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
1129 1135 number_of_lines_of_context=5):
1130 1136 """Return a nice text document describing the traceback."""
1131 1137
1132 1138 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1133 1139 tb_offset)
1134 1140
1135 1141 colors = self.Colors # just a shorthand + quicker name lookup
1136 1142 colorsnormal = colors.Normal # used a lot
1137 1143 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1138 1144 structured_traceback_parts = [head]
1139 1145 chained_exceptions_tb_offset = 0
1140 1146 lines_of_context = 3
1141 1147 formatted_exceptions = formatted_exception
1142 1148 exception = self.get_parts_of_chained_exception(evalue)
1143 1149 if exception:
1144 1150 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1145 1151 etype, evalue, etb = exception
1146 1152 else:
1147 1153 evalue = None
1148 1154 chained_exc_ids = set()
1149 1155 while evalue:
1150 1156 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1151 1157 chained_exceptions_tb_offset)
1152 1158 exception = self.get_parts_of_chained_exception(evalue)
1153 1159
1154 1160 if exception and not id(exception[1]) in chained_exc_ids:
1155 1161 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1156 1162 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1157 1163 etype, evalue, etb = exception
1158 1164 else:
1159 1165 evalue = None
1160 1166
1161 1167 # we want to see exceptions in a reversed order:
1162 1168 # the first exception should be on top
1163 1169 for formatted_exception in reversed(formatted_exceptions):
1164 1170 structured_traceback_parts += formatted_exception
1165 1171
1166 1172 return structured_traceback_parts
1167 1173
1168 1174 def debugger(self, force=False):
1169 1175 """Call up the pdb debugger if desired, always clean up the tb
1170 1176 reference.
1171 1177
1172 1178 Keywords:
1173 1179
1174 1180 - force(False): by default, this routine checks the instance call_pdb
1175 1181 flag and does not actually invoke the debugger if the flag is false.
1176 1182 The 'force' option forces the debugger to activate even if the flag
1177 1183 is false.
1178 1184
1179 1185 If the call_pdb flag is set, the pdb interactive debugger is
1180 1186 invoked. In all cases, the self.tb reference to the current traceback
1181 1187 is deleted to prevent lingering references which hamper memory
1182 1188 management.
1183 1189
1184 1190 Note that each call to pdb() does an 'import readline', so if your app
1185 1191 requires a special setup for the readline completers, you'll have to
1186 1192 fix that by hand after invoking the exception handler."""
1187 1193
1188 1194 if force or self.call_pdb:
1189 1195 if self.pdb is None:
1190 1196 self.pdb = self.debugger_cls()
1191 1197 # the system displayhook may have changed, restore the original
1192 1198 # for pdb
1193 1199 display_trap = DisplayTrap(hook=sys.__displayhook__)
1194 1200 with display_trap:
1195 1201 self.pdb.reset()
1196 1202 # Find the right frame so we don't pop up inside ipython itself
1197 1203 if hasattr(self, 'tb') and self.tb is not None:
1198 1204 etb = self.tb
1199 1205 else:
1200 1206 etb = self.tb = sys.last_traceback
1201 1207 while self.tb is not None and self.tb.tb_next is not None:
1202 1208 self.tb = self.tb.tb_next
1203 1209 if etb and etb.tb_next:
1204 1210 etb = etb.tb_next
1205 1211 self.pdb.botframe = etb.tb_frame
1206 1212 self.pdb.interaction(None, etb)
1207 1213
1208 1214 if hasattr(self, 'tb'):
1209 1215 del self.tb
1210 1216
1211 1217 def handler(self, info=None):
1212 1218 (etype, evalue, etb) = info or sys.exc_info()
1213 1219 self.tb = etb
1214 1220 ostream = self.ostream
1215 1221 ostream.flush()
1216 1222 ostream.write(self.text(etype, evalue, etb))
1217 1223 ostream.write('\n')
1218 1224 ostream.flush()
1219 1225
1220 1226 # Changed so an instance can just be called as VerboseTB_inst() and print
1221 1227 # out the right info on its own.
1222 1228 def __call__(self, etype=None, evalue=None, etb=None):
1223 1229 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1224 1230 if etb is None:
1225 1231 self.handler()
1226 1232 else:
1227 1233 self.handler((etype, evalue, etb))
1228 1234 try:
1229 1235 self.debugger()
1230 1236 except KeyboardInterrupt:
1231 1237 print("\nKeyboardInterrupt")
1232 1238
1233 1239
1234 1240 #----------------------------------------------------------------------------
1235 1241 class FormattedTB(VerboseTB, ListTB):
1236 1242 """Subclass ListTB but allow calling with a traceback.
1237 1243
1238 1244 It can thus be used as a sys.excepthook for Python > 2.1.
1239 1245
1240 1246 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1241 1247
1242 1248 Allows a tb_offset to be specified. This is useful for situations where
1243 1249 one needs to remove a number of topmost frames from the traceback (such as
1244 1250 occurs with python programs that themselves execute other python code,
1245 1251 like Python shells). """
1246 1252
1247 1253 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1248 1254 ostream=None,
1249 1255 tb_offset=0, long_header=False, include_vars=False,
1250 1256 check_cache=None, debugger_cls=None,
1251 1257 parent=None, config=None):
1252 1258
1253 1259 # NEVER change the order of this list. Put new modes at the end:
1254 1260 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1255 1261 self.verbose_modes = self.valid_modes[1:3]
1256 1262
1257 1263 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1258 1264 ostream=ostream, tb_offset=tb_offset,
1259 1265 long_header=long_header, include_vars=include_vars,
1260 1266 check_cache=check_cache, debugger_cls=debugger_cls,
1261 1267 parent=parent, config=config)
1262 1268
1263 1269 # Different types of tracebacks are joined with different separators to
1264 1270 # form a single string. They are taken from this dict
1265 1271 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1266 1272 Minimal='')
1267 1273 # set_mode also sets the tb_join_char attribute
1268 1274 self.set_mode(mode)
1269 1275
1270 1276 def _extract_tb(self, tb):
1271 1277 if tb:
1272 1278 return traceback.extract_tb(tb)
1273 1279 else:
1274 1280 return None
1275 1281
1276 1282 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1277 1283 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1278 1284 mode = self.mode
1279 1285 if mode in self.verbose_modes:
1280 1286 # Verbose modes need a full traceback
1281 1287 return VerboseTB.structured_traceback(
1282 1288 self, etype, value, tb, tb_offset, number_of_lines_of_context
1283 1289 )
1284 1290 elif mode == 'Minimal':
1285 1291 return ListTB.get_exception_only(self, etype, value)
1286 1292 else:
1287 1293 # We must check the source cache because otherwise we can print
1288 1294 # out-of-date source code.
1289 1295 self.check_cache()
1290 1296 # Now we can extract and format the exception
1291 1297 elist = self._extract_tb(tb)
1292 1298 return ListTB.structured_traceback(
1293 1299 self, etype, value, elist, tb_offset, number_of_lines_of_context
1294 1300 )
1295 1301
1296 1302 def stb2text(self, stb):
1297 1303 """Convert a structured traceback (a list) to a string."""
1298 1304 return self.tb_join_char.join(stb)
1299 1305
1300 1306
1301 1307 def set_mode(self, mode=None):
1302 1308 """Switch to the desired mode.
1303 1309
1304 1310 If mode is not specified, cycles through the available modes."""
1305 1311
1306 1312 if not mode:
1307 1313 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1308 1314 len(self.valid_modes)
1309 1315 self.mode = self.valid_modes[new_idx]
1310 1316 elif mode not in self.valid_modes:
1311 1317 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
1312 1318 'Valid modes: ' + str(self.valid_modes))
1313 1319 else:
1314 1320 self.mode = mode
1315 1321 # include variable details only in 'Verbose' mode
1316 1322 self.include_vars = (self.mode == self.valid_modes[2])
1317 1323 # Set the join character for generating text tracebacks
1318 1324 self.tb_join_char = self._join_chars[self.mode]
1319 1325
1320 1326 # some convenient shortcuts
1321 1327 def plain(self):
1322 1328 self.set_mode(self.valid_modes[0])
1323 1329
1324 1330 def context(self):
1325 1331 self.set_mode(self.valid_modes[1])
1326 1332
1327 1333 def verbose(self):
1328 1334 self.set_mode(self.valid_modes[2])
1329 1335
1330 1336 def minimal(self):
1331 1337 self.set_mode(self.valid_modes[3])
1332 1338
1333 1339
1334 1340 #----------------------------------------------------------------------------
1335 1341 class AutoFormattedTB(FormattedTB):
1336 1342 """A traceback printer which can be called on the fly.
1337 1343
1338 1344 It will find out about exceptions by itself.
1339 1345
1340 1346 A brief example::
1341 1347
1342 1348 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1343 1349 try:
1344 1350 ...
1345 1351 except:
1346 1352 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1347 1353 """
1348 1354
1349 1355 def __call__(self, etype=None, evalue=None, etb=None,
1350 1356 out=None, tb_offset=None):
1351 1357 """Print out a formatted exception traceback.
1352 1358
1353 1359 Optional arguments:
1354 1360 - out: an open file-like object to direct output to.
1355 1361
1356 1362 - tb_offset: the number of frames to skip over in the stack, on a
1357 1363 per-call basis (this overrides temporarily the instance's tb_offset
1358 1364 given at initialization time. """
1359 1365
1360 1366 if out is None:
1361 1367 out = self.ostream
1362 1368 out.flush()
1363 1369 out.write(self.text(etype, evalue, etb, tb_offset))
1364 1370 out.write('\n')
1365 1371 out.flush()
1366 1372 # FIXME: we should remove the auto pdb behavior from here and leave
1367 1373 # that to the clients.
1368 1374 try:
1369 1375 self.debugger()
1370 1376 except KeyboardInterrupt:
1371 1377 print("\nKeyboardInterrupt")
1372 1378
1373 1379 def structured_traceback(self, etype=None, value=None, tb=None,
1374 1380 tb_offset=None, number_of_lines_of_context=5):
1375 1381 if etype is None:
1376 1382 etype, value, tb = sys.exc_info()
1377 1383 self.tb = tb
1378 1384 return FormattedTB.structured_traceback(
1379 1385 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1380 1386
1381 1387
1382 1388 #---------------------------------------------------------------------------
1383 1389
1384 1390 # A simple class to preserve Nathan's original functionality.
1385 1391 class ColorTB(FormattedTB):
1386 1392 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1387 1393
1388 1394 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1389 1395 FormattedTB.__init__(self, color_scheme=color_scheme,
1390 1396 call_pdb=call_pdb, **kwargs)
1391 1397
1392 1398
1393 1399 class SyntaxTB(ListTB):
1394 1400 """Extension which holds some state: the last exception value"""
1395 1401
1396 1402 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1397 1403 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1398 1404 self.last_syntax_error = None
1399 1405
1400 1406 def __call__(self, etype, value, elist):
1401 1407 self.last_syntax_error = value
1402 1408
1403 1409 ListTB.__call__(self, etype, value, elist)
1404 1410
1405 1411 def structured_traceback(self, etype, value, elist, tb_offset=None,
1406 1412 context=5):
1407 1413 # If the source file has been edited, the line in the syntax error can
1408 1414 # be wrong (retrieved from an outdated cache). This replaces it with
1409 1415 # the current value.
1410 1416 if isinstance(value, SyntaxError) \
1411 1417 and isinstance(value.filename, str) \
1412 1418 and isinstance(value.lineno, int):
1413 1419 linecache.checkcache(value.filename)
1414 1420 newtext = linecache.getline(value.filename, value.lineno)
1415 1421 if newtext:
1416 1422 value.text = newtext
1417 1423 self.last_syntax_error = value
1418 1424 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1419 1425 tb_offset=tb_offset, context=context)
1420 1426
1421 1427 def clear_err_state(self):
1422 1428 """Return the current error state and clear it"""
1423 1429 e = self.last_syntax_error
1424 1430 self.last_syntax_error = None
1425 1431 return e
1426 1432
1427 1433 def stb2text(self, stb):
1428 1434 """Convert a structured traceback (a list) to a string."""
1429 1435 return ''.join(stb)
1430 1436
1431 1437
1432 1438 # some internal-use functions
1433 1439 def text_repr(value):
1434 1440 """Hopefully pretty robust repr equivalent."""
1435 1441 # this is pretty horrible but should always return *something*
1436 1442 try:
1437 1443 return pydoc.text.repr(value)
1438 1444 except KeyboardInterrupt:
1439 1445 raise
1440 1446 except:
1441 1447 try:
1442 1448 return repr(value)
1443 1449 except KeyboardInterrupt:
1444 1450 raise
1445 1451 except:
1446 1452 try:
1447 1453 # all still in an except block so we catch
1448 1454 # getattr raising
1449 1455 name = getattr(value, '__name__', None)
1450 1456 if name:
1451 1457 # ick, recursion
1452 1458 return text_repr(name)
1453 1459 klass = getattr(value, '__class__', None)
1454 1460 if klass:
1455 1461 return '%s instance' % text_repr(klass)
1456 1462 except KeyboardInterrupt:
1457 1463 raise
1458 1464 except:
1459 1465 return 'UNRECOVERABLE REPR FAILURE'
1460 1466
1461 1467
1462 1468 def eqrepr(value, repr=text_repr):
1463 1469 return '=%s' % repr(value)
1464 1470
1465 1471
1466 1472 def nullrepr(value, repr=text_repr):
1467 1473 return ''
@@ -1,50 +1,53 b''
1 1 import tempfile, os
2 2
3 3 from traitlets.config.loader import Config
4 4 import nose.tools as nt
5 5
6 ip = get_ipython()
7 ip.magic('load_ext storemagic')
6
7 def setup_module():
8 ip.magic('load_ext storemagic')
8 9
9 10 def test_store_restore():
11 assert 'bar' not in ip.user_ns, "Error: some other test leaked `bar` in user_ns"
12 assert 'foo' not in ip.user_ns, "Error: some other test leaked `foo` in user_ns"
10 13 ip.user_ns['foo'] = 78
11 14 ip.magic('alias bar echo "hello"')
12 15 tmpd = tempfile.mkdtemp()
13 16 ip.magic('cd ' + tmpd)
14 17 ip.magic('store foo')
15 18 ip.magic('store bar')
16 19
17 20 # Check storing
18 21 nt.assert_equal(ip.db['autorestore/foo'], 78)
19 22 nt.assert_in('bar', ip.db['stored_aliases'])
20 23
21 24 # Remove those items
22 25 ip.user_ns.pop('foo', None)
23 26 ip.alias_manager.undefine_alias('bar')
24 27 ip.magic('cd -')
25 28 ip.user_ns['_dh'][:] = []
26 29
27 30 # Check restoring
28 31 ip.magic('store -r')
29 32 nt.assert_equal(ip.user_ns['foo'], 78)
30 33 assert ip.alias_manager.is_alias('bar')
31 34 nt.assert_in(os.path.realpath(tmpd), ip.user_ns['_dh'])
32 35
33 36 os.rmdir(tmpd)
34 37
35 38 def test_autorestore():
36 39 ip.user_ns['foo'] = 95
37 40 ip.magic('store foo')
38 41 del ip.user_ns['foo']
39 42 c = Config()
40 43 c.StoreMagics.autorestore = False
41 44 orig_config = ip.config
42 45 try:
43 46 ip.config = c
44 47 ip.extension_manager.reload_extension('storemagic')
45 48 nt.assert_not_in('foo', ip.user_ns)
46 49 c.StoreMagics.autorestore = True
47 50 ip.extension_manager.reload_extension('storemagic')
48 51 nt.assert_equal(ip.user_ns['foo'], 95)
49 52 finally:
50 53 ip.config = orig_config
General Comments 0
You need to be logged in to leave comments. Login now