test_run.py
416 lines
| 12.6 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r6303 | # encoding: utf-8 | ||
Fernando Perez
|
r2414 | """Tests for code execution (%run and related), which is particularly tricky. | ||
Because of how %run manages namespaces, and the fact that we are trying here to | ||||
verify subtle object deletion and reference counting issues, the %run tests | ||||
will be kept in this separate file. This makes it easier to aggregate in one | ||||
place the tricks needed to handle it; most other magics are much easier to test | ||||
and we do so in a common test_magic file. | ||||
""" | ||||
from __future__ import absolute_import | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Takafumi Arakaki
|
r10553 | import functools | ||
Fernando Perez
|
r2414 | import os | ||
Takafumi Arakaki
|
r10553 | import random | ||
Fernando Perez
|
r2414 | import sys | ||
import tempfile | ||||
Takafumi Arakaki
|
r9994 | import textwrap | ||
Takafumi Arakaki
|
r10553 | import unittest | ||
Fernando Perez
|
r2414 | |||
import nose.tools as nt | ||||
Min RK
|
r4105 | from nose import SkipTest | ||
Fernando Perez
|
r2414 | |||
from IPython.testing import decorators as dec | ||||
from IPython.testing import tools as tt | ||||
Thomas Kluyver
|
r4896 | from IPython.utils import py3compat | ||
Takafumi Arakaki
|
r9994 | from IPython.utils.tempdir import TemporaryDirectory | ||
Takafumi Arakaki
|
r10002 | from IPython.core import debugger | ||
Fernando Perez
|
r2414 | |||
#----------------------------------------------------------------------------- | ||||
# Test functions begin | ||||
#----------------------------------------------------------------------------- | ||||
def doctest_refbug(): | ||||
"""Very nasty problem with references held by multiple runs of a script. | ||||
Thomas Kluyver
|
r3917 | See: https://github.com/ipython/ipython/issues/141 | ||
Fernando Perez
|
r2414 | |||
In [1]: _ip.clear_main_mod_cache() | ||||
# random | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2414 | In [2]: %run refbug | ||
In [3]: call_f() | ||||
lowercased: hello | ||||
In [4]: %run refbug | ||||
In [5]: call_f() | ||||
lowercased: hello | ||||
lowercased: hello | ||||
""" | ||||
def doctest_run_builtins(): | ||||
r"""Check that %run doesn't damage __builtins__. | ||||
In [1]: import tempfile | ||||
In [2]: bid1 = id(__builtins__) | ||||
In [3]: fname = tempfile.mkstemp('.py')[1] | ||||
In [3]: f = open(fname,'w') | ||||
Thomas Kluyver
|
r4895 | In [4]: dummy= f.write('pass\n') | ||
Fernando Perez
|
r2414 | |||
In [5]: f.flush() | ||||
In [6]: t1 = type(__builtins__) | ||||
Fernando Perez
|
r2484 | In [7]: %run $fname | ||
Fernando Perez
|
r2414 | |||
In [7]: f.close() | ||||
In [8]: bid2 = id(__builtins__) | ||||
In [9]: t2 = type(__builtins__) | ||||
In [10]: t1 == t2 | ||||
Out[10]: True | ||||
In [10]: bid1 == bid2 | ||||
Out[10]: True | ||||
In [12]: try: | ||||
....: os.unlink(fname) | ||||
....: except: | ||||
....: pass | ||||
Bernardo B. Marques
|
r4872 | ....: | ||
Fernando Perez
|
r2414 | """ | ||
Fernando Perez
|
r3107 | |||
Takafumi Arakaki
|
r8121 | |||
def doctest_run_option_parser(): | ||||
r"""Test option parser in %run. | ||||
In [1]: %run print_argv.py | ||||
[] | ||||
In [2]: %run print_argv.py print*.py | ||||
['print_argv.py'] | ||||
Takafumi Arakaki
|
r8548 | In [3]: %run -G print_argv.py print*.py | ||
Takafumi Arakaki
|
r8121 | ['print*.py'] | ||
Takafumi Arakaki
|
r8548 | """ | ||
@dec.skip_win32 | ||||
def doctest_run_option_parser_for_posix(): | ||||
r"""Test option parser in %run (Linux/OSX specific). | ||||
You need double quote to escape glob in POSIX systems: | ||||
In [1]: %run print_argv.py print\\*.py | ||||
['print*.py'] | ||||
You can't use quote to escape glob in POSIX systems: | ||||
In [2]: %run print_argv.py 'print*.py' | ||||
Takafumi Arakaki
|
r8121 | ['print_argv.py'] | ||
Takafumi Arakaki
|
r8548 | """ | ||
Takafumi Arakaki
|
r8636 | @dec.skip_if_not_win32 | ||
Takafumi Arakaki
|
r8548 | def doctest_run_option_parser_for_windows(): | ||
r"""Test option parser in %run (Windows specific). | ||||
In Windows, you can't escape ``*` `by backslash: | ||||
In [1]: %run print_argv.py print\\*.py | ||||
['print\\*.py'] | ||||
You can use quote to escape glob: | ||||
In [2]: %run print_argv.py 'print*.py' | ||||
Takafumi Arakaki
|
r8121 | ['print*.py'] | ||
""" | ||||
Thomas Kluyver
|
r4896 | @py3compat.doctest_refactor_print | ||
Fernando Perez
|
r3107 | def doctest_reset_del(): | ||
"""Test that resetting doesn't cause errors in __del__ methods. | ||||
In [2]: class A(object): | ||||
...: def __del__(self): | ||||
Thomas Kluyver
|
r3156 | ...: print str("Hi") | ||
Bernardo B. Marques
|
r4872 | ...: | ||
Fernando Perez
|
r3107 | |||
In [3]: a = A() | ||||
In [4]: get_ipython().reset() | ||||
Thomas Kluyver
|
r3156 | Hi | ||
Fernando Perez
|
r3107 | |||
In [5]: 1+1 | ||||
Out[5]: 2 | ||||
""" | ||||
Fernando Perez
|
r2414 | |||
# For some tests, it will be handy to organize them in a class with a common | ||||
# setup that makes a temp file | ||||
Fernando Perez
|
r2415 | class TestMagicRunPass(tt.TempFileMixin): | ||
Fernando Perez
|
r2414 | |||
def setup(self): | ||||
"""Make a valid python temp file.""" | ||||
self.mktmp('pass\n') | ||||
Alcides
|
r5238 | |||
Fernando Perez
|
r2414 | def run_tmpfile(self): | ||
_ip = get_ipython() | ||||
# This fails on Windows if self.tmpfile.name has spaces or "~" in it. | ||||
# See below and ticket https://bugs.launchpad.net/bugs/366353 | ||||
Fernando Perez
|
r2484 | _ip.magic('run %s' % self.fname) | ||
Alcides
|
r5238 | |||
def run_tmpfile_p(self): | ||||
_ip = get_ipython() | ||||
# This fails on Windows if self.tmpfile.name has spaces or "~" in it. | ||||
# See below and ticket https://bugs.launchpad.net/bugs/366353 | ||||
_ip.magic('run -p %s' % self.fname) | ||||
Fernando Perez
|
r2414 | |||
def test_builtins_id(self): | ||||
"""Check that %run doesn't damage __builtins__ """ | ||||
_ip = get_ipython() | ||||
# Test that the id of __builtins__ is not modified by %run | ||||
bid1 = id(_ip.user_ns['__builtins__']) | ||||
self.run_tmpfile() | ||||
bid2 = id(_ip.user_ns['__builtins__']) | ||||
Bradley M. Froehle
|
r7879 | nt.assert_equal(bid1, bid2) | ||
Fernando Perez
|
r2414 | |||
def test_builtins_type(self): | ||||
"""Check that the type of __builtins__ doesn't change with %run. | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2414 | However, the above could pass if __builtins__ was already modified to | ||
be a dict (it should be a module) by a previous use of %run. So we | ||||
also check explicitly that it really is a module: | ||||
""" | ||||
_ip = get_ipython() | ||||
self.run_tmpfile() | ||||
Bradley M. Froehle
|
r7879 | nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) | ||
Fernando Perez
|
r2414 | |||
def test_prompts(self): | ||||
"""Test that prompts correctly generate after %run""" | ||||
self.run_tmpfile() | ||||
_ip = get_ipython() | ||||
Thomas Kluyver
|
r5495 | p2 = _ip.prompt_manager.render('in2').strip() | ||
Bradley M. Froehle
|
r7875 | nt.assert_equal(p2[:3], '...') | ||
Alcides
|
r5238 | |||
def test_run_profile( self ): | ||||
"""Test that the option -p, which invokes the profiler, do not | ||||
crash by invoking execfile""" | ||||
_ip = get_ipython() | ||||
self.run_tmpfile_p() | ||||
Fernando Perez
|
r2414 | |||
Fernando Perez
|
r2415 | class TestMagicRunSimple(tt.TempFileMixin): | ||
Fernando Perez
|
r2414 | |||
def test_simpledef(self): | ||||
"""Test that simple class definitions work.""" | ||||
src = ("class foo: pass\n" | ||||
"def f(): return foo()") | ||||
self.mktmp(src) | ||||
Fernando Perez
|
r2451 | _ip.magic('run %s' % self.fname) | ||
Thomas Kluyver
|
r3752 | _ip.run_cell('t = isinstance(f(), foo)') | ||
Fernando Perez
|
r2414 | nt.assert_true(_ip.user_ns['t']) | ||
Fernando Perez
|
r2446 | |||
Fernando Perez
|
r2414 | def test_obj_del(self): | ||
"""Test that object's __del__ methods are called on exit.""" | ||||
Min RK
|
r4105 | if sys.platform == 'win32': | ||
try: | ||||
import win32api | ||||
except ImportError: | ||||
raise SkipTest("Test requires pywin32") | ||||
Fernando Perez
|
r2414 | src = ("class A(object):\n" | ||
" def __del__(self):\n" | ||||
" print 'object A deleted'\n" | ||||
"a = A()\n") | ||||
Thomas Kluyver
|
r4896 | self.mktmp(py3compat.doctest_refactor_print(src)) | ||
MinRK
|
r5147 | if dec.module_not_available('sqlite3'): | ||
err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | ||||
else: | ||||
err = None | ||||
tt.ipexec_validate(self.fname, 'object A deleted', err) | ||||
@dec.skip_known_failure | ||||
Paul Ivanov
|
r3502 | def test_aggressive_namespace_cleanup(self): | ||
"""Test that namespace cleanup is not too aggressive GH-238 | ||||
Paul Ivanov
|
r3503 | |||
Returning from another run magic deletes the namespace""" | ||||
Paul Ivanov
|
r3502 | # see ticket https://github.com/ipython/ipython/issues/238 | ||
class secondtmp(tt.TempFileMixin): pass | ||||
empty = secondtmp() | ||||
empty.mktmp('') | ||||
src = ("ip = get_ipython()\n" | ||||
"for i in range(5):\n" | ||||
" try:\n" | ||||
" ip.magic('run %s')\n" | ||||
Matthias BUSSONNIER
|
r7787 | " except NameError as e:\n" | ||
Paul Ivanov
|
r3502 | " print i;break\n" % empty.fname) | ||
Thomas Kluyver
|
r4896 | self.mktmp(py3compat.doctest_refactor_print(src)) | ||
Paul Ivanov
|
r3502 | _ip.magic('run %s' % self.fname) | ||
Thomas Kluyver
|
r3752 | _ip.run_cell('ip == get_ipython()') | ||
Bradley M. Froehle
|
r7879 | nt.assert_equal(_ip.user_ns['i'], 5) | ||
Fernando Perez
|
r2414 | |||
Fernando Perez
|
r2446 | @dec.skip_win32 | ||
Fernando Perez
|
r2414 | def test_tclass(self): | ||
mydir = os.path.dirname(__file__) | ||||
tc = os.path.join(mydir, 'tclass') | ||||
src = ("%%run '%s' C-first\n" | ||||
Thomas Kluyver
|
r3763 | "%%run '%s' C-second\n" | ||
"%%run '%s' C-third\n") % (tc, tc, tc) | ||||
Fernando Perez
|
r2414 | self.mktmp(src, '.ipy') | ||
out = """\ | ||||
MinRK
|
r5126 | ARGV 1-: ['C-first'] | ||
ARGV 1-: ['C-second'] | ||||
Fernando Perez
|
r2414 | tclass.py: deleting object: C-first | ||
MinRK
|
r5126 | ARGV 1-: ['C-third'] | ||
Thomas Kluyver
|
r3762 | tclass.py: deleting object: C-second | ||
Thomas Kluyver
|
r3763 | tclass.py: deleting object: C-third | ||
Fernando Perez
|
r2414 | """ | ||
MinRK
|
r5147 | if dec.module_not_available('sqlite3'): | ||
err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | ||||
else: | ||||
err = None | ||||
tt.ipexec_validate(self.fname, out, err) | ||||
Thomas Kluyver
|
r5466 | |||
def test_run_i_after_reset(self): | ||||
"""Check that %run -i still works after %reset (gh-693)""" | ||||
src = "yy = zz\n" | ||||
self.mktmp(src) | ||||
_ip.run_cell("zz = 23") | ||||
_ip.magic('run -i %s' % self.fname) | ||||
Bradley M. Froehle
|
r7879 | nt.assert_equal(_ip.user_ns['yy'], 23) | ||
Thomas Kluyver
|
r5466 | _ip.magic('reset -f') | ||
_ip.run_cell("zz = 23") | ||||
_ip.magic('run -i %s' % self.fname) | ||||
Bradley M. Froehle
|
r7879 | nt.assert_equal(_ip.user_ns['yy'], 23) | ||
Thomas Kluyver
|
r6300 | |||
def test_unicode(self): | ||||
"""Check that files in odd encodings are accepted.""" | ||||
mydir = os.path.dirname(__file__) | ||||
na = os.path.join(mydir, 'nonascii.py') | ||||
Jonathan March
|
r7674 | _ip.magic('run "%s"' % na) | ||
Bradley M. Froehle
|
r7879 | nt.assert_equal(_ip.user_ns['u'], u'Ўт№Ф') | ||
Bradley M. Froehle
|
r8530 | |||
def test_run_py_file_attribute(self): | ||||
"""Test handling of `__file__` attribute in `%run <file>.py`.""" | ||||
src = "t = __file__\n" | ||||
self.mktmp(src) | ||||
_missing = object() | ||||
file1 = _ip.user_ns.get('__file__', _missing) | ||||
_ip.magic('run %s' % self.fname) | ||||
file2 = _ip.user_ns.get('__file__', _missing) | ||||
# Check that __file__ was equal to the filename in the script's | ||||
# namespace. | ||||
nt.assert_equal(_ip.user_ns['t'], self.fname) | ||||
# Check that __file__ was not leaked back into user_ns. | ||||
nt.assert_equal(file1, file2) | ||||
def test_run_ipy_file_attribute(self): | ||||
"""Test handling of `__file__` attribute in `%run <file.ipy>`.""" | ||||
src = "t = __file__\n" | ||||
self.mktmp(src, ext='.ipy') | ||||
_missing = object() | ||||
file1 = _ip.user_ns.get('__file__', _missing) | ||||
_ip.magic('run %s' % self.fname) | ||||
file2 = _ip.user_ns.get('__file__', _missing) | ||||
# Check that __file__ was equal to the filename in the script's | ||||
# namespace. | ||||
nt.assert_equal(_ip.user_ns['t'], self.fname) | ||||
# Check that __file__ was not leaked back into user_ns. | ||||
nt.assert_equal(file1, file2) | ||||
Robert Marchman
|
r9360 | |||
def test_run_formatting(self): | ||||
""" Test that %run -t -N<N> does not raise a TypeError for N > 1.""" | ||||
src = "pass" | ||||
self.mktmp(src) | ||||
_ip.magic('run -t -N 1 %s' % self.fname) | ||||
_ip.magic('run -t -N 10 %s' % self.fname) | ||||
Takafumi Arakaki
|
r9994 | |||
class TestMagicRunWithPackage(unittest.TestCase): | ||||
def writefile(self, name, content): | ||||
path = os.path.join(self.tempdir.name, name) | ||||
d = os.path.dirname(path) | ||||
if not os.path.isdir(d): | ||||
os.makedirs(d) | ||||
with open(path, 'w') as f: | ||||
f.write(textwrap.dedent(content)) | ||||
def setUp(self): | ||||
self.package = package = 'tmp{0}'.format(repr(random.random())[2:]) | ||||
"""Temporary valid python package name.""" | ||||
self.value = int(random.random() * 10000) | ||||
self.tempdir = TemporaryDirectory() | ||||
self.__orig_cwd = os.getcwdu() | ||||
sys.path.insert(0, self.tempdir.name) | ||||
self.writefile(os.path.join(package, '__init__.py'), '') | ||||
Takafumi Arakaki
|
r10007 | self.writefile(os.path.join(package, 'sub.py'), """ | ||
Takafumi Arakaki
|
r9994 | x = {0!r} | ||
""".format(self.value)) | ||||
self.writefile(os.path.join(package, 'relative.py'), """ | ||||
Takafumi Arakaki
|
r10007 | from .sub import x | ||
Takafumi Arakaki
|
r9994 | """) | ||
self.writefile(os.path.join(package, 'absolute.py'), """ | ||||
Takafumi Arakaki
|
r10007 | from {0}.sub import x | ||
Takafumi Arakaki
|
r9994 | """.format(package)) | ||
def tearDown(self): | ||||
os.chdir(self.__orig_cwd) | ||||
Takafumi Arakaki
|
r10010 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] | ||
Takafumi Arakaki
|
r9994 | self.tempdir.cleanup() | ||
Takafumi Arakaki
|
r9998 | def check_run_submodule(self, submodule, opts=''): | ||
_ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) | ||||
Takafumi Arakaki
|
r9994 | self.assertEqual(_ip.user_ns['x'], self.value, | ||
'Variable `x` is not loaded from module `{0}`.' | ||||
.format(submodule)) | ||||
def test_run_submodule_with_absolute_import(self): | ||||
self.check_run_submodule('absolute') | ||||
def test_run_submodule_with_relative_import(self): | ||||
"""Run submodule that has a relative import statement (#2727).""" | ||||
self.check_run_submodule('relative') | ||||
Takafumi Arakaki
|
r9998 | |||
def test_prun_submodule_with_absolute_import(self): | ||||
self.check_run_submodule('absolute', '-p') | ||||
def test_prun_submodule_with_relative_import(self): | ||||
self.check_run_submodule('relative', '-p') | ||||
Takafumi Arakaki
|
r10002 | |||
def with_fake_debugger(func): | ||||
@functools.wraps(func) | ||||
def wrapper(*args, **kwds): | ||||
with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)): | ||||
return func(*args, **kwds) | ||||
return wrapper | ||||
@with_fake_debugger | ||||
def test_debug_run_submodule_with_absolute_import(self): | ||||
self.check_run_submodule('absolute', '-d') | ||||
@with_fake_debugger | ||||
def test_debug_run_submodule_with_relative_import(self): | ||||
self.check_run_submodule('relative', '-d') | ||||