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