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