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