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