##// END OF EJS Templates
Robustness fixes in test suite machinery....
Fernando Perez -
Show More
@@ -1,30 +1,31 b''
1 """Simple script to be run *twice*, to check reference counting bugs.
1 """Simple script to be run *twice*, to check reference counting bugs.
2
2
3 See test_run for details."""
3 See test_run for details."""
4
4
5 import sys
5 import sys
6
6
7 # We want to ensure that while objects remain available for immediate access,
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
8 # objects from *previous* runs of the same script get collected, to avoid
9 # accumulating massive amounts of old references.
9 # accumulating massive amounts of old references.
10 class C(object):
10 class C(object):
11 def __init__(self,name):
11 def __init__(self,name):
12 self.name = name
12 self.name = name
13
13
14 def __del__(self):
14 def __del__(self):
15 print 'tclass.py: deleting object:',self.name
15 print 'tclass.py: deleting object:',self.name
16
16 sys.stdout.flush()
17
17
18 try:
18 try:
19 name = sys.argv[1]
19 name = sys.argv[1]
20 except IndexError:
20 except IndexError:
21 pass
21 pass
22 else:
22 else:
23 if name.startswith('C'):
23 if name.startswith('C'):
24 c = C(name)
24 c = C(name)
25
25
26 #print >> sys.stderr, "ARGV:", sys.argv # dbg
26 #print >> sys.stderr, "ARGV:", sys.argv # dbg
27
27
28 # This next print statement is NOT debugging, we're making the check on a
28 # This next print statement is NOT debugging, we're making the check on a
29 # completely separate process so we verify by capturing stdout:
29 # completely separate process so we verify by capturing stdout:
30 print 'ARGV 1-:', sys.argv[1:]
30 print 'ARGV 1-:', sys.argv[1:]
31 sys.stdout.flush()
@@ -1,434 +1,450 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) or trial recursively. This
8 calling this script (with different arguments) or trial recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Module imports
20 # Module imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # Stdlib
23 # Stdlib
24 import os
24 import os
25 import os.path as path
25 import os.path as path
26 import signal
26 import signal
27 import sys
27 import sys
28 import subprocess
28 import subprocess
29 import tempfile
29 import tempfile
30 import time
30 import time
31 import warnings
31 import warnings
32
32
33
33
34 # Ugly, but necessary hack to ensure the test suite finds our version of
34 # Ugly, but necessary hack to ensure the test suite finds our version of
35 # IPython and not a possibly different one that may exist system-wide.
35 # IPython and not a possibly different one that may exist system-wide.
36 # Note that this must be done here, so the imports that come next work
36 # Note that this must be done here, so the imports that come next work
37 # correctly even if IPython isn't installed yet.
37 # correctly even if IPython isn't installed yet.
38 p = os.path
38 p = os.path
39 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
39 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
40 sys.path.insert(0, ippath)
40 sys.path.insert(0, ippath)
41
41
42 # Note: monkeypatch!
42 # Note: monkeypatch!
43 # We need to monkeypatch a small problem in nose itself first, before importing
43 # We need to monkeypatch a small problem in nose itself first, before importing
44 # it for actual use. This should get into nose upstream, but its release cycle
44 # it for actual use. This should get into nose upstream, but its release cycle
45 # is slow and we need it for our parametric tests to work correctly.
45 # is slow and we need it for our parametric tests to work correctly.
46 from IPython.testing import nosepatch
46 from IPython.testing import nosepatch
47 # Now, proceed to import nose itself
47 # Now, proceed to import nose itself
48 import nose.plugins.builtin
48 import nose.plugins.builtin
49 from nose.core import TestProgram
49 from nose.core import TestProgram
50
50
51 # Our own imports
51 # Our own imports
52 from IPython.utils import genutils
52 from IPython.utils import genutils
53 from IPython.utils.platutils import find_cmd, FindCmdError
53 from IPython.utils.platutils import find_cmd, FindCmdError
54 from IPython.testing import globalipapp
54 from IPython.testing import globalipapp
55 from IPython.testing import tools
55 from IPython.testing import tools
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57
57
58 pjoin = path.join
58 pjoin = path.join
59
59
60
61 #-----------------------------------------------------------------------------
62 # Globals
63 #-----------------------------------------------------------------------------
64
65 # By default, we assume IPython has been installed. But if the test suite is
66 # being run from a source tree that has NOT been installed yet, this flag can
67 # be set to False by the entry point scripts, to let us know that we must call
68 # the source tree versions of the scripts which manipulate sys.path instead of
69 # assuming that things exist system-wide.
70 INSTALLED = True
71
60 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
61 # Warnings control
73 # Warnings control
62 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
63 # Twisted generates annoying warnings with Python 2.6, as will do other code
75 # Twisted generates annoying warnings with Python 2.6, as will do other code
64 # that imports 'sets' as of today
76 # that imports 'sets' as of today
65 warnings.filterwarnings('ignore', 'the sets module is deprecated',
77 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 DeprecationWarning )
78 DeprecationWarning )
67
79
68 # This one also comes from Twisted
80 # This one also comes from Twisted
69 warnings.filterwarnings('ignore', 'the sha module is deprecated',
81 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 DeprecationWarning)
82 DeprecationWarning)
71
83
72 # Wx on Fedora11 spits these out
84 # Wx on Fedora11 spits these out
73 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
85 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 UserWarning)
86 UserWarning)
75
87
76 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
77 # Logic for skipping doctests
89 # Logic for skipping doctests
78 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
79
91
80 def test_for(mod):
92 def test_for(mod):
81 """Test to see if mod is importable."""
93 """Test to see if mod is importable."""
82 try:
94 try:
83 __import__(mod)
95 __import__(mod)
84 except (ImportError, RuntimeError):
96 except (ImportError, RuntimeError):
85 # GTK reports Runtime error if it can't be initialized even if it's
97 # GTK reports Runtime error if it can't be initialized even if it's
86 # importable.
98 # importable.
87 return False
99 return False
88 else:
100 else:
89 return True
101 return True
90
102
91
92 have_curses = test_for('_curses')
103 have_curses = test_for('_curses')
93 have_wx = test_for('wx')
104 have_wx = test_for('wx')
94 have_wx_aui = test_for('wx.aui')
105 have_wx_aui = test_for('wx.aui')
95 have_zi = test_for('zope.interface')
106 have_zi = test_for('zope.interface')
96 have_twisted = test_for('twisted')
107 have_twisted = test_for('twisted')
97 have_foolscap = test_for('foolscap')
108 have_foolscap = test_for('foolscap')
98 have_objc = test_for('objc')
109 have_objc = test_for('objc')
99 have_pexpect = test_for('pexpect')
110 have_pexpect = test_for('pexpect')
100 have_gtk = test_for('gtk')
111 have_gtk = test_for('gtk')
101 have_gobject = test_for('gobject')
112 have_gobject = test_for('gobject')
102
113
114 #-----------------------------------------------------------------------------
115 # Functions and classes
116 #-----------------------------------------------------------------------------
103
117
104 def make_exclude():
118 def make_exclude():
105 """Make patterns of modules and packages to exclude from testing.
119 """Make patterns of modules and packages to exclude from testing.
106
120
107 For the IPythonDoctest plugin, we need to exclude certain patterns that
121 For the IPythonDoctest plugin, we need to exclude certain patterns that
108 cause testing problems. We should strive to minimize the number of
122 cause testing problems. We should strive to minimize the number of
109 skipped modules, since this means untested code. As the testing
123 skipped modules, since this means untested code. As the testing
110 machinery solidifies, this list should eventually become empty.
124 machinery solidifies, this list should eventually become empty.
111 These modules and packages will NOT get scanned by nose at all for tests.
125 These modules and packages will NOT get scanned by nose at all for tests.
112 """
126 """
113 # Simple utility to make IPython paths more readably, we need a lot of
127 # Simple utility to make IPython paths more readably, we need a lot of
114 # these below
128 # these below
115 ipjoin = lambda *paths: pjoin('IPython', *paths)
129 ipjoin = lambda *paths: pjoin('IPython', *paths)
116
130
117 exclusions = [ipjoin('external'),
131 exclusions = [ipjoin('external'),
118 ipjoin('frontend', 'process', 'winprocess.py'),
132 ipjoin('frontend', 'process', 'winprocess.py'),
119 # Deprecated old Shell and iplib modules, skip to avoid
133 # Deprecated old Shell and iplib modules, skip to avoid
120 # warnings
134 # warnings
121 ipjoin('Shell'),
135 ipjoin('Shell'),
122 ipjoin('iplib'),
136 ipjoin('iplib'),
123 pjoin('IPython_doctest_plugin'),
137 pjoin('IPython_doctest_plugin'),
124 ipjoin('quarantine'),
138 ipjoin('quarantine'),
125 ipjoin('deathrow'),
139 ipjoin('deathrow'),
126 ipjoin('testing', 'attic'),
140 ipjoin('testing', 'attic'),
127 # This guy is probably attic material
141 # This guy is probably attic material
128 ipjoin('testing', 'mkdoctests'),
142 ipjoin('testing', 'mkdoctests'),
129 # Testing inputhook will need a lot of thought, to figure out
143 # Testing inputhook will need a lot of thought, to figure out
130 # how to have tests that don't lock up with the gui event
144 # how to have tests that don't lock up with the gui event
131 # loops in the picture
145 # loops in the picture
132 ipjoin('lib', 'inputhook'),
146 ipjoin('lib', 'inputhook'),
133 # Config files aren't really importable stand-alone
147 # Config files aren't really importable stand-alone
134 ipjoin('config', 'default'),
148 ipjoin('config', 'default'),
135 ipjoin('config', 'profile'),
149 ipjoin('config', 'profile'),
136 ]
150 ]
137
151
138 if not have_wx:
152 if not have_wx:
139 exclusions.append(ipjoin('gui'))
153 exclusions.append(ipjoin('gui'))
140 exclusions.append(ipjoin('frontend', 'wx'))
154 exclusions.append(ipjoin('frontend', 'wx'))
141 exclusions.append(ipjoin('lib', 'inputhookwx'))
155 exclusions.append(ipjoin('lib', 'inputhookwx'))
142
156
143 if not have_gtk or not have_gobject:
157 if not have_gtk or not have_gobject:
144 exclusions.append(ipjoin('lib', 'inputhookgtk'))
158 exclusions.append(ipjoin('lib', 'inputhookgtk'))
145
159
146 if not have_wx_aui:
160 if not have_wx_aui:
147 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
161 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
148
162
149 if not have_objc:
163 if not have_objc:
150 exclusions.append(ipjoin('frontend', 'cocoa'))
164 exclusions.append(ipjoin('frontend', 'cocoa'))
151
165
152 if not sys.platform == 'win32':
166 if not sys.platform == 'win32':
153 exclusions.append(ipjoin('utils', 'platutils_win32'))
167 exclusions.append(ipjoin('utils', 'platutils_win32'))
154
168
155 # These have to be skipped on win32 because the use echo, rm, cd, etc.
169 # These have to be skipped on win32 because the use echo, rm, cd, etc.
156 # See ticket https://bugs.launchpad.net/bugs/366982
170 # See ticket https://bugs.launchpad.net/bugs/366982
157 if sys.platform == 'win32':
171 if sys.platform == 'win32':
158 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
172 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
159 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
173 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
160
174
161 if not os.name == 'posix':
175 if not os.name == 'posix':
162 exclusions.append(ipjoin('utils', 'platutils_posix'))
176 exclusions.append(ipjoin('utils', 'platutils_posix'))
163
177
164 if not have_pexpect:
178 if not have_pexpect:
165 exclusions.extend([ipjoin('scripts', 'irunner'),
179 exclusions.extend([ipjoin('scripts', 'irunner'),
166 ipjoin('lib', 'irunner')])
180 ipjoin('lib', 'irunner')])
167
181
168 # This is scary. We still have things in frontend and testing that
182 # This is scary. We still have things in frontend and testing that
169 # are being tested by nose that use twisted. We need to rethink
183 # are being tested by nose that use twisted. We need to rethink
170 # how we are isolating dependencies in testing.
184 # how we are isolating dependencies in testing.
171 if not (have_twisted and have_zi and have_foolscap):
185 if not (have_twisted and have_zi and have_foolscap):
172 exclusions.extend(
186 exclusions.extend(
173 [ipjoin('frontend', 'asyncfrontendbase'),
187 [ipjoin('frontend', 'asyncfrontendbase'),
174 ipjoin('frontend', 'prefilterfrontend'),
188 ipjoin('frontend', 'prefilterfrontend'),
175 ipjoin('frontend', 'frontendbase'),
189 ipjoin('frontend', 'frontendbase'),
176 ipjoin('frontend', 'linefrontendbase'),
190 ipjoin('frontend', 'linefrontendbase'),
177 ipjoin('frontend', 'tests', 'test_linefrontend'),
191 ipjoin('frontend', 'tests', 'test_linefrontend'),
178 ipjoin('frontend', 'tests', 'test_frontendbase'),
192 ipjoin('frontend', 'tests', 'test_frontendbase'),
179 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
193 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
180 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
194 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
181 ipjoin('testing', 'parametric'),
195 ipjoin('testing', 'parametric'),
182 ipjoin('testing', 'util'),
196 ipjoin('testing', 'util'),
183 ipjoin('testing', 'tests', 'test_decorators_trial'),
197 ipjoin('testing', 'tests', 'test_decorators_trial'),
184 ] )
198 ] )
185
199
186 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
200 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
187 if sys.platform == 'win32':
201 if sys.platform == 'win32':
188 exclusions = [s.replace('\\','\\\\') for s in exclusions]
202 exclusions = [s.replace('\\','\\\\') for s in exclusions]
189
203
190 return exclusions
204 return exclusions
191
205
192
206
193 #-----------------------------------------------------------------------------
194 # Functions and classes
195 #-----------------------------------------------------------------------------
196
197 class IPTester(object):
207 class IPTester(object):
198 """Call that calls iptest or trial in a subprocess.
208 """Call that calls iptest or trial in a subprocess.
199 """
209 """
200 #: string, name of test runner that will be called
210 #: string, name of test runner that will be called
201 runner = None
211 runner = None
202 #: list, parameters for test runner
212 #: list, parameters for test runner
203 params = None
213 params = None
204 #: list, arguments of system call to be made to call test runner
214 #: list, arguments of system call to be made to call test runner
205 call_args = None
215 call_args = None
206 #: list, process ids of subprocesses we start (for cleanup)
216 #: list, process ids of subprocesses we start (for cleanup)
207 pids = None
217 pids = None
208
218
209 def __init__(self, runner='iptest', params=None):
219 def __init__(self, runner='iptest', params=None):
210 """Create new test runner."""
220 """Create new test runner."""
221 p = os.path
211 if runner == 'iptest':
222 if runner == 'iptest':
212 # Find our own 'iptest' script OS-level entry point. Don't look
223 if INSTALLED:
213 # system-wide, so we are sure we pick up *this one*. And pass
224 self.runner = tools.cmd2argv(
214 # through to subprocess call our own sys.argv
225 p.abspath(find_cmd('iptest'))) + sys.argv[1:]
215 self.runner = tools.cmd2argv(os.path.abspath(__file__)) + \
226 else:
216 sys.argv[1:]
227 # Find our own 'iptest' script OS-level entry point. Don't
228 # look system-wide, so we are sure we pick up *this one*. And
229 # pass through to subprocess call our own sys.argv
230 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
231 script = p.join(ippath, 'iptest.py')
232 self.runner = tools.cmd2argv(script) + sys.argv[1:]
233
217 else:
234 else:
218 self.runner = tools.cmd2argv(os.path.abspath(find_cmd('trial')))
235 # For trial, it needs to be installed system-wide
236 self.runner = tools.cmd2argv(p.abspath(find_cmd('trial')))
219 if params is None:
237 if params is None:
220 params = []
238 params = []
221 if isinstance(params, str):
239 if isinstance(params, str):
222 params = [params]
240 params = [params]
223 self.params = params
241 self.params = params
224
242
225 # Assemble call
243 # Assemble call
226 self.call_args = self.runner+self.params
244 self.call_args = self.runner+self.params
227
245
228 # Store pids of anything we start to clean up on deletion, if possible
246 # Store pids of anything we start to clean up on deletion, if possible
229 # (on posix only, since win32 has no os.kill)
247 # (on posix only, since win32 has no os.kill)
230 self.pids = []
248 self.pids = []
231
249
232 if sys.platform == 'win32':
250 if sys.platform == 'win32':
233 def _run_cmd(self):
251 def _run_cmd(self):
234 # On Windows, use os.system instead of subprocess.call, because I
252 # On Windows, use os.system instead of subprocess.call, because I
235 # was having problems with subprocess and I just don't know enough
253 # was having problems with subprocess and I just don't know enough
236 # about win32 to debug this reliably. Os.system may be the 'old
254 # about win32 to debug this reliably. Os.system may be the 'old
237 # fashioned' way to do it, but it works just fine. If someone
255 # fashioned' way to do it, but it works just fine. If someone
238 # later can clean this up that's fine, as long as the tests run
256 # later can clean this up that's fine, as long as the tests run
239 # reliably in win32.
257 # reliably in win32.
240 return os.system(' '.join(self.call_args))
258 return os.system(' '.join(self.call_args))
241 else:
259 else:
242 def _run_cmd(self):
260 def _run_cmd(self):
243 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
261 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
244 subp = subprocess.Popen(self.call_args)
262 subp = subprocess.Popen(self.call_args)
245 self.pids.append(subp.pid)
263 self.pids.append(subp.pid)
246 # If this fails, the pid will be left in self.pids and cleaned up
264 # If this fails, the pid will be left in self.pids and cleaned up
247 # later, but if the wait call succeeds, then we can clear the
265 # later, but if the wait call succeeds, then we can clear the
248 # stored pid.
266 # stored pid.
249 retcode = subp.wait()
267 retcode = subp.wait()
250 self.pids.pop()
268 self.pids.pop()
251 return retcode
269 return retcode
252
270
253 def run(self):
271 def run(self):
254 """Run the stored commands"""
272 """Run the stored commands"""
255 try:
273 try:
256 return self._run_cmd()
274 return self._run_cmd()
257 except:
275 except:
258 import traceback
276 import traceback
259 traceback.print_exc()
277 traceback.print_exc()
260 return 1 # signal failure
278 return 1 # signal failure
261
279
262 def __del__(self):
280 def __del__(self):
263 """Cleanup on exit by killing any leftover processes."""
281 """Cleanup on exit by killing any leftover processes."""
264
282
265 if not hasattr(os, 'kill'):
283 if not hasattr(os, 'kill'):
266 return
284 return
267
285
268 for pid in self.pids:
286 for pid in self.pids:
269 try:
287 try:
270 print 'Cleaning stale PID:', pid
288 print 'Cleaning stale PID:', pid
271 os.kill(pid, signal.SIGKILL)
289 os.kill(pid, signal.SIGKILL)
272 except OSError:
290 except OSError:
273 # This is just a best effort, if we fail or the process was
291 # This is just a best effort, if we fail or the process was
274 # really gone, ignore it.
292 # really gone, ignore it.
275 pass
293 pass
276
294
277
295
278 def make_runners():
296 def make_runners():
279 """Define the top-level packages that need to be tested.
297 """Define the top-level packages that need to be tested.
280 """
298 """
281
299
282 # Packages to be tested via nose, that only depend on the stdlib
300 # Packages to be tested via nose, that only depend on the stdlib
283 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
284 'scripts', 'testing', 'utils' ]
302 'scripts', 'testing', 'utils' ]
285 # The machinery in kernel needs twisted for real testing
303 # The machinery in kernel needs twisted for real testing
286 trial_pkg_names = []
304 trial_pkg_names = []
287
305
288 if have_wx:
306 if have_wx:
289 nose_pkg_names.append('gui')
307 nose_pkg_names.append('gui')
290
308
291 # And add twisted ones if conditions are met
309 # And add twisted ones if conditions are met
292 if have_zi and have_twisted and have_foolscap:
310 if have_zi and have_twisted and have_foolscap:
293 # Note that we list the kernel here, though the bulk of it is
311 # Note that we list the kernel here, though the bulk of it is
294 # twisted-based, because nose picks up doctests that twisted doesn't.
312 # twisted-based, because nose picks up doctests that twisted doesn't.
295 nose_pkg_names.append('kernel')
313 nose_pkg_names.append('kernel')
296 trial_pkg_names.append('kernel')
314 trial_pkg_names.append('kernel')
297
315
298 # For debugging this code, only load quick stuff
316 # For debugging this code, only load quick stuff
299 #nose_pkg_names = ['core'] # dbg
317 #nose_pkg_names = ['core', 'extensions'] # dbg
300 #trial_pkg_names = [] # dbg
318 #trial_pkg_names = [] # dbg
301
319
302 # Make fully qualified package names prepending 'IPython.' to our name lists
320 # Make fully qualified package names prepending 'IPython.' to our name lists
303 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
321 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
304 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
322 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
305
323
306 # Make runners
324 # Make runners
307 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
325 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
308 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
326 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
309
327
310 return runners
328 return runners
311
329
312
330
313 def run_iptest():
331 def run_iptest():
314 """Run the IPython test suite using nose.
332 """Run the IPython test suite using nose.
315
333
316 This function is called when this script is **not** called with the form
334 This function is called when this script is **not** called with the form
317 `iptest all`. It simply calls nose with appropriate command line flags
335 `iptest all`. It simply calls nose with appropriate command line flags
318 and accepts all of the standard nose arguments.
336 and accepts all of the standard nose arguments.
319 """
337 """
320
338
321 warnings.filterwarnings('ignore',
339 warnings.filterwarnings('ignore',
322 'This will be removed soon. Use IPython.testing.util instead')
340 'This will be removed soon. Use IPython.testing.util instead')
323
341
324 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
342 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
325
343
326 # I don't fully understand why we need this one, but
327 # depending on what directory the test suite is run
328 # from, if we don't give it, 0 tests get run.
329 # Specifically, if the test suite is run from the
330 # source dir with an argument (like 'iptest.py
331 # IPython.core', 0 tests are run, even if the same call
332 # done in this directory works fine). It appears that
333 # if the requested package is in the current dir,
334 # nose bails early by default. Since it's otherwise
335 # harmless, leave it in by default.
336 '--traverse-namespace',
337
338 # Loading ipdoctest causes problems with Twisted, but
344 # Loading ipdoctest causes problems with Twisted, but
339 # our test suite runner now separates things and runs
345 # our test suite runner now separates things and runs
340 # all Twisted tests with trial.
346 # all Twisted tests with trial.
341 '--with-ipdoctest',
347 '--with-ipdoctest',
342 '--ipdoctest-tests','--ipdoctest-extension=txt',
348 '--ipdoctest-tests','--ipdoctest-extension=txt',
343
349
344 # We add --exe because of setuptools' imbecility (it
350 # We add --exe because of setuptools' imbecility (it
345 # blindly does chmod +x on ALL files). Nose does the
351 # blindly does chmod +x on ALL files). Nose does the
346 # right thing and it tries to avoid executables,
352 # right thing and it tries to avoid executables,
347 # setuptools unfortunately forces our hand here. This
353 # setuptools unfortunately forces our hand here. This
348 # has been discussed on the distutils list and the
354 # has been discussed on the distutils list and the
349 # setuptools devs refuse to fix this problem!
355 # setuptools devs refuse to fix this problem!
350 '--exe',
356 '--exe',
351 ]
357 ]
352
358
359 if nose.__version__ >= '0.11':
360 # I don't fully understand why we need this one, but depending on what
361 # directory the test suite is run from, if we don't give it, 0 tests
362 # get run. Specifically, if the test suite is run from the source dir
363 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
364 # even if the same call done in this directory works fine). It appears
365 # that if the requested package is in the current dir, nose bails early
366 # by default. Since it's otherwise harmless, leave it in by default
367 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
368 argv.append('--traverse-namespace')
353
369
354 # Construct list of plugins, omitting the existing doctest plugin, which
370 # Construct list of plugins, omitting the existing doctest plugin, which
355 # ours replaces (and extends).
371 # ours replaces (and extends).
356 plugins = [IPythonDoctest(make_exclude())]
372 plugins = [IPythonDoctest(make_exclude())]
357 for p in nose.plugins.builtin.plugins:
373 for p in nose.plugins.builtin.plugins:
358 plug = p()
374 plug = p()
359 if plug.name == 'doctest':
375 if plug.name == 'doctest':
360 continue
376 continue
361 plugins.append(plug)
377 plugins.append(plug)
362
378
363 # We need a global ipython running in this process
379 # We need a global ipython running in this process
364 globalipapp.start_ipython()
380 globalipapp.start_ipython()
365 # Now nose can run
381 # Now nose can run
366 TestProgram(argv=argv, plugins=plugins)
382 TestProgram(argv=argv, plugins=plugins)
367
383
368
384
369 def run_iptestall():
385 def run_iptestall():
370 """Run the entire IPython test suite by calling nose and trial.
386 """Run the entire IPython test suite by calling nose and trial.
371
387
372 This function constructs :class:`IPTester` instances for all IPython
388 This function constructs :class:`IPTester` instances for all IPython
373 modules and package and then runs each of them. This causes the modules
389 modules and package and then runs each of them. This causes the modules
374 and packages of IPython to be tested each in their own subprocess using
390 and packages of IPython to be tested each in their own subprocess using
375 nose or twisted.trial appropriately.
391 nose or twisted.trial appropriately.
376 """
392 """
377
393
378 runners = make_runners()
394 runners = make_runners()
379
395
380 # Run the test runners in a temporary dir so we can nuke it when finished
396 # Run the test runners in a temporary dir so we can nuke it when finished
381 # to clean up any junk files left over by accident. This also makes it
397 # to clean up any junk files left over by accident. This also makes it
382 # robust against being run in non-writeable directories by mistake, as the
398 # robust against being run in non-writeable directories by mistake, as the
383 # temp dir will always be user-writeable.
399 # temp dir will always be user-writeable.
384 curdir = os.getcwd()
400 curdir = os.getcwd()
385 testdir = tempfile.gettempdir()
401 testdir = tempfile.gettempdir()
386 os.chdir(testdir)
402 os.chdir(testdir)
387
403
388 # Run all test runners, tracking execution time
404 # Run all test runners, tracking execution time
389 failed = []
405 failed = []
390 t_start = time.time()
406 t_start = time.time()
391 try:
407 try:
392 for (name, runner) in runners:
408 for (name, runner) in runners:
393 print '*'*70
409 print '*'*70
394 print 'IPython test group:',name
410 print 'IPython test group:',name
395 res = runner.run()
411 res = runner.run()
396 if res:
412 if res:
397 failed.append( (name, runner) )
413 failed.append( (name, runner) )
398 finally:
414 finally:
399 os.chdir(curdir)
415 os.chdir(curdir)
400 t_end = time.time()
416 t_end = time.time()
401 t_tests = t_end - t_start
417 t_tests = t_end - t_start
402 nrunners = len(runners)
418 nrunners = len(runners)
403 nfail = len(failed)
419 nfail = len(failed)
404 # summarize results
420 # summarize results
405 print
421 print
406 print '*'*70
422 print '*'*70
407 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
423 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
408 print
424 print
409 if not failed:
425 if not failed:
410 print 'OK'
426 print 'OK'
411 else:
427 else:
412 # If anything went wrong, point out what command to rerun manually to
428 # If anything went wrong, point out what command to rerun manually to
413 # see the actual errors and individual summary
429 # see the actual errors and individual summary
414 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
430 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
415 for name, failed_runner in failed:
431 for name, failed_runner in failed:
416 print '-'*40
432 print '-'*40
417 print 'Runner failed:',name
433 print 'Runner failed:',name
418 print 'You may wish to rerun this one individually, with:'
434 print 'You may wish to rerun this one individually, with:'
419 print ' '.join(failed_runner.call_args)
435 print ' '.join(failed_runner.call_args)
420 print
436 print
421
437
422
438
423 def main():
439 def main():
424 for arg in sys.argv[1:]:
440 for arg in sys.argv[1:]:
425 if arg.startswith('IPython'):
441 if arg.startswith('IPython'):
426 # This is in-process
442 # This is in-process
427 run_iptest()
443 run_iptest()
428 else:
444 else:
429 # This starts subprocesses
445 # This starts subprocesses
430 run_iptestall()
446 run_iptestall()
431
447
432
448
433 if __name__ == '__main__':
449 if __name__ == '__main__':
434 main()
450 main()
@@ -1,317 +1,337 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools that do NOT depend on Twisted.
2
2
3 In particular, this module exposes a set of top-level assert* functions that
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
5 nose can not, at least as of nose 0.10.4).
6
6
7 Note: our testing package contains testing.util, which does depend on Twisted
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
10 instead.
11
11
12
12
13 Authors
13 Authors
14 -------
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
16 """
17
17
18 #*****************************************************************************
18 #*****************************************************************************
19 # Copyright (C) 2009 The IPython Development Team
19 # Copyright (C) 2009 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #*****************************************************************************
23 #*****************************************************************************
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Required modules and packages
26 # Required modules and packages
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 from __future__ import absolute_import
28 from __future__ import absolute_import
29
29
30 import os
30 import os
31 import re
31 import re
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34
34
35 try:
35 try:
36 # These tools are used by parts of the runtime, so we make the nose
36 # These tools are used by parts of the runtime, so we make the nose
37 # dependency optional at this point. Nose is a hard dependency to run the
37 # dependency optional at this point. Nose is a hard dependency to run the
38 # test suite, but NOT to use ipython itself.
38 # test suite, but NOT to use ipython itself.
39 import nose.tools as nt
39 import nose.tools as nt
40 has_nose = True
40 has_nose = True
41 except ImportError:
41 except ImportError:
42 has_nose = False
42 has_nose = False
43
43
44 from IPython.utils import genutils, platutils
44 from IPython.utils import genutils, platutils
45
45
46 from . import decorators as dec
46 from . import decorators as dec
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Globals
49 # Globals
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 # By default, we assume IPython has been installed. But if the test suite is
53 # being run from a source tree that has NOT been installed yet, this flag can
54 # be set to False by the entry point scripts, to let us know that we must call
55 # the source tree versions of the scripts which manipulate sys.path instead of
56 # assuming that things exist system-wide.
57 INSTALLED = True
58
52 # Make a bunch of nose.tools assert wrappers that can be used in test
59 # Make a bunch of nose.tools assert wrappers that can be used in test
53 # generators. This will expose an assert* function for each one in nose.tools.
60 # generators. This will expose an assert* function for each one in nose.tools.
54
61
55 _tpl = """
62 _tpl = """
56 def %(name)s(*a,**kw):
63 def %(name)s(*a,**kw):
57 return nt.%(name)s(*a,**kw)
64 return nt.%(name)s(*a,**kw)
58 """
65 """
59
66
60 if has_nose:
67 if has_nose:
61 for _x in [a for a in dir(nt) if a.startswith('assert')]:
68 for _x in [a for a in dir(nt) if a.startswith('assert')]:
62 exec _tpl % dict(name=_x)
69 exec _tpl % dict(name=_x)
63
70
64 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
65 # Functions and classes
72 # Functions and classes
66 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
67
74
68 # The docstring for full_path doctests differently on win32 (different path
75 # The docstring for full_path doctests differently on win32 (different path
69 # separator) so just skip the doctest there. The example remains informative.
76 # separator) so just skip the doctest there. The example remains informative.
70 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
77 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
71
78
72 @doctest_deco
79 @doctest_deco
73 def full_path(startPath,files):
80 def full_path(startPath,files):
74 """Make full paths for all the listed files, based on startPath.
81 """Make full paths for all the listed files, based on startPath.
75
82
76 Only the base part of startPath is kept, since this routine is typically
83 Only the base part of startPath is kept, since this routine is typically
77 used with a script's __file__ variable as startPath. The base of startPath
84 used with a script's __file__ variable as startPath. The base of startPath
78 is then prepended to all the listed files, forming the output list.
85 is then prepended to all the listed files, forming the output list.
79
86
80 Parameters
87 Parameters
81 ----------
88 ----------
82 startPath : string
89 startPath : string
83 Initial path to use as the base for the results. This path is split
90 Initial path to use as the base for the results. This path is split
84 using os.path.split() and only its first component is kept.
91 using os.path.split() and only its first component is kept.
85
92
86 files : string or list
93 files : string or list
87 One or more files.
94 One or more files.
88
95
89 Examples
96 Examples
90 --------
97 --------
91
98
92 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
99 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
93 ['/foo/a.txt', '/foo/b.txt']
100 ['/foo/a.txt', '/foo/b.txt']
94
101
95 >>> full_path('/foo',['a.txt','b.txt'])
102 >>> full_path('/foo',['a.txt','b.txt'])
96 ['/a.txt', '/b.txt']
103 ['/a.txt', '/b.txt']
97
104
98 If a single file is given, the output is still a list:
105 If a single file is given, the output is still a list:
99 >>> full_path('/foo','a.txt')
106 >>> full_path('/foo','a.txt')
100 ['/a.txt']
107 ['/a.txt']
101 """
108 """
102
109
103 files = genutils.list_strings(files)
110 files = genutils.list_strings(files)
104 base = os.path.split(startPath)[0]
111 base = os.path.split(startPath)[0]
105 return [ os.path.join(base,f) for f in files ]
112 return [ os.path.join(base,f) for f in files ]
106
113
107
114
108 def parse_test_output(txt):
115 def parse_test_output(txt):
109 """Parse the output of a test run and return errors, failures.
116 """Parse the output of a test run and return errors, failures.
110
117
111 Parameters
118 Parameters
112 ----------
119 ----------
113 txt : str
120 txt : str
114 Text output of a test run, assumed to contain a line of one of the
121 Text output of a test run, assumed to contain a line of one of the
115 following forms::
122 following forms::
116 'FAILED (errors=1)'
123 'FAILED (errors=1)'
117 'FAILED (failures=1)'
124 'FAILED (failures=1)'
118 'FAILED (errors=1, failures=1)'
125 'FAILED (errors=1, failures=1)'
119
126
120 Returns
127 Returns
121 -------
128 -------
122 nerr, nfail: number of errors and failures.
129 nerr, nfail: number of errors and failures.
123 """
130 """
124
131
125 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
132 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
126 if err_m:
133 if err_m:
127 nerr = int(err_m.group(1))
134 nerr = int(err_m.group(1))
128 nfail = 0
135 nfail = 0
129 return nerr, nfail
136 return nerr, nfail
130
137
131 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
138 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
132 if fail_m:
139 if fail_m:
133 nerr = 0
140 nerr = 0
134 nfail = int(fail_m.group(1))
141 nfail = int(fail_m.group(1))
135 return nerr, nfail
142 return nerr, nfail
136
143
137 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
144 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
138 re.MULTILINE)
145 re.MULTILINE)
139 if both_m:
146 if both_m:
140 nerr = int(both_m.group(1))
147 nerr = int(both_m.group(1))
141 nfail = int(both_m.group(2))
148 nfail = int(both_m.group(2))
142 return nerr, nfail
149 return nerr, nfail
143
150
144 # If the input didn't match any of these forms, assume no error/failures
151 # If the input didn't match any of these forms, assume no error/failures
145 return 0, 0
152 return 0, 0
146
153
147
154
148 # So nose doesn't think this is a test
155 # So nose doesn't think this is a test
149 parse_test_output.__test__ = False
156 parse_test_output.__test__ = False
150
157
151
158
152 def cmd2argv(cmd):
159 def cmd2argv(cmd):
153 r"""Take the path of a command and return a list (argv-style).
160 r"""Take the path of a command and return a list (argv-style).
154
161
155 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
162 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
156 .com or .bat, and ['python', cmd] otherwise.
163 .com or .bat, and ['python', cmd] otherwise.
157
164
158 This is mostly a Windows utility, to deal with the fact that the scripts in
165 This is mostly a Windows utility, to deal with the fact that the scripts in
159 Windows get wrapped in .exe entry points, so we have to call them
166 Windows get wrapped in .exe entry points, so we have to call them
160 differently.
167 differently.
161
168
162 Parameters
169 Parameters
163 ----------
170 ----------
164 cmd : string
171 cmd : string
165 The path of the command.
172 The path of the command.
166
173
167 Returns
174 Returns
168 -------
175 -------
169 argv-style list.
176 argv-style list.
170
177
171 Examples
178 Examples
172 --------
179 --------
173 In [2]: cmd2argv('/usr/bin/ipython')
180 In [2]: cmd2argv('/usr/bin/ipython')
174 Out[2]: ['python', '/usr/bin/ipython']
181 Out[2]: ['python', '/usr/bin/ipython']
175
182
176 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
183 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
177 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
184 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
178 """
185 """
179 ext = os.path.splitext(cmd)[1]
186 ext = os.path.splitext(cmd)[1]
180 if ext in ['.exe', '.com', '.bat']:
187 if ext in ['.exe', '.com', '.bat']:
181 return [cmd]
188 return [cmd]
182 else:
189 else:
183 return ['python', cmd]
190 return ['python', cmd]
184
191
185
192
186 def temp_pyfile(src, ext='.py'):
193 def temp_pyfile(src, ext='.py'):
187 """Make a temporary python file, return filename and filehandle.
194 """Make a temporary python file, return filename and filehandle.
188
195
189 Parameters
196 Parameters
190 ----------
197 ----------
191 src : string or list of strings (no need for ending newlines if list)
198 src : string or list of strings (no need for ending newlines if list)
192 Source code to be written to the file.
199 Source code to be written to the file.
193
200
194 ext : optional, string
201 ext : optional, string
195 Extension for the generated file.
202 Extension for the generated file.
196
203
197 Returns
204 Returns
198 -------
205 -------
199 (filename, open filehandle)
206 (filename, open filehandle)
200 It is the caller's responsibility to close the open file and unlink it.
207 It is the caller's responsibility to close the open file and unlink it.
201 """
208 """
202 fname = tempfile.mkstemp(ext)[1]
209 fname = tempfile.mkstemp(ext)[1]
203 f = open(fname,'w')
210 f = open(fname,'w')
204 f.write(src)
211 f.write(src)
205 f.flush()
212 f.flush()
206 return fname, f
213 return fname, f
207
214
208
215
209 def default_argv():
216 def default_argv():
210 """Return a valid default argv for creating testing instances of ipython"""
217 """Return a valid default argv for creating testing instances of ipython"""
211
218
212 return ['--quick', # so no config file is loaded
219 return ['--quick', # so no config file is loaded
213 # Other defaults to minimize side effects on stdout
220 # Other defaults to minimize side effects on stdout
214 '--colors=NoColor', '--no-term-title','--no-banner',
221 '--colors=NoColor', '--no-term-title','--no-banner',
215 '--autocall=0']
222 '--autocall=0']
216
223
217
224
218 def ipexec(fname, options=None):
225 def ipexec(fname, options=None):
219 """Utility to call 'ipython filename'.
226 """Utility to call 'ipython filename'.
220
227
221 Starts IPython witha minimal and safe configuration to make startup as fast
228 Starts IPython witha minimal and safe configuration to make startup as fast
222 as possible.
229 as possible.
223
230
224 Note that this starts IPython in a subprocess!
231 Note that this starts IPython in a subprocess!
225
232
226 Parameters
233 Parameters
227 ----------
234 ----------
228 fname : str
235 fname : str
229 Name of file to be executed (should have .py or .ipy extension).
236 Name of file to be executed (should have .py or .ipy extension).
230
237
231 options : optional, list
238 options : optional, list
232 Extra command-line flags to be passed to IPython.
239 Extra command-line flags to be passed to IPython.
233
240
234 Returns
241 Returns
235 -------
242 -------
236 (stdout, stderr) of ipython subprocess.
243 (stdout, stderr) of ipython subprocess.
237 """
244 """
238 if options is None: options = []
245 if options is None: options = []
239
246
240 # For these subprocess calls, eliminate all prompt printing so we only see
247 # For these subprocess calls, eliminate all prompt printing so we only see
241 # output from script execution
248 # output from script execution
242 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
249 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
243 cmdargs = ' '.join(default_argv() + prompt_opts + options)
250 cmdargs = ' '.join(default_argv() + prompt_opts + options)
244
251
245 _ip = get_ipython()
252 _ip = get_ipython()
246 test_dir = os.path.dirname(__file__)
253 test_dir = os.path.dirname(__file__)
247
254
248 # Find the ipython script from the package we're using, so that the test
255 # Find the ipython script from the package we're using, so that the test
249 # suite can be run from the source tree without an installed IPython
256 # suite can be run from the source tree without an installed IPython
250 p = os.path
257 p = os.path
251 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
258 if INSTALLED:
252 ipython_script = p.join(ippath, 'ipython.py')
259 ipython_cmd = platutils.find_cmd('ipython')
253 ipython_cmd = 'python "%s"' % ipython_script
260 else:
261 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
262 ipython_script = p.join(ippath, 'ipython.py')
263 ipython_cmd = 'python "%s"' % ipython_script
254 # Absolute path for filename
264 # Absolute path for filename
255 full_fname = p.join(test_dir, fname)
265 full_fname = p.join(test_dir, fname)
256 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
266 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
267 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
257 return genutils.getoutputerror(full_cmd)
268 return genutils.getoutputerror(full_cmd)
258
269
259
270
260 def ipexec_validate(fname, expected_out, expected_err=None,
271 def ipexec_validate(fname, expected_out, expected_err='',
261 options=None):
272 options=None):
262 """Utility to call 'ipython filename' and validate output/error.
273 """Utility to call 'ipython filename' and validate output/error.
263
274
264 This function raises an AssertionError if the validation fails.
275 This function raises an AssertionError if the validation fails.
265
276
266 Note that this starts IPython in a subprocess!
277 Note that this starts IPython in a subprocess!
267
278
268 Parameters
279 Parameters
269 ----------
280 ----------
270 fname : str
281 fname : str
271 Name of the file to be executed (should have .py or .ipy extension).
282 Name of the file to be executed (should have .py or .ipy extension).
272
283
273 expected_out : str
284 expected_out : str
274 Expected stdout of the process.
285 Expected stdout of the process.
275
286
276 expected_err : optional, str
287 expected_err : optional, str
277 Expected stderr of the process.
288 Expected stderr of the process.
278
289
279 options : optional, list
290 options : optional, list
280 Extra command-line flags to be passed to IPython.
291 Extra command-line flags to be passed to IPython.
281
292
282 Returns
293 Returns
283 -------
294 -------
284 None
295 None
285 """
296 """
286
297
287 import nose.tools as nt
298 import nose.tools as nt
288
299
289 out, err = ipexec(fname)
300 out, err = ipexec(fname)
301 #print 'OUT', out # dbg
302 #print 'ERR', err # dbg
303 # If there are any errors, we must check those befor stdout, as they may be
304 # more informative than simply having an empty stdout.
305 if err:
306 if expected_err:
307 nt.assert_equals(err.strip(), expected_err.strip())
308 else:
309 raise ValueError('Running file %r produced error: %r' %
310 (fname, err))
311 # If no errors or output on stderr was expected, match stdout
290 nt.assert_equals(out.strip(), expected_out.strip())
312 nt.assert_equals(out.strip(), expected_out.strip())
291 if expected_err:
292 nt.assert_equals(err.strip(), expected_err.strip())
293
313
294
314
295 class TempFileMixin(object):
315 class TempFileMixin(object):
296 """Utility class to create temporary Python/IPython files.
316 """Utility class to create temporary Python/IPython files.
297
317
298 Meant as a mixin class for test cases."""
318 Meant as a mixin class for test cases."""
299
319
300 def mktmp(self, src, ext='.py'):
320 def mktmp(self, src, ext='.py'):
301 """Make a valid python temp file."""
321 """Make a valid python temp file."""
302 fname, f = temp_pyfile(src, ext)
322 fname, f = temp_pyfile(src, ext)
303 self.tmpfile = f
323 self.tmpfile = f
304 self.fname = fname
324 self.fname = fname
305
325
306 def teardown(self):
326 def teardown(self):
307 if hasattr(self, 'tmpfile'):
327 if hasattr(self, 'tmpfile'):
308 # If the tmpfile wasn't made because of skipped tests, like in
328 # If the tmpfile wasn't made because of skipped tests, like in
309 # win32, there's nothing to cleanup.
329 # win32, there's nothing to cleanup.
310 self.tmpfile.close()
330 self.tmpfile.close()
311 try:
331 try:
312 os.unlink(self.fname)
332 os.unlink(self.fname)
313 except:
333 except:
314 # On Windows, even though we close the file, we still can't
334 # On Windows, even though we close the file, we still can't
315 # delete it. I have no clue why
335 # delete it. I have no clue why
316 pass
336 pass
317
337
@@ -1,21 +1,26 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Test script for IPython.
2 """Test script for IPython.
3
3
4 The actual ipython test script to be installed with 'python setup.py install'
4 The actual ipython test script to be installed with 'python setup.py install'
5 is in './scripts' directory, and will test IPython from an importable
5 is in './scripts' directory, and will test IPython from an importable
6 location.
6 location.
7
7
8 This file is here (ipython source root directory) to facilitate non-root
8 This file is here (ipython source root directory) to facilitate non-root
9 'zero-installation testing and development' (just copy the source tree
9 'zero-installation testing and development' (just copy the source tree
10 somewhere and run iptest.py).
10 somewhere and run iptest.py).
11
11
12 You can run this script directly, type -h to see all options."""
12 You can run this script directly, type -h to see all options."""
13
13
14 # Ensure that the imported IPython packages come from *THIS* IPython, not some
14 # Ensure that the imported IPython packages come from *THIS* IPython, not some
15 # other one that may exist system-wide
15 # other one that may exist system-wide
16 import os, sys
16 import os, sys
17 this_dir = os.path.dirname(os.path.abspath(__file__))
17 this_dir = os.path.dirname(os.path.abspath(__file__))
18 sys.path.insert(0, this_dir)
18 sys.path.insert(0, this_dir)
19
19
20 import IPython.testing.tools as t
21 import IPython.testing.iptest as ipt
22 t.INSTALLED = False
23 ipt.INSTALLED = False
24
20 # Now proceed with execution
25 # Now proceed with execution
21 execfile(os.path.join(this_dir, 'IPython', 'scripts', 'iptest'))
26 execfile(os.path.join(this_dir, 'IPython', 'scripts', 'iptest'))
General Comments 0
You need to be logged in to leave comments. Login now