##// END OF EJS Templates
Add test for issue #11615
takuya fujiwara -
Show More
@@ -1,1042 +1,1060 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 from unittest import mock
20 from unittest import mock
21
21
22 from os.path import join
22 from os.path import join
23
23
24 import nose.tools as nt
24 import nose.tools as nt
25
25
26 from IPython.core.error import InputRejected
26 from IPython.core.error import InputRejected
27 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core.inputtransformer import InputTransformer
28 from IPython.core import interactiveshell
28 from IPython.core import interactiveshell
29 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
31 )
31 )
32 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
33 from IPython.utils.process import find_cmd
33 from IPython.utils.process import find_cmd
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Globals
36 # Globals
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # This is used by every single test, no point repeating it ad nauseam
38 # This is used by every single test, no point repeating it ad nauseam
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Tests
41 # Tests
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 class DerivedInterrupt(KeyboardInterrupt):
44 class DerivedInterrupt(KeyboardInterrupt):
45 pass
45 pass
46
46
47 class InteractiveShellTestCase(unittest.TestCase):
47 class InteractiveShellTestCase(unittest.TestCase):
48 def test_naked_string_cells(self):
48 def test_naked_string_cells(self):
49 """Test that cells with only naked strings are fully executed"""
49 """Test that cells with only naked strings are fully executed"""
50 # First, single-line inputs
50 # First, single-line inputs
51 ip.run_cell('"a"\n')
51 ip.run_cell('"a"\n')
52 self.assertEqual(ip.user_ns['_'], 'a')
52 self.assertEqual(ip.user_ns['_'], 'a')
53 # And also multi-line cells
53 # And also multi-line cells
54 ip.run_cell('"""a\nb"""\n')
54 ip.run_cell('"""a\nb"""\n')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
56
56
57 def test_run_empty_cell(self):
57 def test_run_empty_cell(self):
58 """Just make sure we don't get a horrible error with a blank
58 """Just make sure we don't get a horrible error with a blank
59 cell of input. Yes, I did overlook that."""
59 cell of input. Yes, I did overlook that."""
60 old_xc = ip.execution_count
60 old_xc = ip.execution_count
61 res = ip.run_cell('')
61 res = ip.run_cell('')
62 self.assertEqual(ip.execution_count, old_xc)
62 self.assertEqual(ip.execution_count, old_xc)
63 self.assertEqual(res.execution_count, None)
63 self.assertEqual(res.execution_count, None)
64
64
65 def test_run_cell_multiline(self):
65 def test_run_cell_multiline(self):
66 """Multi-block, multi-line cells must execute correctly.
66 """Multi-block, multi-line cells must execute correctly.
67 """
67 """
68 src = '\n'.join(["x=1",
68 src = '\n'.join(["x=1",
69 "y=2",
69 "y=2",
70 "if 1:",
70 "if 1:",
71 " x += 1",
71 " x += 1",
72 " y += 1",])
72 " y += 1",])
73 res = ip.run_cell(src)
73 res = ip.run_cell(src)
74 self.assertEqual(ip.user_ns['x'], 2)
74 self.assertEqual(ip.user_ns['x'], 2)
75 self.assertEqual(ip.user_ns['y'], 3)
75 self.assertEqual(ip.user_ns['y'], 3)
76 self.assertEqual(res.success, True)
76 self.assertEqual(res.success, True)
77 self.assertEqual(res.result, None)
77 self.assertEqual(res.result, None)
78
78
79 def test_multiline_string_cells(self):
79 def test_multiline_string_cells(self):
80 "Code sprinkled with multiline strings should execute (GH-306)"
80 "Code sprinkled with multiline strings should execute (GH-306)"
81 ip.run_cell('tmp=0')
81 ip.run_cell('tmp=0')
82 self.assertEqual(ip.user_ns['tmp'], 0)
82 self.assertEqual(ip.user_ns['tmp'], 0)
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
84 self.assertEqual(ip.user_ns['tmp'], 1)
84 self.assertEqual(ip.user_ns['tmp'], 1)
85 self.assertEqual(res.success, True)
85 self.assertEqual(res.success, True)
86 self.assertEqual(res.result, "a\nb")
86 self.assertEqual(res.result, "a\nb")
87
87
88 def test_dont_cache_with_semicolon(self):
88 def test_dont_cache_with_semicolon(self):
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
90 oldlen = len(ip.user_ns['Out'])
90 oldlen = len(ip.user_ns['Out'])
91 for cell in ['1;', '1;1;']:
91 for cell in ['1;', '1;1;']:
92 res = ip.run_cell(cell, store_history=True)
92 res = ip.run_cell(cell, store_history=True)
93 newlen = len(ip.user_ns['Out'])
93 newlen = len(ip.user_ns['Out'])
94 self.assertEqual(oldlen, newlen)
94 self.assertEqual(oldlen, newlen)
95 self.assertIsNone(res.result)
95 self.assertIsNone(res.result)
96 i = 0
96 i = 0
97 #also test the default caching behavior
97 #also test the default caching behavior
98 for cell in ['1', '1;1']:
98 for cell in ['1', '1;1']:
99 ip.run_cell(cell, store_history=True)
99 ip.run_cell(cell, store_history=True)
100 newlen = len(ip.user_ns['Out'])
100 newlen = len(ip.user_ns['Out'])
101 i += 1
101 i += 1
102 self.assertEqual(oldlen+i, newlen)
102 self.assertEqual(oldlen+i, newlen)
103
103
104 def test_syntax_error(self):
104 def test_syntax_error(self):
105 res = ip.run_cell("raise = 3")
105 res = ip.run_cell("raise = 3")
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
107
107
108 def test_In_variable(self):
108 def test_In_variable(self):
109 "Verify that In variable grows with user input (GH-284)"
109 "Verify that In variable grows with user input (GH-284)"
110 oldlen = len(ip.user_ns['In'])
110 oldlen = len(ip.user_ns['In'])
111 ip.run_cell('1;', store_history=True)
111 ip.run_cell('1;', store_history=True)
112 newlen = len(ip.user_ns['In'])
112 newlen = len(ip.user_ns['In'])
113 self.assertEqual(oldlen+1, newlen)
113 self.assertEqual(oldlen+1, newlen)
114 self.assertEqual(ip.user_ns['In'][-1],'1;')
114 self.assertEqual(ip.user_ns['In'][-1],'1;')
115
115
116 def test_magic_names_in_string(self):
116 def test_magic_names_in_string(self):
117 ip.run_cell('a = """\n%exit\n"""')
117 ip.run_cell('a = """\n%exit\n"""')
118 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
118 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
119
119
120 def test_trailing_newline(self):
120 def test_trailing_newline(self):
121 """test that running !(command) does not raise a SyntaxError"""
121 """test that running !(command) does not raise a SyntaxError"""
122 ip.run_cell('!(true)\n', False)
122 ip.run_cell('!(true)\n', False)
123 ip.run_cell('!(true)\n\n\n', False)
123 ip.run_cell('!(true)\n\n\n', False)
124
124
125 def test_gh_597(self):
125 def test_gh_597(self):
126 """Pretty-printing lists of objects with non-ascii reprs may cause
126 """Pretty-printing lists of objects with non-ascii reprs may cause
127 problems."""
127 problems."""
128 class Spam(object):
128 class Spam(object):
129 def __repr__(self):
129 def __repr__(self):
130 return "\xe9"*50
130 return "\xe9"*50
131 import IPython.core.formatters
131 import IPython.core.formatters
132 f = IPython.core.formatters.PlainTextFormatter()
132 f = IPython.core.formatters.PlainTextFormatter()
133 f([Spam(),Spam()])
133 f([Spam(),Spam()])
134
134
135
135
136 def test_future_flags(self):
136 def test_future_flags(self):
137 """Check that future flags are used for parsing code (gh-777)"""
137 """Check that future flags are used for parsing code (gh-777)"""
138 ip.run_cell('from __future__ import barry_as_FLUFL')
138 ip.run_cell('from __future__ import barry_as_FLUFL')
139 try:
139 try:
140 ip.run_cell('prfunc_return_val = 1 <> 2')
140 ip.run_cell('prfunc_return_val = 1 <> 2')
141 assert 'prfunc_return_val' in ip.user_ns
141 assert 'prfunc_return_val' in ip.user_ns
142 finally:
142 finally:
143 # Reset compiler flags so we don't mess up other tests.
143 # Reset compiler flags so we don't mess up other tests.
144 ip.compile.reset_compiler_flags()
144 ip.compile.reset_compiler_flags()
145
145
146 def test_can_pickle(self):
146 def test_can_pickle(self):
147 "Can we pickle objects defined interactively (GH-29)"
147 "Can we pickle objects defined interactively (GH-29)"
148 ip = get_ipython()
148 ip = get_ipython()
149 ip.reset()
149 ip.reset()
150 ip.run_cell(("class Mylist(list):\n"
150 ip.run_cell(("class Mylist(list):\n"
151 " def __init__(self,x=[]):\n"
151 " def __init__(self,x=[]):\n"
152 " list.__init__(self,x)"))
152 " list.__init__(self,x)"))
153 ip.run_cell("w=Mylist([1,2,3])")
153 ip.run_cell("w=Mylist([1,2,3])")
154
154
155 from pickle import dumps
155 from pickle import dumps
156
156
157 # We need to swap in our main module - this is only necessary
157 # We need to swap in our main module - this is only necessary
158 # inside the test framework, because IPython puts the interactive module
158 # inside the test framework, because IPython puts the interactive module
159 # in place (but the test framework undoes this).
159 # in place (but the test framework undoes this).
160 _main = sys.modules['__main__']
160 _main = sys.modules['__main__']
161 sys.modules['__main__'] = ip.user_module
161 sys.modules['__main__'] = ip.user_module
162 try:
162 try:
163 res = dumps(ip.user_ns["w"])
163 res = dumps(ip.user_ns["w"])
164 finally:
164 finally:
165 sys.modules['__main__'] = _main
165 sys.modules['__main__'] = _main
166 self.assertTrue(isinstance(res, bytes))
166 self.assertTrue(isinstance(res, bytes))
167
167
168 def test_global_ns(self):
168 def test_global_ns(self):
169 "Code in functions must be able to access variables outside them."
169 "Code in functions must be able to access variables outside them."
170 ip = get_ipython()
170 ip = get_ipython()
171 ip.run_cell("a = 10")
171 ip.run_cell("a = 10")
172 ip.run_cell(("def f(x):\n"
172 ip.run_cell(("def f(x):\n"
173 " return x + a"))
173 " return x + a"))
174 ip.run_cell("b = f(12)")
174 ip.run_cell("b = f(12)")
175 self.assertEqual(ip.user_ns["b"], 22)
175 self.assertEqual(ip.user_ns["b"], 22)
176
176
177 def test_bad_custom_tb(self):
177 def test_bad_custom_tb(self):
178 """Check that InteractiveShell is protected from bad custom exception handlers"""
178 """Check that InteractiveShell is protected from bad custom exception handlers"""
179 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
179 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
180 self.assertEqual(ip.custom_exceptions, (IOError,))
180 self.assertEqual(ip.custom_exceptions, (IOError,))
181 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
181 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
182 ip.run_cell(u'raise IOError("foo")')
182 ip.run_cell(u'raise IOError("foo")')
183 self.assertEqual(ip.custom_exceptions, ())
183 self.assertEqual(ip.custom_exceptions, ())
184
184
185 def test_bad_custom_tb_return(self):
185 def test_bad_custom_tb_return(self):
186 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
186 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
187 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
187 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
188 self.assertEqual(ip.custom_exceptions, (NameError,))
188 self.assertEqual(ip.custom_exceptions, (NameError,))
189 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
189 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
190 ip.run_cell(u'a=abracadabra')
190 ip.run_cell(u'a=abracadabra')
191 self.assertEqual(ip.custom_exceptions, ())
191 self.assertEqual(ip.custom_exceptions, ())
192
192
193 def test_drop_by_id(self):
193 def test_drop_by_id(self):
194 myvars = {"a":object(), "b":object(), "c": object()}
194 myvars = {"a":object(), "b":object(), "c": object()}
195 ip.push(myvars, interactive=False)
195 ip.push(myvars, interactive=False)
196 for name in myvars:
196 for name in myvars:
197 assert name in ip.user_ns, name
197 assert name in ip.user_ns, name
198 assert name in ip.user_ns_hidden, name
198 assert name in ip.user_ns_hidden, name
199 ip.user_ns['b'] = 12
199 ip.user_ns['b'] = 12
200 ip.drop_by_id(myvars)
200 ip.drop_by_id(myvars)
201 for name in ["a", "c"]:
201 for name in ["a", "c"]:
202 assert name not in ip.user_ns, name
202 assert name not in ip.user_ns, name
203 assert name not in ip.user_ns_hidden, name
203 assert name not in ip.user_ns_hidden, name
204 assert ip.user_ns['b'] == 12
204 assert ip.user_ns['b'] == 12
205 ip.reset()
205 ip.reset()
206
206
207 def test_var_expand(self):
207 def test_var_expand(self):
208 ip.user_ns['f'] = u'Ca\xf1o'
208 ip.user_ns['f'] = u'Ca\xf1o'
209 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
209 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
210 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
210 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
211 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
211 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
212 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
212 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
213
213
214 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
214 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
215
215
216 ip.user_ns['f'] = b'Ca\xc3\xb1o'
216 ip.user_ns['f'] = b'Ca\xc3\xb1o'
217 # This should not raise any exception:
217 # This should not raise any exception:
218 ip.var_expand(u'echo $f')
218 ip.var_expand(u'echo $f')
219
219
220 def test_var_expand_local(self):
220 def test_var_expand_local(self):
221 """Test local variable expansion in !system and %magic calls"""
221 """Test local variable expansion in !system and %magic calls"""
222 # !system
222 # !system
223 ip.run_cell('def test():\n'
223 ip.run_cell('def test():\n'
224 ' lvar = "ttt"\n'
224 ' lvar = "ttt"\n'
225 ' ret = !echo {lvar}\n'
225 ' ret = !echo {lvar}\n'
226 ' return ret[0]\n')
226 ' return ret[0]\n')
227 res = ip.user_ns['test']()
227 res = ip.user_ns['test']()
228 nt.assert_in('ttt', res)
228 nt.assert_in('ttt', res)
229
229
230 # %magic
230 # %magic
231 ip.run_cell('def makemacro():\n'
231 ip.run_cell('def makemacro():\n'
232 ' macroname = "macro_var_expand_locals"\n'
232 ' macroname = "macro_var_expand_locals"\n'
233 ' %macro {macroname} codestr\n')
233 ' %macro {macroname} codestr\n')
234 ip.user_ns['codestr'] = "str(12)"
234 ip.user_ns['codestr'] = "str(12)"
235 ip.run_cell('makemacro()')
235 ip.run_cell('makemacro()')
236 nt.assert_in('macro_var_expand_locals', ip.user_ns)
236 nt.assert_in('macro_var_expand_locals', ip.user_ns)
237
237
238 def test_var_expand_self(self):
238 def test_var_expand_self(self):
239 """Test variable expansion with the name 'self', which was failing.
239 """Test variable expansion with the name 'self', which was failing.
240
240
241 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
241 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
242 """
242 """
243 ip.run_cell('class cTest:\n'
243 ip.run_cell('class cTest:\n'
244 ' classvar="see me"\n'
244 ' classvar="see me"\n'
245 ' def test(self):\n'
245 ' def test(self):\n'
246 ' res = !echo Variable: {self.classvar}\n'
246 ' res = !echo Variable: {self.classvar}\n'
247 ' return res[0]\n')
247 ' return res[0]\n')
248 nt.assert_in('see me', ip.user_ns['cTest']().test())
248 nt.assert_in('see me', ip.user_ns['cTest']().test())
249
249
250 def test_bad_var_expand(self):
250 def test_bad_var_expand(self):
251 """var_expand on invalid formats shouldn't raise"""
251 """var_expand on invalid formats shouldn't raise"""
252 # SyntaxError
252 # SyntaxError
253 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
253 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
254 # NameError
254 # NameError
255 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
255 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
256 # ZeroDivisionError
256 # ZeroDivisionError
257 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
257 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
258
258
259 def test_silent_postexec(self):
259 def test_silent_postexec(self):
260 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
260 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
261 pre_explicit = mock.Mock()
261 pre_explicit = mock.Mock()
262 pre_always = mock.Mock()
262 pre_always = mock.Mock()
263 post_explicit = mock.Mock()
263 post_explicit = mock.Mock()
264 post_always = mock.Mock()
264 post_always = mock.Mock()
265 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
265 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
266
266
267 ip.events.register('pre_run_cell', pre_explicit)
267 ip.events.register('pre_run_cell', pre_explicit)
268 ip.events.register('pre_execute', pre_always)
268 ip.events.register('pre_execute', pre_always)
269 ip.events.register('post_run_cell', post_explicit)
269 ip.events.register('post_run_cell', post_explicit)
270 ip.events.register('post_execute', post_always)
270 ip.events.register('post_execute', post_always)
271
271
272 try:
272 try:
273 ip.run_cell("1", silent=True)
273 ip.run_cell("1", silent=True)
274 assert pre_always.called
274 assert pre_always.called
275 assert not pre_explicit.called
275 assert not pre_explicit.called
276 assert post_always.called
276 assert post_always.called
277 assert not post_explicit.called
277 assert not post_explicit.called
278 # double-check that non-silent exec did what we expected
278 # double-check that non-silent exec did what we expected
279 # silent to avoid
279 # silent to avoid
280 ip.run_cell("1")
280 ip.run_cell("1")
281 assert pre_explicit.called
281 assert pre_explicit.called
282 assert post_explicit.called
282 assert post_explicit.called
283 info, = pre_explicit.call_args[0]
283 info, = pre_explicit.call_args[0]
284 result, = post_explicit.call_args[0]
284 result, = post_explicit.call_args[0]
285 self.assertEqual(info, result.info)
285 self.assertEqual(info, result.info)
286 # check that post hooks are always called
286 # check that post hooks are always called
287 [m.reset_mock() for m in all_mocks]
287 [m.reset_mock() for m in all_mocks]
288 ip.run_cell("syntax error")
288 ip.run_cell("syntax error")
289 assert pre_always.called
289 assert pre_always.called
290 assert pre_explicit.called
290 assert pre_explicit.called
291 assert post_always.called
291 assert post_always.called
292 assert post_explicit.called
292 assert post_explicit.called
293 info, = pre_explicit.call_args[0]
293 info, = pre_explicit.call_args[0]
294 result, = post_explicit.call_args[0]
294 result, = post_explicit.call_args[0]
295 self.assertEqual(info, result.info)
295 self.assertEqual(info, result.info)
296 finally:
296 finally:
297 # remove post-exec
297 # remove post-exec
298 ip.events.unregister('pre_run_cell', pre_explicit)
298 ip.events.unregister('pre_run_cell', pre_explicit)
299 ip.events.unregister('pre_execute', pre_always)
299 ip.events.unregister('pre_execute', pre_always)
300 ip.events.unregister('post_run_cell', post_explicit)
300 ip.events.unregister('post_run_cell', post_explicit)
301 ip.events.unregister('post_execute', post_always)
301 ip.events.unregister('post_execute', post_always)
302
302
303 def test_silent_noadvance(self):
303 def test_silent_noadvance(self):
304 """run_cell(silent=True) doesn't advance execution_count"""
304 """run_cell(silent=True) doesn't advance execution_count"""
305 ec = ip.execution_count
305 ec = ip.execution_count
306 # silent should force store_history=False
306 # silent should force store_history=False
307 ip.run_cell("1", store_history=True, silent=True)
307 ip.run_cell("1", store_history=True, silent=True)
308
308
309 self.assertEqual(ec, ip.execution_count)
309 self.assertEqual(ec, ip.execution_count)
310 # double-check that non-silent exec did what we expected
310 # double-check that non-silent exec did what we expected
311 # silent to avoid
311 # silent to avoid
312 ip.run_cell("1", store_history=True)
312 ip.run_cell("1", store_history=True)
313 self.assertEqual(ec+1, ip.execution_count)
313 self.assertEqual(ec+1, ip.execution_count)
314
314
315 def test_silent_nodisplayhook(self):
315 def test_silent_nodisplayhook(self):
316 """run_cell(silent=True) doesn't trigger displayhook"""
316 """run_cell(silent=True) doesn't trigger displayhook"""
317 d = dict(called=False)
317 d = dict(called=False)
318
318
319 trap = ip.display_trap
319 trap = ip.display_trap
320 save_hook = trap.hook
320 save_hook = trap.hook
321
321
322 def failing_hook(*args, **kwargs):
322 def failing_hook(*args, **kwargs):
323 d['called'] = True
323 d['called'] = True
324
324
325 try:
325 try:
326 trap.hook = failing_hook
326 trap.hook = failing_hook
327 res = ip.run_cell("1", silent=True)
327 res = ip.run_cell("1", silent=True)
328 self.assertFalse(d['called'])
328 self.assertFalse(d['called'])
329 self.assertIsNone(res.result)
329 self.assertIsNone(res.result)
330 # double-check that non-silent exec did what we expected
330 # double-check that non-silent exec did what we expected
331 # silent to avoid
331 # silent to avoid
332 ip.run_cell("1")
332 ip.run_cell("1")
333 self.assertTrue(d['called'])
333 self.assertTrue(d['called'])
334 finally:
334 finally:
335 trap.hook = save_hook
335 trap.hook = save_hook
336
336
337 def test_ofind_line_magic(self):
337 def test_ofind_line_magic(self):
338 from IPython.core.magic import register_line_magic
338 from IPython.core.magic import register_line_magic
339
339
340 @register_line_magic
340 @register_line_magic
341 def lmagic(line):
341 def lmagic(line):
342 "A line magic"
342 "A line magic"
343
343
344 # Get info on line magic
344 # Get info on line magic
345 lfind = ip._ofind('lmagic')
345 lfind = ip._ofind('lmagic')
346 info = dict(found=True, isalias=False, ismagic=True,
346 info = dict(found=True, isalias=False, ismagic=True,
347 namespace = 'IPython internal', obj= lmagic.__wrapped__,
347 namespace = 'IPython internal', obj= lmagic.__wrapped__,
348 parent = None)
348 parent = None)
349 nt.assert_equal(lfind, info)
349 nt.assert_equal(lfind, info)
350
350
351 def test_ofind_cell_magic(self):
351 def test_ofind_cell_magic(self):
352 from IPython.core.magic import register_cell_magic
352 from IPython.core.magic import register_cell_magic
353
353
354 @register_cell_magic
354 @register_cell_magic
355 def cmagic(line, cell):
355 def cmagic(line, cell):
356 "A cell magic"
356 "A cell magic"
357
357
358 # Get info on cell magic
358 # Get info on cell magic
359 find = ip._ofind('cmagic')
359 find = ip._ofind('cmagic')
360 info = dict(found=True, isalias=False, ismagic=True,
360 info = dict(found=True, isalias=False, ismagic=True,
361 namespace = 'IPython internal', obj= cmagic.__wrapped__,
361 namespace = 'IPython internal', obj= cmagic.__wrapped__,
362 parent = None)
362 parent = None)
363 nt.assert_equal(find, info)
363 nt.assert_equal(find, info)
364
364
365 def test_ofind_property_with_error(self):
365 def test_ofind_property_with_error(self):
366 class A(object):
366 class A(object):
367 @property
367 @property
368 def foo(self):
368 def foo(self):
369 raise NotImplementedError()
369 raise NotImplementedError()
370 a = A()
370 a = A()
371
371
372 found = ip._ofind('a.foo', [('locals', locals())])
372 found = ip._ofind('a.foo', [('locals', locals())])
373 info = dict(found=True, isalias=False, ismagic=False,
373 info = dict(found=True, isalias=False, ismagic=False,
374 namespace='locals', obj=A.foo, parent=a)
374 namespace='locals', obj=A.foo, parent=a)
375 nt.assert_equal(found, info)
375 nt.assert_equal(found, info)
376
376
377 def test_ofind_multiple_attribute_lookups(self):
377 def test_ofind_multiple_attribute_lookups(self):
378 class A(object):
378 class A(object):
379 @property
379 @property
380 def foo(self):
380 def foo(self):
381 raise NotImplementedError()
381 raise NotImplementedError()
382
382
383 a = A()
383 a = A()
384 a.a = A()
384 a.a = A()
385 a.a.a = A()
385 a.a.a = A()
386
386
387 found = ip._ofind('a.a.a.foo', [('locals', locals())])
387 found = ip._ofind('a.a.a.foo', [('locals', locals())])
388 info = dict(found=True, isalias=False, ismagic=False,
388 info = dict(found=True, isalias=False, ismagic=False,
389 namespace='locals', obj=A.foo, parent=a.a.a)
389 namespace='locals', obj=A.foo, parent=a.a.a)
390 nt.assert_equal(found, info)
390 nt.assert_equal(found, info)
391
391
392 def test_ofind_slotted_attributes(self):
392 def test_ofind_slotted_attributes(self):
393 class A(object):
393 class A(object):
394 __slots__ = ['foo']
394 __slots__ = ['foo']
395 def __init__(self):
395 def __init__(self):
396 self.foo = 'bar'
396 self.foo = 'bar'
397
397
398 a = A()
398 a = A()
399 found = ip._ofind('a.foo', [('locals', locals())])
399 found = ip._ofind('a.foo', [('locals', locals())])
400 info = dict(found=True, isalias=False, ismagic=False,
400 info = dict(found=True, isalias=False, ismagic=False,
401 namespace='locals', obj=a.foo, parent=a)
401 namespace='locals', obj=a.foo, parent=a)
402 nt.assert_equal(found, info)
402 nt.assert_equal(found, info)
403
403
404 found = ip._ofind('a.bar', [('locals', locals())])
404 found = ip._ofind('a.bar', [('locals', locals())])
405 info = dict(found=False, isalias=False, ismagic=False,
405 info = dict(found=False, isalias=False, ismagic=False,
406 namespace=None, obj=None, parent=a)
406 namespace=None, obj=None, parent=a)
407 nt.assert_equal(found, info)
407 nt.assert_equal(found, info)
408
408
409 def test_ofind_prefers_property_to_instance_level_attribute(self):
409 def test_ofind_prefers_property_to_instance_level_attribute(self):
410 class A(object):
410 class A(object):
411 @property
411 @property
412 def foo(self):
412 def foo(self):
413 return 'bar'
413 return 'bar'
414 a = A()
414 a = A()
415 a.__dict__['foo'] = 'baz'
415 a.__dict__['foo'] = 'baz'
416 nt.assert_equal(a.foo, 'bar')
416 nt.assert_equal(a.foo, 'bar')
417 found = ip._ofind('a.foo', [('locals', locals())])
417 found = ip._ofind('a.foo', [('locals', locals())])
418 nt.assert_is(found['obj'], A.foo)
418 nt.assert_is(found['obj'], A.foo)
419
419
420 def test_custom_syntaxerror_exception(self):
420 def test_custom_syntaxerror_exception(self):
421 called = []
421 called = []
422 def my_handler(shell, etype, value, tb, tb_offset=None):
422 def my_handler(shell, etype, value, tb, tb_offset=None):
423 called.append(etype)
423 called.append(etype)
424 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
424 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
425
425
426 ip.set_custom_exc((SyntaxError,), my_handler)
426 ip.set_custom_exc((SyntaxError,), my_handler)
427 try:
427 try:
428 ip.run_cell("1f")
428 ip.run_cell("1f")
429 # Check that this was called, and only once.
429 # Check that this was called, and only once.
430 self.assertEqual(called, [SyntaxError])
430 self.assertEqual(called, [SyntaxError])
431 finally:
431 finally:
432 # Reset the custom exception hook
432 # Reset the custom exception hook
433 ip.set_custom_exc((), None)
433 ip.set_custom_exc((), None)
434
434
435 def test_custom_exception(self):
435 def test_custom_exception(self):
436 called = []
436 called = []
437 def my_handler(shell, etype, value, tb, tb_offset=None):
437 def my_handler(shell, etype, value, tb, tb_offset=None):
438 called.append(etype)
438 called.append(etype)
439 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
439 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
440
440
441 ip.set_custom_exc((ValueError,), my_handler)
441 ip.set_custom_exc((ValueError,), my_handler)
442 try:
442 try:
443 res = ip.run_cell("raise ValueError('test')")
443 res = ip.run_cell("raise ValueError('test')")
444 # Check that this was called, and only once.
444 # Check that this was called, and only once.
445 self.assertEqual(called, [ValueError])
445 self.assertEqual(called, [ValueError])
446 # Check that the error is on the result object
446 # Check that the error is on the result object
447 self.assertIsInstance(res.error_in_exec, ValueError)
447 self.assertIsInstance(res.error_in_exec, ValueError)
448 finally:
448 finally:
449 # Reset the custom exception hook
449 # Reset the custom exception hook
450 ip.set_custom_exc((), None)
450 ip.set_custom_exc((), None)
451
451
452 @mock.patch('builtins.print')
453 def test_showtraceback_with_surrogates(self, mocked_print):
454 values = []
455 def mock_print_func(value, sep=' ', end='\n', file=sys.stdout, flush=False):
456 values.append(value)
457 if value == chr(0xd8ff):
458 raise UnicodeEncodeError("utf-8", chr(0xd8ff), 0, 1, "")
459
460 # mock builtins.print
461 mocked_print.side_effect = mock_print_func
462
463 # ip._showtraceback() is replaced in globalipapp.py.
464 # Call original method to test.
465 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xd8ff))
466
467 self.assertEqual(mocked_print.call_count, 2)
468 self.assertEqual(values, [chr(0xd8ff), '\\ud8ff'])
469
452 def test_mktempfile(self):
470 def test_mktempfile(self):
453 filename = ip.mktempfile()
471 filename = ip.mktempfile()
454 # Check that we can open the file again on Windows
472 # Check that we can open the file again on Windows
455 with open(filename, 'w') as f:
473 with open(filename, 'w') as f:
456 f.write('abc')
474 f.write('abc')
457
475
458 filename = ip.mktempfile(data='blah')
476 filename = ip.mktempfile(data='blah')
459 with open(filename, 'r') as f:
477 with open(filename, 'r') as f:
460 self.assertEqual(f.read(), 'blah')
478 self.assertEqual(f.read(), 'blah')
461
479
462 def test_new_main_mod(self):
480 def test_new_main_mod(self):
463 # Smoketest to check that this accepts a unicode module name
481 # Smoketest to check that this accepts a unicode module name
464 name = u'jiefmw'
482 name = u'jiefmw'
465 mod = ip.new_main_mod(u'%s.py' % name, name)
483 mod = ip.new_main_mod(u'%s.py' % name, name)
466 self.assertEqual(mod.__name__, name)
484 self.assertEqual(mod.__name__, name)
467
485
468 def test_get_exception_only(self):
486 def test_get_exception_only(self):
469 try:
487 try:
470 raise KeyboardInterrupt
488 raise KeyboardInterrupt
471 except KeyboardInterrupt:
489 except KeyboardInterrupt:
472 msg = ip.get_exception_only()
490 msg = ip.get_exception_only()
473 self.assertEqual(msg, 'KeyboardInterrupt\n')
491 self.assertEqual(msg, 'KeyboardInterrupt\n')
474
492
475 try:
493 try:
476 raise DerivedInterrupt("foo")
494 raise DerivedInterrupt("foo")
477 except KeyboardInterrupt:
495 except KeyboardInterrupt:
478 msg = ip.get_exception_only()
496 msg = ip.get_exception_only()
479 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
497 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
480
498
481 def test_inspect_text(self):
499 def test_inspect_text(self):
482 ip.run_cell('a = 5')
500 ip.run_cell('a = 5')
483 text = ip.object_inspect_text('a')
501 text = ip.object_inspect_text('a')
484 self.assertIsInstance(text, str)
502 self.assertIsInstance(text, str)
485
503
486 def test_last_execution_result(self):
504 def test_last_execution_result(self):
487 """ Check that last execution result gets set correctly (GH-10702) """
505 """ Check that last execution result gets set correctly (GH-10702) """
488 result = ip.run_cell('a = 5; a')
506 result = ip.run_cell('a = 5; a')
489 self.assertTrue(ip.last_execution_succeeded)
507 self.assertTrue(ip.last_execution_succeeded)
490 self.assertEqual(ip.last_execution_result.result, 5)
508 self.assertEqual(ip.last_execution_result.result, 5)
491
509
492 result = ip.run_cell('a = x_invalid_id_x')
510 result = ip.run_cell('a = x_invalid_id_x')
493 self.assertFalse(ip.last_execution_succeeded)
511 self.assertFalse(ip.last_execution_succeeded)
494 self.assertFalse(ip.last_execution_result.success)
512 self.assertFalse(ip.last_execution_result.success)
495 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
513 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
496
514
497 def test_reset_aliasing(self):
515 def test_reset_aliasing(self):
498 """ Check that standard posix aliases work after %reset. """
516 """ Check that standard posix aliases work after %reset. """
499 if os.name != 'posix':
517 if os.name != 'posix':
500 return
518 return
501
519
502 ip.reset()
520 ip.reset()
503 for cmd in ('clear', 'more', 'less', 'man'):
521 for cmd in ('clear', 'more', 'less', 'man'):
504 res = ip.run_cell('%' + cmd)
522 res = ip.run_cell('%' + cmd)
505 self.assertEqual(res.success, True)
523 self.assertEqual(res.success, True)
506
524
507
525
508 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
526 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
509
527
510 @onlyif_unicode_paths
528 @onlyif_unicode_paths
511 def setUp(self):
529 def setUp(self):
512 self.BASETESTDIR = tempfile.mkdtemp()
530 self.BASETESTDIR = tempfile.mkdtemp()
513 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
531 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
514 os.mkdir(self.TESTDIR)
532 os.mkdir(self.TESTDIR)
515 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
533 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
516 sfile.write("pass\n")
534 sfile.write("pass\n")
517 self.oldpath = os.getcwd()
535 self.oldpath = os.getcwd()
518 os.chdir(self.TESTDIR)
536 os.chdir(self.TESTDIR)
519 self.fname = u"Γ₯Àâtestscript.py"
537 self.fname = u"Γ₯Àâtestscript.py"
520
538
521 def tearDown(self):
539 def tearDown(self):
522 os.chdir(self.oldpath)
540 os.chdir(self.oldpath)
523 shutil.rmtree(self.BASETESTDIR)
541 shutil.rmtree(self.BASETESTDIR)
524
542
525 @onlyif_unicode_paths
543 @onlyif_unicode_paths
526 def test_1(self):
544 def test_1(self):
527 """Test safe_execfile with non-ascii path
545 """Test safe_execfile with non-ascii path
528 """
546 """
529 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
547 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
530
548
531 class ExitCodeChecks(tt.TempFileMixin):
549 class ExitCodeChecks(tt.TempFileMixin):
532
550
533 def setUp(self):
551 def setUp(self):
534 self.system = ip.system_raw
552 self.system = ip.system_raw
535
553
536 def test_exit_code_ok(self):
554 def test_exit_code_ok(self):
537 self.system('exit 0')
555 self.system('exit 0')
538 self.assertEqual(ip.user_ns['_exit_code'], 0)
556 self.assertEqual(ip.user_ns['_exit_code'], 0)
539
557
540 def test_exit_code_error(self):
558 def test_exit_code_error(self):
541 self.system('exit 1')
559 self.system('exit 1')
542 self.assertEqual(ip.user_ns['_exit_code'], 1)
560 self.assertEqual(ip.user_ns['_exit_code'], 1)
543
561
544 @skipif(not hasattr(signal, 'SIGALRM'))
562 @skipif(not hasattr(signal, 'SIGALRM'))
545 def test_exit_code_signal(self):
563 def test_exit_code_signal(self):
546 self.mktmp("import signal, time\n"
564 self.mktmp("import signal, time\n"
547 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
565 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
548 "time.sleep(1)\n")
566 "time.sleep(1)\n")
549 self.system("%s %s" % (sys.executable, self.fname))
567 self.system("%s %s" % (sys.executable, self.fname))
550 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
568 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
551
569
552 @onlyif_cmds_exist("csh")
570 @onlyif_cmds_exist("csh")
553 def test_exit_code_signal_csh(self):
571 def test_exit_code_signal_csh(self):
554 SHELL = os.environ.get('SHELL', None)
572 SHELL = os.environ.get('SHELL', None)
555 os.environ['SHELL'] = find_cmd("csh")
573 os.environ['SHELL'] = find_cmd("csh")
556 try:
574 try:
557 self.test_exit_code_signal()
575 self.test_exit_code_signal()
558 finally:
576 finally:
559 if SHELL is not None:
577 if SHELL is not None:
560 os.environ['SHELL'] = SHELL
578 os.environ['SHELL'] = SHELL
561 else:
579 else:
562 del os.environ['SHELL']
580 del os.environ['SHELL']
563
581
564
582
565 class TestSystemRaw(ExitCodeChecks):
583 class TestSystemRaw(ExitCodeChecks):
566
584
567 def setUp(self):
585 def setUp(self):
568 super().setUp()
586 super().setUp()
569 self.system = ip.system_raw
587 self.system = ip.system_raw
570
588
571 @onlyif_unicode_paths
589 @onlyif_unicode_paths
572 def test_1(self):
590 def test_1(self):
573 """Test system_raw with non-ascii cmd
591 """Test system_raw with non-ascii cmd
574 """
592 """
575 cmd = u'''python -c "'Γ₯Àâ'" '''
593 cmd = u'''python -c "'Γ₯Àâ'" '''
576 ip.system_raw(cmd)
594 ip.system_raw(cmd)
577
595
578 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
596 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
579 @mock.patch('os.system', side_effect=KeyboardInterrupt)
597 @mock.patch('os.system', side_effect=KeyboardInterrupt)
580 def test_control_c(self, *mocks):
598 def test_control_c(self, *mocks):
581 try:
599 try:
582 self.system("sleep 1 # wont happen")
600 self.system("sleep 1 # wont happen")
583 except KeyboardInterrupt:
601 except KeyboardInterrupt:
584 self.fail("system call should intercept "
602 self.fail("system call should intercept "
585 "keyboard interrupt from subprocess.call")
603 "keyboard interrupt from subprocess.call")
586 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
604 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
587
605
588 # TODO: Exit codes are currently ignored on Windows.
606 # TODO: Exit codes are currently ignored on Windows.
589 class TestSystemPipedExitCode(ExitCodeChecks):
607 class TestSystemPipedExitCode(ExitCodeChecks):
590
608
591 def setUp(self):
609 def setUp(self):
592 super().setUp()
610 super().setUp()
593 self.system = ip.system_piped
611 self.system = ip.system_piped
594
612
595 @skip_win32
613 @skip_win32
596 def test_exit_code_ok(self):
614 def test_exit_code_ok(self):
597 ExitCodeChecks.test_exit_code_ok(self)
615 ExitCodeChecks.test_exit_code_ok(self)
598
616
599 @skip_win32
617 @skip_win32
600 def test_exit_code_error(self):
618 def test_exit_code_error(self):
601 ExitCodeChecks.test_exit_code_error(self)
619 ExitCodeChecks.test_exit_code_error(self)
602
620
603 @skip_win32
621 @skip_win32
604 def test_exit_code_signal(self):
622 def test_exit_code_signal(self):
605 ExitCodeChecks.test_exit_code_signal(self)
623 ExitCodeChecks.test_exit_code_signal(self)
606
624
607 class TestModules(tt.TempFileMixin):
625 class TestModules(tt.TempFileMixin):
608 def test_extraneous_loads(self):
626 def test_extraneous_loads(self):
609 """Test we're not loading modules on startup that we shouldn't.
627 """Test we're not loading modules on startup that we shouldn't.
610 """
628 """
611 self.mktmp("import sys\n"
629 self.mktmp("import sys\n"
612 "print('numpy' in sys.modules)\n"
630 "print('numpy' in sys.modules)\n"
613 "print('ipyparallel' in sys.modules)\n"
631 "print('ipyparallel' in sys.modules)\n"
614 "print('ipykernel' in sys.modules)\n"
632 "print('ipykernel' in sys.modules)\n"
615 )
633 )
616 out = "False\nFalse\nFalse\n"
634 out = "False\nFalse\nFalse\n"
617 tt.ipexec_validate(self.fname, out)
635 tt.ipexec_validate(self.fname, out)
618
636
619 class Negator(ast.NodeTransformer):
637 class Negator(ast.NodeTransformer):
620 """Negates all number literals in an AST."""
638 """Negates all number literals in an AST."""
621
639
622 # for python 3.7 and earlier
640 # for python 3.7 and earlier
623 def visit_Num(self, node):
641 def visit_Num(self, node):
624 node.n = -node.n
642 node.n = -node.n
625 return node
643 return node
626
644
627 # for python 3.8+
645 # for python 3.8+
628 def visit_Constant(self, node):
646 def visit_Constant(self, node):
629 if isinstance(node.value, int):
647 if isinstance(node.value, int):
630 return self.visit_Num(node)
648 return self.visit_Num(node)
631 return node
649 return node
632
650
633 class TestAstTransform(unittest.TestCase):
651 class TestAstTransform(unittest.TestCase):
634 def setUp(self):
652 def setUp(self):
635 self.negator = Negator()
653 self.negator = Negator()
636 ip.ast_transformers.append(self.negator)
654 ip.ast_transformers.append(self.negator)
637
655
638 def tearDown(self):
656 def tearDown(self):
639 ip.ast_transformers.remove(self.negator)
657 ip.ast_transformers.remove(self.negator)
640
658
641 def test_run_cell(self):
659 def test_run_cell(self):
642 with tt.AssertPrints('-34'):
660 with tt.AssertPrints('-34'):
643 ip.run_cell('print (12 + 22)')
661 ip.run_cell('print (12 + 22)')
644
662
645 # A named reference to a number shouldn't be transformed.
663 # A named reference to a number shouldn't be transformed.
646 ip.user_ns['n'] = 55
664 ip.user_ns['n'] = 55
647 with tt.AssertNotPrints('-55'):
665 with tt.AssertNotPrints('-55'):
648 ip.run_cell('print (n)')
666 ip.run_cell('print (n)')
649
667
650 def test_timeit(self):
668 def test_timeit(self):
651 called = set()
669 called = set()
652 def f(x):
670 def f(x):
653 called.add(x)
671 called.add(x)
654 ip.push({'f':f})
672 ip.push({'f':f})
655
673
656 with tt.AssertPrints("std. dev. of"):
674 with tt.AssertPrints("std. dev. of"):
657 ip.run_line_magic("timeit", "-n1 f(1)")
675 ip.run_line_magic("timeit", "-n1 f(1)")
658 self.assertEqual(called, {-1})
676 self.assertEqual(called, {-1})
659 called.clear()
677 called.clear()
660
678
661 with tt.AssertPrints("std. dev. of"):
679 with tt.AssertPrints("std. dev. of"):
662 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
680 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
663 self.assertEqual(called, {-2, -3})
681 self.assertEqual(called, {-2, -3})
664
682
665 def test_time(self):
683 def test_time(self):
666 called = []
684 called = []
667 def f(x):
685 def f(x):
668 called.append(x)
686 called.append(x)
669 ip.push({'f':f})
687 ip.push({'f':f})
670
688
671 # Test with an expression
689 # Test with an expression
672 with tt.AssertPrints("Wall time: "):
690 with tt.AssertPrints("Wall time: "):
673 ip.run_line_magic("time", "f(5+9)")
691 ip.run_line_magic("time", "f(5+9)")
674 self.assertEqual(called, [-14])
692 self.assertEqual(called, [-14])
675 called[:] = []
693 called[:] = []
676
694
677 # Test with a statement (different code path)
695 # Test with a statement (different code path)
678 with tt.AssertPrints("Wall time: "):
696 with tt.AssertPrints("Wall time: "):
679 ip.run_line_magic("time", "a = f(-3 + -2)")
697 ip.run_line_magic("time", "a = f(-3 + -2)")
680 self.assertEqual(called, [5])
698 self.assertEqual(called, [5])
681
699
682 def test_macro(self):
700 def test_macro(self):
683 ip.push({'a':10})
701 ip.push({'a':10})
684 # The AST transformation makes this do a+=-1
702 # The AST transformation makes this do a+=-1
685 ip.define_macro("amacro", "a+=1\nprint(a)")
703 ip.define_macro("amacro", "a+=1\nprint(a)")
686
704
687 with tt.AssertPrints("9"):
705 with tt.AssertPrints("9"):
688 ip.run_cell("amacro")
706 ip.run_cell("amacro")
689 with tt.AssertPrints("8"):
707 with tt.AssertPrints("8"):
690 ip.run_cell("amacro")
708 ip.run_cell("amacro")
691
709
692 class TestMiscTransform(unittest.TestCase):
710 class TestMiscTransform(unittest.TestCase):
693
711
694
712
695 def test_transform_only_once(self):
713 def test_transform_only_once(self):
696 cleanup = 0
714 cleanup = 0
697 line_t = 0
715 line_t = 0
698 def count_cleanup(lines):
716 def count_cleanup(lines):
699 nonlocal cleanup
717 nonlocal cleanup
700 cleanup += 1
718 cleanup += 1
701 return lines
719 return lines
702
720
703 def count_line_t(lines):
721 def count_line_t(lines):
704 nonlocal line_t
722 nonlocal line_t
705 line_t += 1
723 line_t += 1
706 return lines
724 return lines
707
725
708 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
726 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
709 ip.input_transformer_manager.line_transforms.append(count_line_t)
727 ip.input_transformer_manager.line_transforms.append(count_line_t)
710
728
711 ip.run_cell('1')
729 ip.run_cell('1')
712
730
713 assert cleanup == 1
731 assert cleanup == 1
714 assert line_t == 1
732 assert line_t == 1
715
733
716 class IntegerWrapper(ast.NodeTransformer):
734 class IntegerWrapper(ast.NodeTransformer):
717 """Wraps all integers in a call to Integer()"""
735 """Wraps all integers in a call to Integer()"""
718
736
719 # for Python 3.7 and earlier
737 # for Python 3.7 and earlier
720
738
721 # for Python 3.7 and earlier
739 # for Python 3.7 and earlier
722 def visit_Num(self, node):
740 def visit_Num(self, node):
723 if isinstance(node.n, int):
741 if isinstance(node.n, int):
724 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
742 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
725 args=[node], keywords=[])
743 args=[node], keywords=[])
726 return node
744 return node
727
745
728 # For Python 3.8+
746 # For Python 3.8+
729 def visit_Constant(self, node):
747 def visit_Constant(self, node):
730 if isinstance(node.value, int):
748 if isinstance(node.value, int):
731 return self.visit_Num(node)
749 return self.visit_Num(node)
732 return node
750 return node
733
751
734
752
735 class TestAstTransform2(unittest.TestCase):
753 class TestAstTransform2(unittest.TestCase):
736 def setUp(self):
754 def setUp(self):
737 self.intwrapper = IntegerWrapper()
755 self.intwrapper = IntegerWrapper()
738 ip.ast_transformers.append(self.intwrapper)
756 ip.ast_transformers.append(self.intwrapper)
739
757
740 self.calls = []
758 self.calls = []
741 def Integer(*args):
759 def Integer(*args):
742 self.calls.append(args)
760 self.calls.append(args)
743 return args
761 return args
744 ip.push({"Integer": Integer})
762 ip.push({"Integer": Integer})
745
763
746 def tearDown(self):
764 def tearDown(self):
747 ip.ast_transformers.remove(self.intwrapper)
765 ip.ast_transformers.remove(self.intwrapper)
748 del ip.user_ns['Integer']
766 del ip.user_ns['Integer']
749
767
750 def test_run_cell(self):
768 def test_run_cell(self):
751 ip.run_cell("n = 2")
769 ip.run_cell("n = 2")
752 self.assertEqual(self.calls, [(2,)])
770 self.assertEqual(self.calls, [(2,)])
753
771
754 # This shouldn't throw an error
772 # This shouldn't throw an error
755 ip.run_cell("o = 2.0")
773 ip.run_cell("o = 2.0")
756 self.assertEqual(ip.user_ns['o'], 2.0)
774 self.assertEqual(ip.user_ns['o'], 2.0)
757
775
758 def test_timeit(self):
776 def test_timeit(self):
759 called = set()
777 called = set()
760 def f(x):
778 def f(x):
761 called.add(x)
779 called.add(x)
762 ip.push({'f':f})
780 ip.push({'f':f})
763
781
764 with tt.AssertPrints("std. dev. of"):
782 with tt.AssertPrints("std. dev. of"):
765 ip.run_line_magic("timeit", "-n1 f(1)")
783 ip.run_line_magic("timeit", "-n1 f(1)")
766 self.assertEqual(called, {(1,)})
784 self.assertEqual(called, {(1,)})
767 called.clear()
785 called.clear()
768
786
769 with tt.AssertPrints("std. dev. of"):
787 with tt.AssertPrints("std. dev. of"):
770 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
788 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
771 self.assertEqual(called, {(2,), (3,)})
789 self.assertEqual(called, {(2,), (3,)})
772
790
773 class ErrorTransformer(ast.NodeTransformer):
791 class ErrorTransformer(ast.NodeTransformer):
774 """Throws an error when it sees a number."""
792 """Throws an error when it sees a number."""
775
793
776 # for Python 3.7 and earlier
794 # for Python 3.7 and earlier
777 def visit_Num(self, node):
795 def visit_Num(self, node):
778 raise ValueError("test")
796 raise ValueError("test")
779
797
780 # for Python 3.8+
798 # for Python 3.8+
781 def visit_Constant(self, node):
799 def visit_Constant(self, node):
782 if isinstance(node.value, int):
800 if isinstance(node.value, int):
783 return self.visit_Num(node)
801 return self.visit_Num(node)
784 return node
802 return node
785
803
786
804
787 class TestAstTransformError(unittest.TestCase):
805 class TestAstTransformError(unittest.TestCase):
788 def test_unregistering(self):
806 def test_unregistering(self):
789 err_transformer = ErrorTransformer()
807 err_transformer = ErrorTransformer()
790 ip.ast_transformers.append(err_transformer)
808 ip.ast_transformers.append(err_transformer)
791
809
792 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
810 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
793 ip.run_cell("1 + 2")
811 ip.run_cell("1 + 2")
794
812
795 # This should have been removed.
813 # This should have been removed.
796 nt.assert_not_in(err_transformer, ip.ast_transformers)
814 nt.assert_not_in(err_transformer, ip.ast_transformers)
797
815
798
816
799 class StringRejector(ast.NodeTransformer):
817 class StringRejector(ast.NodeTransformer):
800 """Throws an InputRejected when it sees a string literal.
818 """Throws an InputRejected when it sees a string literal.
801
819
802 Used to verify that NodeTransformers can signal that a piece of code should
820 Used to verify that NodeTransformers can signal that a piece of code should
803 not be executed by throwing an InputRejected.
821 not be executed by throwing an InputRejected.
804 """
822 """
805
823
806 #for python 3.7 and earlier
824 #for python 3.7 and earlier
807 def visit_Str(self, node):
825 def visit_Str(self, node):
808 raise InputRejected("test")
826 raise InputRejected("test")
809
827
810 # 3.8 only
828 # 3.8 only
811 def visit_Constant(self, node):
829 def visit_Constant(self, node):
812 if isinstance(node.value, str):
830 if isinstance(node.value, str):
813 raise InputRejected("test")
831 raise InputRejected("test")
814 return node
832 return node
815
833
816
834
817 class TestAstTransformInputRejection(unittest.TestCase):
835 class TestAstTransformInputRejection(unittest.TestCase):
818
836
819 def setUp(self):
837 def setUp(self):
820 self.transformer = StringRejector()
838 self.transformer = StringRejector()
821 ip.ast_transformers.append(self.transformer)
839 ip.ast_transformers.append(self.transformer)
822
840
823 def tearDown(self):
841 def tearDown(self):
824 ip.ast_transformers.remove(self.transformer)
842 ip.ast_transformers.remove(self.transformer)
825
843
826 def test_input_rejection(self):
844 def test_input_rejection(self):
827 """Check that NodeTransformers can reject input."""
845 """Check that NodeTransformers can reject input."""
828
846
829 expect_exception_tb = tt.AssertPrints("InputRejected: test")
847 expect_exception_tb = tt.AssertPrints("InputRejected: test")
830 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
848 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
831
849
832 # Run the same check twice to verify that the transformer is not
850 # Run the same check twice to verify that the transformer is not
833 # disabled after raising.
851 # disabled after raising.
834 with expect_exception_tb, expect_no_cell_output:
852 with expect_exception_tb, expect_no_cell_output:
835 ip.run_cell("'unsafe'")
853 ip.run_cell("'unsafe'")
836
854
837 with expect_exception_tb, expect_no_cell_output:
855 with expect_exception_tb, expect_no_cell_output:
838 res = ip.run_cell("'unsafe'")
856 res = ip.run_cell("'unsafe'")
839
857
840 self.assertIsInstance(res.error_before_exec, InputRejected)
858 self.assertIsInstance(res.error_before_exec, InputRejected)
841
859
842 def test__IPYTHON__():
860 def test__IPYTHON__():
843 # This shouldn't raise a NameError, that's all
861 # This shouldn't raise a NameError, that's all
844 __IPYTHON__
862 __IPYTHON__
845
863
846
864
847 class DummyRepr(object):
865 class DummyRepr(object):
848 def __repr__(self):
866 def __repr__(self):
849 return "DummyRepr"
867 return "DummyRepr"
850
868
851 def _repr_html_(self):
869 def _repr_html_(self):
852 return "<b>dummy</b>"
870 return "<b>dummy</b>"
853
871
854 def _repr_javascript_(self):
872 def _repr_javascript_(self):
855 return "console.log('hi');", {'key': 'value'}
873 return "console.log('hi');", {'key': 'value'}
856
874
857
875
858 def test_user_variables():
876 def test_user_variables():
859 # enable all formatters
877 # enable all formatters
860 ip.display_formatter.active_types = ip.display_formatter.format_types
878 ip.display_formatter.active_types = ip.display_formatter.format_types
861
879
862 ip.user_ns['dummy'] = d = DummyRepr()
880 ip.user_ns['dummy'] = d = DummyRepr()
863 keys = {'dummy', 'doesnotexist'}
881 keys = {'dummy', 'doesnotexist'}
864 r = ip.user_expressions({ key:key for key in keys})
882 r = ip.user_expressions({ key:key for key in keys})
865
883
866 nt.assert_equal(keys, set(r.keys()))
884 nt.assert_equal(keys, set(r.keys()))
867 dummy = r['dummy']
885 dummy = r['dummy']
868 nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
886 nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
869 nt.assert_equal(dummy['status'], 'ok')
887 nt.assert_equal(dummy['status'], 'ok')
870 data = dummy['data']
888 data = dummy['data']
871 metadata = dummy['metadata']
889 metadata = dummy['metadata']
872 nt.assert_equal(data.get('text/html'), d._repr_html_())
890 nt.assert_equal(data.get('text/html'), d._repr_html_())
873 js, jsmd = d._repr_javascript_()
891 js, jsmd = d._repr_javascript_()
874 nt.assert_equal(data.get('application/javascript'), js)
892 nt.assert_equal(data.get('application/javascript'), js)
875 nt.assert_equal(metadata.get('application/javascript'), jsmd)
893 nt.assert_equal(metadata.get('application/javascript'), jsmd)
876
894
877 dne = r['doesnotexist']
895 dne = r['doesnotexist']
878 nt.assert_equal(dne['status'], 'error')
896 nt.assert_equal(dne['status'], 'error')
879 nt.assert_equal(dne['ename'], 'NameError')
897 nt.assert_equal(dne['ename'], 'NameError')
880
898
881 # back to text only
899 # back to text only
882 ip.display_formatter.active_types = ['text/plain']
900 ip.display_formatter.active_types = ['text/plain']
883
901
884 def test_user_expression():
902 def test_user_expression():
885 # enable all formatters
903 # enable all formatters
886 ip.display_formatter.active_types = ip.display_formatter.format_types
904 ip.display_formatter.active_types = ip.display_formatter.format_types
887 query = {
905 query = {
888 'a' : '1 + 2',
906 'a' : '1 + 2',
889 'b' : '1/0',
907 'b' : '1/0',
890 }
908 }
891 r = ip.user_expressions(query)
909 r = ip.user_expressions(query)
892 import pprint
910 import pprint
893 pprint.pprint(r)
911 pprint.pprint(r)
894 nt.assert_equal(set(r.keys()), set(query.keys()))
912 nt.assert_equal(set(r.keys()), set(query.keys()))
895 a = r['a']
913 a = r['a']
896 nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
914 nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
897 nt.assert_equal(a['status'], 'ok')
915 nt.assert_equal(a['status'], 'ok')
898 data = a['data']
916 data = a['data']
899 metadata = a['metadata']
917 metadata = a['metadata']
900 nt.assert_equal(data.get('text/plain'), '3')
918 nt.assert_equal(data.get('text/plain'), '3')
901
919
902 b = r['b']
920 b = r['b']
903 nt.assert_equal(b['status'], 'error')
921 nt.assert_equal(b['status'], 'error')
904 nt.assert_equal(b['ename'], 'ZeroDivisionError')
922 nt.assert_equal(b['ename'], 'ZeroDivisionError')
905
923
906 # back to text only
924 # back to text only
907 ip.display_formatter.active_types = ['text/plain']
925 ip.display_formatter.active_types = ['text/plain']
908
926
909
927
910 class TestSyntaxErrorTransformer(unittest.TestCase):
928 class TestSyntaxErrorTransformer(unittest.TestCase):
911 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
929 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
912
930
913 @staticmethod
931 @staticmethod
914 def transformer(lines):
932 def transformer(lines):
915 for line in lines:
933 for line in lines:
916 pos = line.find('syntaxerror')
934 pos = line.find('syntaxerror')
917 if pos >= 0:
935 if pos >= 0:
918 e = SyntaxError('input contains "syntaxerror"')
936 e = SyntaxError('input contains "syntaxerror"')
919 e.text = line
937 e.text = line
920 e.offset = pos + 1
938 e.offset = pos + 1
921 raise e
939 raise e
922 return lines
940 return lines
923
941
924 def setUp(self):
942 def setUp(self):
925 ip.input_transformers_post.append(self.transformer)
943 ip.input_transformers_post.append(self.transformer)
926
944
927 def tearDown(self):
945 def tearDown(self):
928 ip.input_transformers_post.remove(self.transformer)
946 ip.input_transformers_post.remove(self.transformer)
929
947
930 def test_syntaxerror_input_transformer(self):
948 def test_syntaxerror_input_transformer(self):
931 with tt.AssertPrints('1234'):
949 with tt.AssertPrints('1234'):
932 ip.run_cell('1234')
950 ip.run_cell('1234')
933 with tt.AssertPrints('SyntaxError: invalid syntax'):
951 with tt.AssertPrints('SyntaxError: invalid syntax'):
934 ip.run_cell('1 2 3') # plain python syntax error
952 ip.run_cell('1 2 3') # plain python syntax error
935 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
953 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
936 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
954 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
937 with tt.AssertPrints('3456'):
955 with tt.AssertPrints('3456'):
938 ip.run_cell('3456')
956 ip.run_cell('3456')
939
957
940
958
941 class TestWarningSuppression(unittest.TestCase):
959 class TestWarningSuppression(unittest.TestCase):
942 def test_warning_suppression(self):
960 def test_warning_suppression(self):
943 ip.run_cell("import warnings")
961 ip.run_cell("import warnings")
944 try:
962 try:
945 with self.assertWarnsRegex(UserWarning, "asdf"):
963 with self.assertWarnsRegex(UserWarning, "asdf"):
946 ip.run_cell("warnings.warn('asdf')")
964 ip.run_cell("warnings.warn('asdf')")
947 # Here's the real test -- if we run that again, we should get the
965 # Here's the real test -- if we run that again, we should get the
948 # warning again. Traditionally, each warning was only issued once per
966 # warning again. Traditionally, each warning was only issued once per
949 # IPython session (approximately), even if the user typed in new and
967 # IPython session (approximately), even if the user typed in new and
950 # different code that should have also triggered the warning, leading
968 # different code that should have also triggered the warning, leading
951 # to much confusion.
969 # to much confusion.
952 with self.assertWarnsRegex(UserWarning, "asdf"):
970 with self.assertWarnsRegex(UserWarning, "asdf"):
953 ip.run_cell("warnings.warn('asdf')")
971 ip.run_cell("warnings.warn('asdf')")
954 finally:
972 finally:
955 ip.run_cell("del warnings")
973 ip.run_cell("del warnings")
956
974
957
975
958 def test_deprecation_warning(self):
976 def test_deprecation_warning(self):
959 ip.run_cell("""
977 ip.run_cell("""
960 import warnings
978 import warnings
961 def wrn():
979 def wrn():
962 warnings.warn(
980 warnings.warn(
963 "I AM A WARNING",
981 "I AM A WARNING",
964 DeprecationWarning
982 DeprecationWarning
965 )
983 )
966 """)
984 """)
967 try:
985 try:
968 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
986 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
969 ip.run_cell("wrn()")
987 ip.run_cell("wrn()")
970 finally:
988 finally:
971 ip.run_cell("del warnings")
989 ip.run_cell("del warnings")
972 ip.run_cell("del wrn")
990 ip.run_cell("del wrn")
973
991
974
992
975 class TestImportNoDeprecate(tt.TempFileMixin):
993 class TestImportNoDeprecate(tt.TempFileMixin):
976
994
977 def setUp(self):
995 def setUp(self):
978 """Make a valid python temp file."""
996 """Make a valid python temp file."""
979 self.mktmp("""
997 self.mktmp("""
980 import warnings
998 import warnings
981 def wrn():
999 def wrn():
982 warnings.warn(
1000 warnings.warn(
983 "I AM A WARNING",
1001 "I AM A WARNING",
984 DeprecationWarning
1002 DeprecationWarning
985 )
1003 )
986 """)
1004 """)
987 super().setUp()
1005 super().setUp()
988
1006
989 def test_no_dep(self):
1007 def test_no_dep(self):
990 """
1008 """
991 No deprecation warning should be raised from imported functions
1009 No deprecation warning should be raised from imported functions
992 """
1010 """
993 ip.run_cell("from {} import wrn".format(self.fname))
1011 ip.run_cell("from {} import wrn".format(self.fname))
994
1012
995 with tt.AssertNotPrints("I AM A WARNING"):
1013 with tt.AssertNotPrints("I AM A WARNING"):
996 ip.run_cell("wrn()")
1014 ip.run_cell("wrn()")
997 ip.run_cell("del wrn")
1015 ip.run_cell("del wrn")
998
1016
999
1017
1000 def test_custom_exc_count():
1018 def test_custom_exc_count():
1001 hook = mock.Mock(return_value=None)
1019 hook = mock.Mock(return_value=None)
1002 ip.set_custom_exc((SyntaxError,), hook)
1020 ip.set_custom_exc((SyntaxError,), hook)
1003 before = ip.execution_count
1021 before = ip.execution_count
1004 ip.run_cell("def foo()", store_history=True)
1022 ip.run_cell("def foo()", store_history=True)
1005 # restore default excepthook
1023 # restore default excepthook
1006 ip.set_custom_exc((), None)
1024 ip.set_custom_exc((), None)
1007 nt.assert_equal(hook.call_count, 1)
1025 nt.assert_equal(hook.call_count, 1)
1008 nt.assert_equal(ip.execution_count, before + 1)
1026 nt.assert_equal(ip.execution_count, before + 1)
1009
1027
1010
1028
1011 def test_run_cell_async():
1029 def test_run_cell_async():
1012 loop = asyncio.get_event_loop()
1030 loop = asyncio.get_event_loop()
1013 ip.run_cell("import asyncio")
1031 ip.run_cell("import asyncio")
1014 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1032 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1015 assert asyncio.iscoroutine(coro)
1033 assert asyncio.iscoroutine(coro)
1016 result = loop.run_until_complete(coro)
1034 result = loop.run_until_complete(coro)
1017 assert isinstance(result, interactiveshell.ExecutionResult)
1035 assert isinstance(result, interactiveshell.ExecutionResult)
1018 assert result.result == 5
1036 assert result.result == 5
1019
1037
1020
1038
1021 def test_should_run_async():
1039 def test_should_run_async():
1022 assert not ip.should_run_async("a = 5")
1040 assert not ip.should_run_async("a = 5")
1023 assert ip.should_run_async("await x")
1041 assert ip.should_run_async("await x")
1024 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1042 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1025
1043
1026
1044
1027 def test_set_custom_completer():
1045 def test_set_custom_completer():
1028 num_completers = len(ip.Completer.matchers)
1046 num_completers = len(ip.Completer.matchers)
1029
1047
1030 def foo(*args, **kwargs):
1048 def foo(*args, **kwargs):
1031 return "I'm a completer!"
1049 return "I'm a completer!"
1032
1050
1033 ip.set_custom_completer(foo, 0)
1051 ip.set_custom_completer(foo, 0)
1034
1052
1035 # check that we've really added a new completer
1053 # check that we've really added a new completer
1036 assert len(ip.Completer.matchers) == num_completers + 1
1054 assert len(ip.Completer.matchers) == num_completers + 1
1037
1055
1038 # check that the first completer is the function we defined
1056 # check that the first completer is the function we defined
1039 assert ip.Completer.matchers[0]() == "I'm a completer!"
1057 assert ip.Completer.matchers[0]() == "I'm a completer!"
1040
1058
1041 # clean up
1059 # clean up
1042 ip.Completer.custom_matchers.pop()
1060 ip.Completer.custom_matchers.pop()
General Comments 0
You need to be logged in to leave comments. Login now