##// END OF EJS Templates
Massive amount of work to improve the test suite, restores doctests....
Fernando Perez -
Show More
@@ -0,0 +1,185 b''
1 """Tests for code execution (%run and related), which is particularly tricky.
2
3 Because of how %run manages namespaces, and the fact that we are trying here to
4 verify subtle object deletion and reference counting issues, the %run tests
5 will be kept in this separate file. This makes it easier to aggregate in one
6 place the tricks needed to handle it; most other magics are much easier to test
7 and we do so in a common test_magic file.
8 """
9 from __future__ import absolute_import
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 # stdlib
16 import os
17 import sys
18 import tempfile
19
20 # third-party
21 import nose.tools as nt
22
23 # our own
24 from IPython.utils.platutils import find_cmd
25 from IPython.utils import genutils
26 from IPython.testing import decorators as dec
27 from IPython.testing import tools as tt
28
29 #-----------------------------------------------------------------------------
30 # Test functions begin
31 #-----------------------------------------------------------------------------
32
33 def doctest_refbug():
34 """Very nasty problem with references held by multiple runs of a script.
35 See: https://bugs.launchpad.net/ipython/+bug/269966
36
37 In [1]: _ip.clear_main_mod_cache()
38 # random
39
40 In [2]: %run refbug
41
42 In [3]: call_f()
43 lowercased: hello
44
45 In [4]: %run refbug
46
47 In [5]: call_f()
48 lowercased: hello
49 lowercased: hello
50 """
51
52
53 def doctest_run_builtins():
54 r"""Check that %run doesn't damage __builtins__.
55
56 In [1]: import tempfile
57
58 In [2]: bid1 = id(__builtins__)
59
60 In [3]: fname = tempfile.mkstemp('.py')[1]
61
62 In [3]: f = open(fname,'w')
63
64 In [4]: f.write('pass\n')
65
66 In [5]: f.flush()
67
68 In [6]: t1 = type(__builtins__)
69
70 In [7]: %run "$fname"
71
72 In [7]: f.close()
73
74 In [8]: bid2 = id(__builtins__)
75
76 In [9]: t2 = type(__builtins__)
77
78 In [10]: t1 == t2
79 Out[10]: True
80
81 In [10]: bid1 == bid2
82 Out[10]: True
83
84 In [12]: try:
85 ....: os.unlink(fname)
86 ....: except:
87 ....: pass
88 ....:
89 """
90
91 # For some tests, it will be handy to organize them in a class with a common
92 # setup that makes a temp file
93
94 class TempFileMixin(object):
95 def mktmp(self, src, ext='.py'):
96 """Make a valid python temp file."""
97 fname, f = tt.temp_pyfile(src, ext)
98 self.tmpfile = f
99 self.fname = fname
100
101 def teardown(self):
102 self.tmpfile.close()
103 try:
104 os.unlink(self.fname)
105 except:
106 # On Windows, even though we close the file, we still can't delete
107 # it. I have no clue why
108 pass
109
110
111 class TestMagicRunPass(TempFileMixin):
112
113 def setup(self):
114 """Make a valid python temp file."""
115 self.mktmp('pass\n')
116
117 def run_tmpfile(self):
118 _ip = get_ipython()
119 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
120 # See below and ticket https://bugs.launchpad.net/bugs/366353
121 _ip.magic('run "%s"' % self.fname)
122
123 def test_builtins_id(self):
124 """Check that %run doesn't damage __builtins__ """
125 _ip = get_ipython()
126 # Test that the id of __builtins__ is not modified by %run
127 bid1 = id(_ip.user_ns['__builtins__'])
128 self.run_tmpfile()
129 bid2 = id(_ip.user_ns['__builtins__'])
130 tt.assert_equals(bid1, bid2)
131
132 def test_builtins_type(self):
133 """Check that the type of __builtins__ doesn't change with %run.
134
135 However, the above could pass if __builtins__ was already modified to
136 be a dict (it should be a module) by a previous use of %run. So we
137 also check explicitly that it really is a module:
138 """
139 _ip = get_ipython()
140 self.run_tmpfile()
141 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
142
143 def test_prompts(self):
144 """Test that prompts correctly generate after %run"""
145 self.run_tmpfile()
146 _ip = get_ipython()
147 p2 = str(_ip.outputcache.prompt2).strip()
148 nt.assert_equals(p2[:3], '...')
149
150
151 class TestMagicRunSimple(TempFileMixin):
152
153 def test_simpledef(self):
154 """Test that simple class definitions work."""
155 src = ("class foo: pass\n"
156 "def f(): return foo()")
157 self.mktmp(src)
158 _ip.magic('run "%s"' % self.fname)
159 _ip.runlines('t = isinstance(f(), foo)')
160 nt.assert_true(_ip.user_ns['t'])
161
162 def test_obj_del(self):
163 """Test that object's __del__ methods are called on exit."""
164
165 # This test is known to fail on win32.
166 # See ticket https://bugs.launchpad.net/bugs/366334
167 src = ("class A(object):\n"
168 " def __del__(self):\n"
169 " print 'object A deleted'\n"
170 "a = A()\n")
171 self.mktmp(src)
172 tt.ipexec_validate(self.fname, 'object A deleted')
173
174 def test_tclass(self):
175 mydir = os.path.dirname(__file__)
176 tc = os.path.join(mydir, 'tclass')
177 src = ("%%run '%s' C-first\n"
178 "%%run '%s' C-second\n") % (tc, tc)
179 self.mktmp(src, '.ipy')
180 out = """\
181 ARGV 1-: ['C-first']
182 ARGV 1-: ['C-second']
183 tclass.py: deleting object: C-first
184 """
185 tt.ipexec_validate(self.fname, out)
@@ -3506,6 +3506,7 b' Defaulting color scheme to \'NoColor\'"""'
3506 3506 """Reload an IPython extension by its module name."""
3507 3507 self.reload_extension(module_str)
3508 3508
3509 @testdec.skip_doctest
3509 3510 def magic_install_profiles(self, s):
3510 3511 """Install the default IPython profiles into the .ipython dir.
3511 3512
@@ -416,7 +416,7 b' class PrefilterManager(Component):'
416 416 # print "prefiltered line: %r" % prefiltered
417 417 return prefiltered
418 418
419 def prefilter_lines(self, lines, continue_prompt):
419 def prefilter_lines(self, lines, continue_prompt=False):
420 420 """Prefilter multiple input lines of text.
421 421
422 422 This is the main entry point for prefiltering multiple lines of
@@ -1,16 +1,12 b''
1 """Simple script to instantiate a class for testing %run"""
1 """Simple script to be run *twice*, to check reference counting bugs.
2 2
3 import sys
4
5 # An external test will check that calls to f() work after %run
6 class foo: pass
3 See test_run for details."""
7 4
8 def f():
9 return foo()
5 import sys
10 6
11 # We also want to ensure that while objects remain available for immediate
12 # access, objects from *previous* runs of the same script get collected, to
13 # avoid accumulating massive amounts of old references.
7 # We want to ensure that while objects remain available for immediate access,
8 # objects from *previous* runs of the same script get collected, to avoid
9 # accumulating massive amounts of old references.
14 10 class C(object):
15 11 def __init__(self,name):
16 12 self.name = name
@@ -18,6 +14,7 b' class C(object):'
18 14 def __del__(self):
19 15 print 'tclass.py: deleting object:',self.name
20 16
17
21 18 try:
22 19 name = sys.argv[1]
23 20 except IndexError:
@@ -25,3 +22,8 b' except IndexError:'
25 22 else:
26 23 if name.startswith('C'):
27 24 c = C(name)
25
26 #print >> sys.stderr, "ARGV:", sys.argv # dbg
27 # This print statement is NOT debugging, we're making the check on a completely
28 # separate process so we verify by capturing stdout.
29 print 'ARGV 1-:', sys.argv[1:]
@@ -2,21 +2,31 b''
2 2
3 3 Needs to be run by nose (to make ipython session available).
4 4 """
5 from __future__ import absolute_import
5 6
7 #-----------------------------------------------------------------------------
8 # Imports
9 #-----------------------------------------------------------------------------
10
11 # stdlib
6 12 import os
7 13 import sys
8 14 import tempfile
9 15 import types
10 16 from cStringIO import StringIO
11 17
18 # third-party
12 19 import nose.tools as nt
13 20
21 # our own
22 from IPython.utils import genutils
14 23 from IPython.utils.platutils import find_cmd, get_long_path_name
15 24 from IPython.testing import decorators as dec
16 25 from IPython.testing import tools as tt
17 26
18 27 #-----------------------------------------------------------------------------
19 28 # Test functions begin
29 #-----------------------------------------------------------------------------
20 30
21 31 def test_rehashx():
22 32 # clear up everything
@@ -63,17 +73,6 b' def doctest_hist_r():'
63 73 hist -n -r 2 # random
64 74 """
65 75
66 # This test is known to fail on win32.
67 # See ticket https://bugs.launchpad.net/bugs/366334
68 def test_obj_del():
69 _ip = get_ipython()
70 """Test that object's __del__ methods are called on exit."""
71 test_dir = os.path.dirname(__file__)
72 del_file = os.path.join(test_dir,'obj_del.py')
73 ipython_cmd = find_cmd('ipython')
74 out = _ip.getoutput('%s %s' % (ipython_cmd, del_file))
75 nt.assert_equals(out,'obj_del.py: object A deleted')
76
77 76
78 77 def test_shist():
79 78 # Simple tests of ShadowHist class - test generator.
@@ -113,161 +112,6 b' def test_numpy_clear_array_undec():'
113 112 yield (nt.assert_false, 'a' in _ip.user_ns)
114 113
115 114
116 @dec.skip()
117 def test_fail_dec(*a,**k):
118 yield nt.assert_true, False
119
120 @dec.skip('This one shouldn not run')
121 def test_fail_dec2(*a,**k):
122 yield nt.assert_true, False
123
124 @dec.skipknownfailure
125 def test_fail_dec3(*a,**k):
126 yield nt.assert_true, False
127
128
129 def doctest_refbug():
130 """Very nasty problem with references held by multiple runs of a script.
131 See: https://bugs.launchpad.net/ipython/+bug/269966
132
133 In [1]: _ip.clear_main_mod_cache()
134
135 In [2]: run refbug
136
137 In [3]: call_f()
138 lowercased: hello
139
140 In [4]: run refbug
141
142 In [5]: call_f()
143 lowercased: hello
144 lowercased: hello
145 """
146
147 #-----------------------------------------------------------------------------
148 # Tests for %run
149 #-----------------------------------------------------------------------------
150
151 # %run is critical enough that it's a good idea to have a solid collection of
152 # tests for it, some as doctests and some as normal tests.
153
154 def doctest_run_ns():
155 """Classes declared %run scripts must be instantiable afterwards.
156
157 In [11]: run tclass foo
158
159 In [12]: isinstance(f(),foo)
160 Out[12]: True
161 """
162
163
164 def doctest_run_ns2():
165 """Classes declared %run scripts must be instantiable afterwards.
166
167 In [4]: run tclass C-first_pass
168
169 In [5]: run tclass C-second_pass
170 tclass.py: deleting object: C-first_pass
171 """
172
173 def doctest_run_builtins():
174 """Check that %run doesn't damage __builtins__ via a doctest.
175
176 This is similar to the test_run_builtins, but I want *both* forms of the
177 test to catch any possible glitches in our testing machinery, since that
178 modifies %run somewhat. So for this, we have both a normal test (below)
179 and a doctest (this one).
180
181 In [1]: import tempfile
182
183 In [2]: bid1 = id(__builtins__)
184
185 In [3]: fname = tempfile.mkstemp()[1]
186
187 In [3]: f = open(fname,'w')
188
189 In [4]: f.write('pass\\n')
190
191 In [5]: f.flush()
192
193 In [6]: print type(__builtins__)
194 <type 'module'>
195
196 In [7]: %run "$fname"
197
198 In [7]: f.close()
199
200 In [8]: bid2 = id(__builtins__)
201
202 In [9]: print type(__builtins__)
203 <type 'module'>
204
205 In [10]: bid1 == bid2
206 Out[10]: True
207
208 In [12]: try:
209 ....: os.unlink(fname)
210 ....: except:
211 ....: pass
212 ....:
213 """
214
215 # For some tests, it will be handy to organize them in a class with a common
216 # setup that makes a temp file
217
218 class TestMagicRun(object):
219
220 def setup(self):
221 """Make a valid python temp file."""
222 fname = tempfile.mkstemp('.py')[1]
223 f = open(fname,'w')
224 f.write('pass\n')
225 f.flush()
226 self.tmpfile = f
227 self.fname = fname
228
229 def run_tmpfile(self):
230 _ip = get_ipython()
231 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
232 # See below and ticket https://bugs.launchpad.net/bugs/366353
233 _ip.magic('run "%s"' % self.fname)
234
235 def test_builtins_id(self):
236 """Check that %run doesn't damage __builtins__ """
237 _ip = get_ipython()
238 # Test that the id of __builtins__ is not modified by %run
239 bid1 = id(_ip.user_ns['__builtins__'])
240 self.run_tmpfile()
241 bid2 = id(_ip.user_ns['__builtins__'])
242 tt.assert_equals(bid1, bid2)
243
244 def test_builtins_type(self):
245 """Check that the type of __builtins__ doesn't change with %run.
246
247 However, the above could pass if __builtins__ was already modified to
248 be a dict (it should be a module) by a previous use of %run. So we
249 also check explicitly that it really is a module:
250 """
251 _ip = get_ipython()
252 self.run_tmpfile()
253 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
254
255 def test_prompts(self):
256 """Test that prompts correctly generate after %run"""
257 self.run_tmpfile()
258 _ip = get_ipython()
259 p2 = str(_ip.outputcache.prompt2).strip()
260 nt.assert_equals(p2[:3], '...')
261
262 def teardown(self):
263 self.tmpfile.close()
264 try:
265 os.unlink(self.fname)
266 except:
267 # On Windows, even though we close the file, we still can't delete
268 # it. I have no clue why
269 pass
270
271 115 # Multiple tests for clipboard pasting
272 116 @dec.parametric
273 117 def test_paste():
@@ -64,6 +64,9 b" if sys.version[0]=='2':"
64 64 else:
65 65 from _paramtestpy3 import parametric
66 66
67 # Expose the unittest-driven decorators
68 from ipunittest import ipdoctest, ipdocstring
69
67 70 # Grab the numpy-specific decorators which we keep in a file that we
68 71 # occasionally update from upstream: decorators.py is a copy of
69 72 # numpy.testing.decorators, we expose all of it here.
@@ -16,6 +16,8 b' For now, this script requires that both nose and twisted are installed. This'
16 16 will change in the future.
17 17 """
18 18
19 from __future__ import absolute_import
20
19 21 #-----------------------------------------------------------------------------
20 22 # Module imports
21 23 #-----------------------------------------------------------------------------
@@ -34,6 +36,8 b' from nose.core import TestProgram'
34 36
35 37 from IPython.utils import genutils
36 38 from IPython.utils.platutils import find_cmd, FindCmdError
39 from . import globalipapp
40 from .plugin.ipdoctest import IPythonDoctest
37 41
38 42 pjoin = path.join
39 43
@@ -76,7 +80,11 b' def make_exclude():'
76 80 # cause testing problems. We should strive to minimize the number of
77 81 # skipped modules, since this means untested code. As the testing
78 82 # machinery solidifies, this list should eventually become empty.
79 EXCLUDE = [pjoin('IPython', 'external'),
83
84 # Note that these exclusions only mean that the docstrings are not analyzed
85 # for examples to be run as tests, if there are other test functions in
86 # those modules, they do get run.
87 exclusions = [pjoin('IPython', 'external'),
80 88 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
81 89 pjoin('IPython_doctest_plugin'),
82 90 pjoin('IPython', 'quarantine'),
@@ -88,58 +96,58 b' def make_exclude():'
88 96 ]
89 97
90 98 if not have_wx:
91 EXCLUDE.append(pjoin('IPython', 'gui'))
92 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
93 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookwx'))
99 exclusions.append(pjoin('IPython', 'gui'))
100 exclusions.append(pjoin('IPython', 'frontend', 'wx'))
101 exclusions.append(pjoin('IPython', 'lib', 'inputhookwx'))
94 102
95 103 if not have_gtk or not have_gobject:
96 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookgtk'))
104 exclusions.append(pjoin('IPython', 'lib', 'inputhookgtk'))
97 105
98 106 if not have_wx_aui:
99 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
107 exclusions.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
100 108
101 109 if not have_objc:
102 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
110 exclusions.append(pjoin('IPython', 'frontend', 'cocoa'))
103 111
104 112 if not sys.platform == 'win32':
105 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32'))
113 exclusions.append(pjoin('IPython', 'utils', 'platutils_win32'))
106 114
107 115 # These have to be skipped on win32 because the use echo, rm, cd, etc.
108 116 # See ticket https://bugs.launchpad.net/bugs/366982
109 117 if sys.platform == 'win32':
110 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
111 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
118 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
119 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
112 120
113 121 if not os.name == 'posix':
114 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix'))
122 exclusions.append(pjoin('IPython', 'utils', 'platutils_posix'))
115 123
116 124 if not have_pexpect:
117 EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner'))
125 exclusions.append(pjoin('IPython', 'scripts', 'irunner'))
118 126
119 127 # This is scary. We still have things in frontend and testing that
120 128 # are being tested by nose that use twisted. We need to rethink
121 129 # how we are isolating dependencies in testing.
122 130 if not (have_twisted and have_zi and have_foolscap):
123 EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
124 EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
125 EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase'))
126 EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
127 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
131 exclusions.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
132 exclusions.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
133 exclusions.append(pjoin('IPython', 'frontend', 'frontendbase'))
134 exclusions.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
135 exclusions.append(pjoin('IPython', 'frontend', 'tests',
128 136 'test_linefrontend'))
129 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
137 exclusions.append(pjoin('IPython', 'frontend', 'tests',
130 138 'test_frontendbase'))
131 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
139 exclusions.append(pjoin('IPython', 'frontend', 'tests',
132 140 'test_prefilterfrontend'))
133 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
141 exclusions.append(pjoin('IPython', 'frontend', 'tests',
134 142 'test_asyncfrontendbase')),
135 EXCLUDE.append(pjoin('IPython', 'testing', 'parametric'))
136 EXCLUDE.append(pjoin('IPython', 'testing', 'util'))
143 exclusions.append(pjoin('IPython', 'testing', 'parametric'))
144 exclusions.append(pjoin('IPython', 'testing', 'util'))
137 145
138 146 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
139 147 if sys.platform == 'win32':
140 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
148 exclusions = [s.replace('\\','\\\\') for s in exclusions]
141 149
142 return EXCLUDE
150 return exclusions
143 151
144 152
145 153 #-----------------------------------------------------------------------------
@@ -163,16 +171,16 b' class IPTester(object):'
163 171 if runner == 'iptest':
164 172 # Find our own 'iptest' script OS-level entry point
165 173 try:
166 iptest_path = find_cmd('iptest')
174 iptest_path = os.path.abspath(find_cmd('iptest'))
167 175 except FindCmdError:
168 176 # Script not installed (may be the case for testing situations
169 177 # that are running from a source tree only), pull from internal
170 178 # path:
171 179 iptest_path = pjoin(genutils.get_ipython_package_dir(),
172 180 'scripts','iptest')
173 self.runner = [iptest_path,'-v']
181 self.runner = ['python', iptest_path, '-v']
174 182 else:
175 self.runner = [find_cmd('trial')]
183 self.runner = ['python', os.path.abspath(find_cmd('trial'))]
176 184 if params is None:
177 185 params = []
178 186 if isinstance(params,str):
@@ -238,11 +246,13 b' def make_runners():'
238 246 nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib',
239 247 'scripts', 'testing', 'utils']
240 248 trial_packages = ['kernel']
241 #trial_packages = [] # dbg
242 249
243 250 if have_wx:
244 251 nose_packages.append('gui')
245 252
253 #nose_packages = ['core'] # dbg
254 #trial_packages = [] # dbg
255
246 256 nose_packages = ['IPython.%s' % m for m in nose_packages ]
247 257 trial_packages = ['IPython.%s' % m for m in trial_packages ]
248 258
@@ -268,16 +278,15 b' def run_iptest():'
268 278 warnings.filterwarnings('ignore',
269 279 'This will be removed soon. Use IPython.testing.util instead')
270 280
271 argv = sys.argv + [
272 # Loading ipdoctest causes problems with Twisted.
273 # I am removing this as a temporary fix to get the
274 # test suite back into working shape. Our nose
275 # plugin needs to be gone through with a fine
276 # toothed comb to find what is causing the problem.
277 # '--with-ipdoctest',
278 # '--ipdoctest-tests','--ipdoctest-extension=txt',
279 # '--detailed-errors',
280
281 argv = sys.argv + [ '--detailed-errors',
282 # Loading ipdoctest causes problems with Twisted, but
283 # our test suite runner now separates things and runs
284 # all Twisted tests with trial.
285 '--with-ipdoctest',
286 '--ipdoctest-tests','--ipdoctest-extension=txt',
287
288 #'-x','-s', # dbg
289
281 290 # We add --exe because of setuptools' imbecility (it
282 291 # blindly does chmod +x on ALL files). Nose does the
283 292 # right thing and it tries to avoid executables,
@@ -300,17 +309,18 b' def run_iptest():'
300 309 if not has_tests:
301 310 argv.append('IPython')
302 311
303 # Construct list of plugins, omitting the existing doctest plugin, which
304 # ours replaces (and extends).
305 EXCLUDE = make_exclude()
306 plugins = []
307 # plugins = [IPythonDoctest(EXCLUDE)]
312 ## # Construct list of plugins, omitting the existing doctest plugin, which
313 ## # ours replaces (and extends).
314 plugins = [IPythonDoctest(make_exclude())]
308 315 for p in nose.plugins.builtin.plugins:
309 316 plug = p()
310 317 if plug.name == 'doctest':
311 318 continue
312 319 plugins.append(plug)
313 320
321 # We need a global ipython running in this process
322 globalipapp.start_ipython()
323 # Now nose can run
314 324 TestProgram(argv=argv,plugins=plugins)
315 325
316 326
@@ -22,6 +22,8 b' Authors'
22 22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 23 """
24 24
25 from __future__ import absolute_import
26
25 27 #-----------------------------------------------------------------------------
26 28 # Copyright (C) 2009 The IPython Development Team
27 29 #
@@ -40,14 +42,16 b' import sys'
40 42 import unittest
41 43 from doctest import DocTestFinder, DocTestRunner, TestResults
42 44
43 # Our own
44 import nosepatch
45 # Our own, a nose monkeypatch
46 from . import nosepatch
45 47
46 48 # We already have python3-compliant code for parametric tests
47 49 if sys.version[0]=='2':
48 from _paramtestpy2 import ParametricTestCase
50 from ._paramtestpy2 import ParametricTestCase
49 51 else:
50 from _paramtestpy3 import ParametricTestCase
52 from ._paramtestpy3 import ParametricTestCase
53
54 from . import globalipapp
51 55
52 56 #-----------------------------------------------------------------------------
53 57 # Classes and functions
@@ -68,9 +72,13 b' class IPython2PythonConverter(object):'
68 72 implementation, but for now it only does prompt convertion."""
69 73
70 74 def __init__(self):
71 self.ps1 = re.compile(r'In\ \[\d+\]: ')
72 self.ps2 = re.compile(r'\ \ \ \.\.\.+: ')
73 self.out = re.compile(r'Out\[\d+\]: \s*?\n?')
75 self.rps1 = re.compile(r'In\ \[\d+\]: ')
76 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
77 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
78 self.pyps1 = '>>> '
79 self.pyps2 = '... '
80 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
81 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
74 82
75 83 def __call__(self, ds):
76 84 """Convert IPython prompts to python ones in a string."""
@@ -79,10 +87,34 b' class IPython2PythonConverter(object):'
79 87 pyout = ''
80 88
81 89 dnew = ds
82 dnew = self.ps1.sub(pyps1, dnew)
83 dnew = self.ps2.sub(pyps2, dnew)
84 dnew = self.out.sub(pyout, dnew)
85 return dnew
90 dnew = self.rps1.sub(pyps1, dnew)
91 dnew = self.rps2.sub(pyps2, dnew)
92 dnew = self.rout.sub(pyout, dnew)
93 ip = globalipapp.get_ipython()
94
95 # Convert input IPython source into valid Python.
96 out = []
97 newline = out.append
98 for line in dnew.splitlines():
99
100 mps1 = self.rpyps1.match(line)
101 if mps1 is not None:
102 prompt, text = mps1.groups()
103 newline(prompt+ip.prefilter(text, False))
104 continue
105
106 mps2 = self.rpyps2.match(line)
107 if mps2 is not None:
108 prompt, text = mps2.groups()
109 newline(prompt+ip.prefilter(text, True))
110 continue
111
112 newline(line)
113 newline('') # ensure a closing newline, needed by doctest
114 #print "PYSRC:", '\n'.join(out) # dbg
115 return '\n'.join(out)
116
117 #return dnew
86 118
87 119
88 120 class Doc2UnitTester(object):
@@ -49,183 +49,14 b' from nose.util import anyp, getpackage, test_address, resolve_name, tolist'
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Module globals and other constants
52 #-----------------------------------------------------------------------------
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 ###########################################################################
56 # *** HACK ***
57 # We must start our own ipython object and heavily muck with it so that all the
58 # modifications IPython makes to system behavior don't send the doctest
59 # machinery into a fit. This code should be considered a gross hack, but it
60 # gets the job done.
61
62 def default_argv():
63 """Return a valid default argv for creating testing instances of ipython"""
64
65 # Get the install directory for the user configuration and tell ipython to
66 # use the default profile from there.
67 from IPython.config import default
68 ipcdir = os.path.dirname(default.__file__)
69 ipconf = os.path.join(ipcdir,'ipython_config.py')
70 #print 'conf:',ipconf # dbg
71 return ['--colors=NoColor', '--no-term-title','--no-banner',
72 '--config-file=%s' % ipconf]
73
74
75 # Hack to modify the %run command so we can sync the user's namespace with the
76 # test globals. Once we move over to a clean magic system, this will be done
77 # with much less ugliness.
78
79 class py_file_finder(object):
80 def __init__(self,test_filename):
81 self.test_filename = test_filename
82
83 def __call__(self,name):
84 from IPython.utils.genutils import get_py_filename
85 try:
86 return get_py_filename(name)
87 except IOError:
88 test_dir = os.path.dirname(self.test_filename)
89 new_path = os.path.join(test_dir,name)
90 return get_py_filename(new_path)
91
92
93 def _run_ns_sync(self,arg_s,runner=None):
94 """Modified version of %run that syncs testing namespaces.
95
96 This is strictly needed for running doctests that call %run.
97 """
98
99 # When tests call %run directly (not via doctest) these function attributes
100 # are not set
101 try:
102 fname = _run_ns_sync.test_filename
103 except AttributeError:
104 fname = arg_s
105
106 finder = py_file_finder(fname)
107 out = _ip.magic_run_ori(arg_s,runner,finder)
108
109 # Simliarly, there is no test_globs when a test is NOT a doctest
110 if hasattr(_run_ns_sync,'test_globs'):
111 _run_ns_sync.test_globs.update(_ip.user_ns)
112 return out
113
114
115 class ipnsdict(dict):
116 """A special subclass of dict for use as an IPython namespace in doctests.
117
118 This subclass adds a simple checkpointing capability so that when testing
119 machinery clears it (we use it as the test execution context), it doesn't
120 get completely destroyed.
121 """
122
123 def __init__(self,*a):
124 dict.__init__(self,*a)
125 self._savedict = {}
126
127 def clear(self):
128 dict.clear(self)
129 self.update(self._savedict)
130
131 def _checkpoint(self):
132 self._savedict.clear()
133 self._savedict.update(self)
134
135 def update(self,other):
136 self._checkpoint()
137 dict.update(self,other)
138
139 # If '_' is in the namespace, python won't set it when executing code,
140 # and we have examples that test it. So we ensure that the namespace
141 # is always 'clean' of it before it's used for test code execution.
142 self.pop('_',None)
143
144 # The builtins namespace must *always* be the real __builtin__ module,
145 # else weird stuff happens. The main ipython code does have provisions
146 # to ensure this after %run, but since in this class we do some
147 # aggressive low-level cleaning of the execution namespace, we need to
148 # correct for that ourselves, to ensure consitency with the 'real'
149 # ipython.
150 self['__builtins__'] = __builtin__
151
152
153 def start_ipython():
154 """Start a global IPython shell, which we need for IPython-specific syntax.
155 """
156
157 # This function should only ever run once!
158 if hasattr(start_ipython,'already_called'):
159 return
160 start_ipython.already_called = True
161
162 # Ok, first time we're called, go ahead
163 import new
164
165 import IPython
166 from IPython.core import ipapp, iplib
167
168 def xsys(cmd):
169 """Execute a command and print its output.
170
171 This is just a convenience function to replace the IPython system call
172 with one that is more doctest-friendly.
173 """
174 cmd = _ip.var_expand(cmd,depth=1)
175 sys.stdout.write(commands.getoutput(cmd))
176 sys.stdout.flush()
177
178 # Store certain global objects that IPython modifies
179 _displayhook = sys.displayhook
180 _excepthook = sys.excepthook
181 _main = sys.modules.get('__main__')
182
183 argv = default_argv()
184
185 # Start IPython instance. We customize it to start with minimal frills.
186 user_ns,global_ns = iplib.make_user_namespaces(ipnsdict(),{})
187 ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns)
188 ip.initialize()
189 ip.shell.builtin_trap.set()
190
191 # Deactivate the various python system hooks added by ipython for
192 # interactive convenience so we don't confuse the doctest system
193 sys.modules['__main__'] = _main
194 sys.displayhook = _displayhook
195 sys.excepthook = _excepthook
196
197 # So that ipython magics and aliases can be doctested (they work by making
198 # a call into a global _ip object)
199 __builtin__._ip = ip.shell
200
201 # Modify the IPython system call with one that uses getoutput, so that we
202 # can capture subcommands and print them to Python's stdout, otherwise the
203 # doctest machinery would miss them.
204 ip.shell.system = xsys
205
206 # Also patch our %run function in.
207 im = new.instancemethod(_run_ns_sync,_ip, _ip.__class__)
208 ip.shell.magic_run_ori = _ip.magic_run
209 ip.shell.magic_run = im
210
211 # XXX - For some very bizarre reason, the loading of %history by default is
212 # failing. This needs to be fixed later, but for now at least this ensures
213 # that tests that use %hist run to completion.
214 from IPython.core import history
215 history.init_ipython(ip.shell)
216 if not hasattr(ip.shell,'magic_history'):
217 raise RuntimeError("Can't load magics, aborting")
218
219
220 # The start call MUST be made here. I'm not sure yet why it doesn't work if
221 # it is made later, at plugin initialization time, but in all my tests, that's
222 # the case.
223 start_ipython()
224
225 # *** END HACK ***
226 ###########################################################################
227 56
57 #-----------------------------------------------------------------------------
228 58 # Classes and functions
59 #-----------------------------------------------------------------------------
229 60
230 61 def is_extension_module(filename):
231 62 """Return whether the given filename is an extension module.
@@ -288,7 +119,7 b' class DocTestFinder(doctest.DocTestFinder):'
288 119 Find tests for the given object and any contained objects, and
289 120 add them to `tests`.
290 121 """
291
122 #print '_find for:', obj, name, module # dbg
292 123 if hasattr(obj,"skip_doctest"):
293 124 #print 'SKIPPING DOCTEST FOR:',obj # dbg
294 125 obj = DocTestSkip(obj)
@@ -396,8 +227,9 b' class DocTestCase(doctests.DocTestCase):'
396 227 self._dt_runner = runner
397 228
398 229
399 # Each doctest should remember what directory it was loaded from...
400 self._ori_dir = os.getcwd()
230 # Each doctest should remember the directory it was loaded from, so
231 # things like %run work without too many contortions
232 self._ori_dir = os.path.dirname(test.filename)
401 233
402 234 # Modified runTest from the default stdlib
403 235 def runTest(self):
@@ -418,6 +250,7 b' class DocTestCase(doctests.DocTestCase):'
418 250 # test was originally created, in case another doctest did a
419 251 # directory change. We'll restore this in the finally clause.
420 252 curdir = os.getcwd()
253 #print 'runTest in dir:', self._ori_dir # dbg
421 254 os.chdir(self._ori_dir)
422 255
423 256 runner.DIVIDER = "-"*70
@@ -432,7 +265,7 b' class DocTestCase(doctests.DocTestCase):'
432 265
433 266 def setUp(self):
434 267 """Modified test setup that syncs with ipython namespace"""
435
268 #print "setUp test", self._dt_test.examples # dbg
436 269 if isinstance(self._dt_test.examples[0],IPExample):
437 270 # for IPython examples *only*, we swap the globals with the ipython
438 271 # namespace, after updating it with the globals (which doctest
@@ -731,8 +564,10 b' class IPDocTestRunner(doctest.DocTestRunner,object):'
731 564 # attribute. Our new %run will then only make the namespace update
732 565 # when called (rather than unconconditionally updating test.globs here
733 566 # for all examples, most of which won't be calling %run anyway).
734 _run_ns_sync.test_globs = test.globs
735 _run_ns_sync.test_filename = test.filename
567 #_ip._ipdoctest_test_globs = test.globs
568 #_ip._ipdoctest_test_filename = test.filename
569
570 test.globs.update(_ip.user_ns)
736 571
737 572 return super(IPDocTestRunner,self).run(test,
738 573 compileflags,out,clear_globs)
@@ -846,6 +681,7 b' class ExtensionDoctest(doctests.Doctest):'
846 681
847 682
848 683 def loadTestsFromFile(self, filename):
684 #print "ipdoctest - from file", filename # dbg
849 685 if is_extension_module(filename):
850 686 for t in self.loadTestsFromExtensionModule(filename):
851 687 yield t
@@ -872,7 +708,7 b' class ExtensionDoctest(doctests.Doctest):'
872 708 Modified version that accepts extension modules as valid containers for
873 709 doctests.
874 710 """
875 # print '*** ipdoctest- wantFile:',filename # dbg
711 #print '*** ipdoctest- wantFile:',filename # dbg
876 712
877 713 for pat in self.exclude_patterns:
878 714 if pat.search(filename):
@@ -890,11 +726,12 b' class IPythonDoctest(ExtensionDoctest):'
890 726 """
891 727 name = 'ipdoctest' # call nosetests with --with-ipdoctest
892 728 enabled = True
893
729
894 730 def makeTest(self, obj, parent):
895 731 """Look for doctests in the given object, which will be a
896 732 function, method or class.
897 733 """
734 #print 'Plugin analyzing:', obj, parent # dbg
898 735 # always use whitespace and ellipsis options
899 736 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
900 737
@@ -909,6 +746,7 b' class IPythonDoctest(ExtensionDoctest):'
909 746 checker=self.checker)
910 747
911 748 def options(self, parser, env=os.environ):
749 #print "Options for nose plugin:", self.name # dbg
912 750 Plugin.options(self, parser, env)
913 751 parser.add_option('--ipdoctest-tests', action='store_true',
914 752 dest='ipdoctest_tests',
@@ -929,6 +767,7 b' class IPythonDoctest(ExtensionDoctest):'
929 767 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
930 768
931 769 def configure(self, options, config):
770 #print "Configuring nose plugin:", self.name # dbg
932 771 Plugin.configure(self, options, config)
933 772 self.doctest_tests = options.ipdoctest_tests
934 773 self.extension = tolist(options.ipdoctest_extension)
@@ -29,10 +29,11 b' Authors'
29 29 import os
30 30 import re
31 31 import sys
32 import tempfile
32 33
33 34 import nose.tools as nt
34 35
35 from IPython.utils import genutils
36 from IPython.utils import genutils, platutils
36 37
37 38 #-----------------------------------------------------------------------------
38 39 # Globals
@@ -128,5 +129,93 b' def parse_test_output(txt):'
128 129 # If the input didn't match any of these forms, assume no error/failures
129 130 return 0, 0
130 131
132
131 133 # So nose doesn't think this is a test
132 134 parse_test_output.__test__ = False
135
136
137 def temp_pyfile(src, ext='.py'):
138 """Make a temporary python file, return filename and filehandle.
139
140 Parameters
141 ----------
142 src : string or list of strings (no need for ending newlines if list)
143 Source code to be written to the file.
144
145 ext : optional, string
146 Extension for the generated file.
147
148 Returns
149 -------
150 (filename, open filehandle)
151 It is the caller's responsibility to close the open file and unlink it.
152 """
153 fname = tempfile.mkstemp(ext)[1]
154 f = open(fname,'w')
155 f.write(src)
156 f.flush()
157 return fname, f
158
159
160 def default_argv():
161 """Return a valid default argv for creating testing instances of ipython"""
162
163 # Get the install directory for the user configuration and tell ipython to
164 # use the default profile from there.
165 from IPython.config import default
166 ipcdir = os.path.dirname(default.__file__)
167 ipconf = os.path.join(ipcdir,'ipython_config.py')
168 #print 'conf:',ipconf # dbg
169 return ['--colors=NoColor', '--no-term-title','--no-banner',
170 '--config-file=%s' % ipconf, '--autocall=0', '--quick']
171
172
173 def ipexec(fname):
174 """Utility to call 'ipython filename'.
175
176 Starts IPython witha minimal and safe configuration to make startup as fast
177 as possible.
178
179 Note that this starts IPython in a subprocess!
180
181 Parameters
182 ----------
183 fname : str
184 Name of file to be executed (should have .py or .ipy extension).
185
186 Returns
187 -------
188 (stdout, stderr) of ipython subprocess.
189 """
190 _ip = get_ipython()
191 test_dir = os.path.dirname(__file__)
192 full_fname = os.path.join(test_dir, fname)
193 ipython_cmd = platutils.find_cmd('ipython')
194 cmdargs = ' '.join(default_argv())
195 return genutils.getoutputerror('%s %s' % (ipython_cmd, full_fname))
196
197
198 def ipexec_validate(fname, expected_out, expected_err=None):
199 """Utility to call 'ipython filename' and validate output/error.
200
201 This function raises an AssertionError if the validation fails.
202
203 Note that this starts IPython in a subprocess!
204
205 Parameters
206 ----------
207 fname : str
208 Name of the file to be executed (should have .py or .ipy extension).
209
210 expected_out : str
211 Expected stdout of the process.
212
213 Returns
214 -------
215 None
216 """
217
218 out, err = ipexec(fname)
219 nt.assert_equals(out.strip(), expected_out.strip())
220 if expected_err:
221 nt.assert_equals(err.strip(), expected_err.strip())
General Comments 0
You need to be logged in to leave comments. Login now