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