##// END OF EJS Templates
Backport PR #10788: None...
Thomas Kluyver -
Show More
@@ -1,527 +1,528 b''
1 1 # encoding: utf-8
2 2 """Tests for code execution (%run and related), which is particularly tricky.
3 3
4 4 Because of how %run manages namespaces, and the fact that we are trying here to
5 5 verify subtle object deletion and reference counting issues, the %run tests
6 6 will be kept in this separate file. This makes it easier to aggregate in one
7 7 place the tricks needed to handle it; most other magics are much easier to test
8 8 and we do so in a common test_magic file.
9 9 """
10 10
11 11 # Copyright (c) IPython Development Team.
12 12 # Distributed under the terms of the Modified BSD License.
13 13
14 14 from __future__ import absolute_import
15 15
16 16
17 17 import functools
18 18 import os
19 19 from os.path import join as pjoin
20 20 import random
21 import string
21 22 import sys
22 23 import tempfile
23 24 import textwrap
24 25 import unittest
25 26
26 27 try:
27 28 from unittest.mock import patch
28 29 except ImportError:
29 30 from mock import patch
30 31
31 32 import nose.tools as nt
32 33 from nose import SkipTest
33 34
34 35 from IPython.testing import decorators as dec
35 36 from IPython.testing import tools as tt
36 37 from IPython.utils import py3compat
37 38 from IPython.utils.io import capture_output
38 39 from IPython.utils.tempdir import TemporaryDirectory
39 40 from IPython.core import debugger
40 41
41 42
42 43 def doctest_refbug():
43 44 """Very nasty problem with references held by multiple runs of a script.
44 45 See: https://github.com/ipython/ipython/issues/141
45 46
46 47 In [1]: _ip.clear_main_mod_cache()
47 48 # random
48 49
49 50 In [2]: %run refbug
50 51
51 52 In [3]: call_f()
52 53 lowercased: hello
53 54
54 55 In [4]: %run refbug
55 56
56 57 In [5]: call_f()
57 58 lowercased: hello
58 59 lowercased: hello
59 60 """
60 61
61 62
62 63 def doctest_run_builtins():
63 64 r"""Check that %run doesn't damage __builtins__.
64 65
65 66 In [1]: import tempfile
66 67
67 68 In [2]: bid1 = id(__builtins__)
68 69
69 70 In [3]: fname = tempfile.mkstemp('.py')[1]
70 71
71 72 In [3]: f = open(fname,'w')
72 73
73 74 In [4]: dummy= f.write('pass\n')
74 75
75 76 In [5]: f.flush()
76 77
77 78 In [6]: t1 = type(__builtins__)
78 79
79 80 In [7]: %run $fname
80 81
81 82 In [7]: f.close()
82 83
83 84 In [8]: bid2 = id(__builtins__)
84 85
85 86 In [9]: t2 = type(__builtins__)
86 87
87 88 In [10]: t1 == t2
88 89 Out[10]: True
89 90
90 91 In [10]: bid1 == bid2
91 92 Out[10]: True
92 93
93 94 In [12]: try:
94 95 ....: os.unlink(fname)
95 96 ....: except:
96 97 ....: pass
97 98 ....:
98 99 """
99 100
100 101
101 102 def doctest_run_option_parser():
102 103 r"""Test option parser in %run.
103 104
104 105 In [1]: %run print_argv.py
105 106 []
106 107
107 108 In [2]: %run print_argv.py print*.py
108 109 ['print_argv.py']
109 110
110 111 In [3]: %run -G print_argv.py print*.py
111 112 ['print*.py']
112 113
113 114 """
114 115
115 116
116 117 @dec.skip_win32
117 118 def doctest_run_option_parser_for_posix():
118 119 r"""Test option parser in %run (Linux/OSX specific).
119 120
120 121 You need double quote to escape glob in POSIX systems:
121 122
122 123 In [1]: %run print_argv.py print\\*.py
123 124 ['print*.py']
124 125
125 126 You can't use quote to escape glob in POSIX systems:
126 127
127 128 In [2]: %run print_argv.py 'print*.py'
128 129 ['print_argv.py']
129 130
130 131 """
131 132
132 133
133 134 @dec.skip_if_not_win32
134 135 def doctest_run_option_parser_for_windows():
135 136 r"""Test option parser in %run (Windows specific).
136 137
137 138 In Windows, you can't escape ``*` `by backslash:
138 139
139 140 In [1]: %run print_argv.py print\\*.py
140 141 ['print\\*.py']
141 142
142 143 You can use quote to escape glob:
143 144
144 145 In [2]: %run print_argv.py 'print*.py'
145 146 ['print*.py']
146 147
147 148 """
148 149
149 150
150 151 @py3compat.doctest_refactor_print
151 152 def doctest_reset_del():
152 153 """Test that resetting doesn't cause errors in __del__ methods.
153 154
154 155 In [2]: class A(object):
155 156 ...: def __del__(self):
156 157 ...: print str("Hi")
157 158 ...:
158 159
159 160 In [3]: a = A()
160 161
161 162 In [4]: get_ipython().reset()
162 163 Hi
163 164
164 165 In [5]: 1+1
165 166 Out[5]: 2
166 167 """
167 168
168 169 # For some tests, it will be handy to organize them in a class with a common
169 170 # setup that makes a temp file
170 171
171 172 class TestMagicRunPass(tt.TempFileMixin):
172 173
173 174 def setup(self):
174 175 content = "a = [1,2,3]\nb = 1"
175 176 self.mktmp(content)
176 177
177 178 def run_tmpfile(self):
178 179 _ip = get_ipython()
179 180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
180 181 # See below and ticket https://bugs.launchpad.net/bugs/366353
181 182 _ip.magic('run %s' % self.fname)
182 183
183 184 def run_tmpfile_p(self):
184 185 _ip = get_ipython()
185 186 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
186 187 # See below and ticket https://bugs.launchpad.net/bugs/366353
187 188 _ip.magic('run -p %s' % self.fname)
188 189
189 190 def test_builtins_id(self):
190 191 """Check that %run doesn't damage __builtins__ """
191 192 _ip = get_ipython()
192 193 # Test that the id of __builtins__ is not modified by %run
193 194 bid1 = id(_ip.user_ns['__builtins__'])
194 195 self.run_tmpfile()
195 196 bid2 = id(_ip.user_ns['__builtins__'])
196 197 nt.assert_equal(bid1, bid2)
197 198
198 199 def test_builtins_type(self):
199 200 """Check that the type of __builtins__ doesn't change with %run.
200 201
201 202 However, the above could pass if __builtins__ was already modified to
202 203 be a dict (it should be a module) by a previous use of %run. So we
203 204 also check explicitly that it really is a module:
204 205 """
205 206 _ip = get_ipython()
206 207 self.run_tmpfile()
207 208 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
208 209
209 210 def test_run_profile( self ):
210 211 """Test that the option -p, which invokes the profiler, do not
211 212 crash by invoking execfile"""
212 213 self.run_tmpfile_p()
213 214
214 215 def test_run_debug_twice(self):
215 216 # https://github.com/ipython/ipython/issues/10028
216 217 _ip = get_ipython()
217 218 with tt.fake_input(['c']):
218 219 _ip.magic('run -d %s' % self.fname)
219 220 with tt.fake_input(['c']):
220 221 _ip.magic('run -d %s' % self.fname)
221 222
222 223 def test_run_debug_twice_with_breakpoint(self):
223 224 """Make a valid python temp file."""
224 225 _ip = get_ipython()
225 226 with tt.fake_input(['b 2', 'c', 'c']):
226 227 _ip.magic('run -d %s' % self.fname)
227 228
228 229 with tt.fake_input(['c']):
229 230 with tt.AssertNotPrints('KeyError'):
230 231 _ip.magic('run -d %s' % self.fname)
231 232
232 233
233 234 class TestMagicRunSimple(tt.TempFileMixin):
234 235
235 236 def test_simpledef(self):
236 237 """Test that simple class definitions work."""
237 238 src = ("class foo: pass\n"
238 239 "def f(): return foo()")
239 240 self.mktmp(src)
240 241 _ip.magic('run %s' % self.fname)
241 242 _ip.run_cell('t = isinstance(f(), foo)')
242 243 nt.assert_true(_ip.user_ns['t'])
243 244
244 245 def test_obj_del(self):
245 246 """Test that object's __del__ methods are called on exit."""
246 247 if sys.platform == 'win32':
247 248 try:
248 249 import win32api
249 250 except ImportError:
250 251 raise SkipTest("Test requires pywin32")
251 252 src = ("class A(object):\n"
252 253 " def __del__(self):\n"
253 254 " print 'object A deleted'\n"
254 255 "a = A()\n")
255 256 self.mktmp(py3compat.doctest_refactor_print(src))
256 257 if dec.module_not_available('sqlite3'):
257 258 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
258 259 else:
259 260 err = None
260 261 tt.ipexec_validate(self.fname, 'object A deleted', err)
261 262
262 263 def test_aggressive_namespace_cleanup(self):
263 264 """Test that namespace cleanup is not too aggressive GH-238
264 265
265 266 Returning from another run magic deletes the namespace"""
266 267 # see ticket https://github.com/ipython/ipython/issues/238
267 268
268 269 with tt.TempFileMixin() as empty:
269 270 empty.mktmp('')
270 271 # On Windows, the filename will have \users in it, so we need to use the
271 272 # repr so that the \u becomes \\u.
272 273 src = ("ip = get_ipython()\n"
273 274 "for i in range(5):\n"
274 275 " try:\n"
275 276 " ip.magic(%r)\n"
276 277 " except NameError as e:\n"
277 278 " print(i)\n"
278 279 " break\n" % ('run ' + empty.fname))
279 280 self.mktmp(src)
280 281 _ip.magic('run %s' % self.fname)
281 282 _ip.run_cell('ip == get_ipython()')
282 283 nt.assert_equal(_ip.user_ns['i'], 4)
283 284
284 285 def test_run_second(self):
285 286 """Test that running a second file doesn't clobber the first, gh-3547
286 287 """
287 288 self.mktmp("avar = 1\n"
288 289 "def afunc():\n"
289 290 " return avar\n")
290 291
291 292 with tt.TempFileMixin() as empty:
292 293 empty.mktmp("")
293 294
294 295 _ip.magic('run %s' % self.fname)
295 296 _ip.magic('run %s' % empty.fname)
296 297 nt.assert_equal(_ip.user_ns['afunc'](), 1)
297 298
298 299 @dec.skip_win32
299 300 def test_tclass(self):
300 301 mydir = os.path.dirname(__file__)
301 302 tc = os.path.join(mydir, 'tclass')
302 303 src = ("%%run '%s' C-first\n"
303 304 "%%run '%s' C-second\n"
304 305 "%%run '%s' C-third\n") % (tc, tc, tc)
305 306 self.mktmp(src, '.ipy')
306 307 out = """\
307 308 ARGV 1-: ['C-first']
308 309 ARGV 1-: ['C-second']
309 310 tclass.py: deleting object: C-first
310 311 ARGV 1-: ['C-third']
311 312 tclass.py: deleting object: C-second
312 313 tclass.py: deleting object: C-third
313 314 """
314 315 if dec.module_not_available('sqlite3'):
315 316 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
316 317 else:
317 318 err = None
318 319 tt.ipexec_validate(self.fname, out, err)
319 320
320 321 def test_run_i_after_reset(self):
321 322 """Check that %run -i still works after %reset (gh-693)"""
322 323 src = "yy = zz\n"
323 324 self.mktmp(src)
324 325 _ip.run_cell("zz = 23")
325 326 _ip.magic('run -i %s' % self.fname)
326 327 nt.assert_equal(_ip.user_ns['yy'], 23)
327 328 _ip.magic('reset -f')
328 329 _ip.run_cell("zz = 23")
329 330 _ip.magic('run -i %s' % self.fname)
330 331 nt.assert_equal(_ip.user_ns['yy'], 23)
331 332
332 333 def test_unicode(self):
333 334 """Check that files in odd encodings are accepted."""
334 335 mydir = os.path.dirname(__file__)
335 336 na = os.path.join(mydir, 'nonascii.py')
336 337 _ip.magic('run "%s"' % na)
337 338 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
338 339
339 340 def test_run_py_file_attribute(self):
340 341 """Test handling of `__file__` attribute in `%run <file>.py`."""
341 342 src = "t = __file__\n"
342 343 self.mktmp(src)
343 344 _missing = object()
344 345 file1 = _ip.user_ns.get('__file__', _missing)
345 346 _ip.magic('run %s' % self.fname)
346 347 file2 = _ip.user_ns.get('__file__', _missing)
347 348
348 349 # Check that __file__ was equal to the filename in the script's
349 350 # namespace.
350 351 nt.assert_equal(_ip.user_ns['t'], self.fname)
351 352
352 353 # Check that __file__ was not leaked back into user_ns.
353 354 nt.assert_equal(file1, file2)
354 355
355 356 def test_run_ipy_file_attribute(self):
356 357 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
357 358 src = "t = __file__\n"
358 359 self.mktmp(src, ext='.ipy')
359 360 _missing = object()
360 361 file1 = _ip.user_ns.get('__file__', _missing)
361 362 _ip.magic('run %s' % self.fname)
362 363 file2 = _ip.user_ns.get('__file__', _missing)
363 364
364 365 # Check that __file__ was equal to the filename in the script's
365 366 # namespace.
366 367 nt.assert_equal(_ip.user_ns['t'], self.fname)
367 368
368 369 # Check that __file__ was not leaked back into user_ns.
369 370 nt.assert_equal(file1, file2)
370 371
371 372 def test_run_formatting(self):
372 373 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
373 374 src = "pass"
374 375 self.mktmp(src)
375 376 _ip.magic('run -t -N 1 %s' % self.fname)
376 377 _ip.magic('run -t -N 10 %s' % self.fname)
377 378
378 379 def test_ignore_sys_exit(self):
379 380 """Test the -e option to ignore sys.exit()"""
380 381 src = "import sys; sys.exit(1)"
381 382 self.mktmp(src)
382 383 with tt.AssertPrints('SystemExit'):
383 384 _ip.magic('run %s' % self.fname)
384 385
385 386 with tt.AssertNotPrints('SystemExit'):
386 387 _ip.magic('run -e %s' % self.fname)
387 388
388 389 @dec.skip_without('nbformat') # Requires jsonschema
389 390 def test_run_nb(self):
390 391 """Test %run notebook.ipynb"""
391 392 from nbformat import v4, writes
392 393 nb = v4.new_notebook(
393 394 cells=[
394 395 v4.new_markdown_cell("The Ultimate Question of Everything"),
395 396 v4.new_code_cell("answer=42")
396 397 ]
397 398 )
398 399 src = writes(nb, version=4)
399 400 self.mktmp(src, ext='.ipynb')
400 401
401 402 _ip.magic("run %s" % self.fname)
402 403
403 404 nt.assert_equal(_ip.user_ns['answer'], 42)
404 405
405 406
406 407
407 408 class TestMagicRunWithPackage(unittest.TestCase):
408 409
409 410 def writefile(self, name, content):
410 411 path = os.path.join(self.tempdir.name, name)
411 412 d = os.path.dirname(path)
412 413 if not os.path.isdir(d):
413 414 os.makedirs(d)
414 415 with open(path, 'w') as f:
415 416 f.write(textwrap.dedent(content))
416 417
417 418 def setUp(self):
418 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
419 """Temporary valid python package name."""
419 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
420 """Temporary (probably) valid python package name."""
420 421
421 422 self.value = int(random.random() * 10000)
422 423
423 424 self.tempdir = TemporaryDirectory()
424 425 self.__orig_cwd = py3compat.getcwd()
425 426 sys.path.insert(0, self.tempdir.name)
426 427
427 428 self.writefile(os.path.join(package, '__init__.py'), '')
428 429 self.writefile(os.path.join(package, 'sub.py'), """
429 430 x = {0!r}
430 431 """.format(self.value))
431 432 self.writefile(os.path.join(package, 'relative.py'), """
432 433 from .sub import x
433 434 """)
434 435 self.writefile(os.path.join(package, 'absolute.py'), """
435 436 from {0}.sub import x
436 437 """.format(package))
437 438
438 439 def tearDown(self):
439 440 os.chdir(self.__orig_cwd)
440 441 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
441 442 self.tempdir.cleanup()
442 443
443 444 def check_run_submodule(self, submodule, opts=''):
444 445 _ip.user_ns.pop('x', None)
445 446 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
446 447 self.assertEqual(_ip.user_ns['x'], self.value,
447 448 'Variable `x` is not loaded from module `{0}`.'
448 449 .format(submodule))
449 450
450 451 def test_run_submodule_with_absolute_import(self):
451 452 self.check_run_submodule('absolute')
452 453
453 454 def test_run_submodule_with_relative_import(self):
454 455 """Run submodule that has a relative import statement (#2727)."""
455 456 self.check_run_submodule('relative')
456 457
457 458 def test_prun_submodule_with_absolute_import(self):
458 459 self.check_run_submodule('absolute', '-p')
459 460
460 461 def test_prun_submodule_with_relative_import(self):
461 462 self.check_run_submodule('relative', '-p')
462 463
463 464 def with_fake_debugger(func):
464 465 @functools.wraps(func)
465 466 def wrapper(*args, **kwds):
466 467 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
467 468 return func(*args, **kwds)
468 469 return wrapper
469 470
470 471 @with_fake_debugger
471 472 def test_debug_run_submodule_with_absolute_import(self):
472 473 self.check_run_submodule('absolute', '-d')
473 474
474 475 @with_fake_debugger
475 476 def test_debug_run_submodule_with_relative_import(self):
476 477 self.check_run_submodule('relative', '-d')
477 478
478 479 def test_run__name__():
479 480 with TemporaryDirectory() as td:
480 481 path = pjoin(td, 'foo.py')
481 482 with open(path, 'w') as f:
482 483 f.write("q = __name__")
483 484
484 485 _ip.user_ns.pop('q', None)
485 486 _ip.magic('run {}'.format(path))
486 487 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
487 488
488 489 _ip.magic('run -n {}'.format(path))
489 490 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
490 491
491 492 def test_run_tb():
492 493 """Test traceback offset in %run"""
493 494 with TemporaryDirectory() as td:
494 495 path = pjoin(td, 'foo.py')
495 496 with open(path, 'w') as f:
496 497 f.write('\n'.join([
497 498 "def foo():",
498 499 " return bar()",
499 500 "def bar():",
500 501 " raise RuntimeError('hello!')",
501 502 "foo()",
502 503 ]))
503 504 with capture_output() as io:
504 505 _ip.magic('run {}'.format(path))
505 506 out = io.stdout
506 507 nt.assert_not_in("execfile", out)
507 508 nt.assert_in("RuntimeError", out)
508 509 nt.assert_equal(out.count("---->"), 3)
509 510
510 511 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
511 512 def test_script_tb():
512 513 """Test traceback offset in `ipython script.py`"""
513 514 with TemporaryDirectory() as td:
514 515 path = pjoin(td, 'foo.py')
515 516 with open(path, 'w') as f:
516 517 f.write('\n'.join([
517 518 "def foo():",
518 519 " return bar()",
519 520 "def bar():",
520 521 " raise RuntimeError('hello!')",
521 522 "foo()",
522 523 ]))
523 524 out, err = tt.ipexec(path)
524 525 nt.assert_not_in("execfile", out)
525 526 nt.assert_in("RuntimeError", out)
526 527 nt.assert_equal(out.count("---->"), 3)
527 528
General Comments 0
You need to be logged in to leave comments. Login now